From feb57aed753c1f464331bafe2b27fda03c217dfe Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 11 Jan 2024 21:12:49 +0700 Subject: [PATCH 0001/1357] fix: 34120 --- src/components/DistanceEReceipt.js | 1 + src/components/Image/index.js | 10 +++++-- src/components/Image/index.native.js | 26 ++++++++++++++++++- src/components/ImageWithSizeCalculation.tsx | 5 +++- .../MoneyRequestConfirmationList.js | 1 + ...oraryForRefactorRequestConfirmationList.js | 1 + .../ReportActionItem/ReportActionItemImage.js | 12 ++++++--- src/components/ThumbnailImage.tsx | 4 ++- .../Navigation/AppNavigator/AuthScreens.tsx | 2 +- src/pages/ErrorPage/NotFoundPage.js | 1 + 10 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 0241eea44063..5ffece092770 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -72,6 +72,7 @@ function DistanceEReceipt({transaction}) { style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} + objectPositionTop /> )} diff --git a/src/components/Image/index.js b/src/components/Image/index.js index ef1a69e19c12..58b7ec3383a4 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useEffect, useMemo} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {Image as RNImage} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -9,6 +9,7 @@ import RESIZE_MODES from './resizeModes'; function Image(props) { const {source: propsSource, isAuthTokenRequired, onLoad, session} = props; + const [aspectRatio, setAspectRatio] = useState(); /** * Check if the image source is a URL - if so the `encryptedAuthToken` is appended * to the source. @@ -38,8 +39,12 @@ function Image(props) { } RNImage.getSize(source.uri, (width, height) => { onLoad({nativeEvent: {width, height}}); + + if (props.objectPositionTop) { + setAspectRatio(height ? width / height : 'auto'); + } }); - }, [onLoad, source]); + }, [onLoad, source, props.objectPositionTop]); // Omit the props which the underlying RNImage won't use const forwardedProps = _.omit(props, ['source', 'onLoad', 'session', 'isAuthTokenRequired']); @@ -49,6 +54,7 @@ function Image(props) { // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} source={source} + style={[forwardedProps.style, aspectRatio !== undefined && {aspectRatio, height: 'auto'}, props.objectPositionTop && !aspectRatio && {opacity: 0}]} /> ); } diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index f31cfb6936d9..edea86725ae7 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,11 +1,12 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; -import React from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; +import {Image as RNImage} from 'react-native'; const dimensionsCache = new Map(); @@ -16,6 +17,7 @@ function resolveDimensions(key) { function Image(props) { // eslint-disable-next-line react/destructuring-assignment const {source, isAuthTokenRequired, session, ...rest} = props; + const [aspectRatio, setAspectRatio] = useState(); let imageSource = source; if (source && source.uri && typeof source.uri === 'number') { @@ -33,6 +35,27 @@ function Image(props) { }; } + const newSource = useMemo(() => { + if (isAuthTokenRequired) { + const authToken = lodashGet(session, 'encryptedAuthToken', null); + return {uri: `${source.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; + } + return source; + }, [source, isAuthTokenRequired]); + + useEffect(() => { + if (props.onLoad == null) { + return; + } + RNImage.getSize(newSource.uri, (width, height) => { + props.onLoad({nativeEvent: {width, height}}); + + if (props.objectPositionTop) { + setAspectRatio(height ? width / height : 'auto'); + } + }); + }, [props.onLoad, newSource]); + return ( ); } diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index b13d863d97e1..7ebdd41379c1 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -29,6 +29,8 @@ type ImageWithSizeCalculationProps = { /** Whether the image requires an authToken */ isAuthTokenRequired: boolean; + + objectPositionTop: boolean; }; /** @@ -37,7 +39,7 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, style, onMeasure, isAuthTokenRequired}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({url, style, onMeasure, isAuthTokenRequired, objectPositionTop}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const isLoadedRef = useRef(null); const [isImageCached, setIsImageCached] = useState(true); @@ -88,6 +90,7 @@ function ImageWithSizeCalculation({url, style, onMeasure, isAuthTokenRequired}: }} onError={onError} onLoad={imageLoadedSuccessfully} + objectPositionTop={objectPositionTop} /> {isLoading && !isImageCached && } diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 13dce9337673..aa33ef87a976 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -612,6 +612,7 @@ function MoneyRequestConfirmationList(props) { // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!_.isEmpty(receiptThumbnail)} + objectPositionTop /> )} {props.shouldShowSmartScanFields && ( diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 36d424ea28f2..2888f7cf2cdf 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -646,6 +646,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!_.isEmpty(receiptThumbnail)} + objectPositionTop /> )} {shouldShowSmartScanFields && ( diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 1495dcbd9111..d8f461aec138 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -72,14 +72,18 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} + objectPositionTop /> ); } else { receiptImageComponent = ( - + + + ); } diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index 3c903ee9e78f..b0fecd07093d 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -26,6 +26,7 @@ type ThumbnailImageProps = { /** Should the image be resized on load or just fit container */ shouldDynamicallyResize?: boolean; + objectPositionTop?: boolean; }; type UpdateImageSizeParams = { @@ -71,7 +72,7 @@ function calculateThumbnailImageSize(width: number, height: number, windowHeight return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; } -function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth = 200, imageHeight = 200, shouldDynamicallyResize = true}: ThumbnailImageProps) { +function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth, imageHeight, shouldDynamicallyResize = true, objectPositionTop = false}: ThumbnailImageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {windowHeight} = useWindowDimensions(); @@ -103,6 +104,7 @@ function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidt url={previewSourceURL} onMeasure={updateImageSize} isAuthTokenRequired={isAuthTokenRequired} + objectPositionTop={objectPositionTop} /> diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 7286615e6ba6..caeb65fbaa3a 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -291,7 +291,7 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom Navigation.navigate(ROUTES.HOME)}/>} /> ); From 0eeedc9bca10abc6554bd2eb5e412a35e9bbe835 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 11 Jan 2024 21:19:21 +0700 Subject: [PATCH 0002/1357] fix: redundant --- src/components/ThumbnailImage.tsx | 2 +- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 2 +- src/pages/ErrorPage/NotFoundPage.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index b0fecd07093d..b04a67fbdc7d 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -72,7 +72,7 @@ function calculateThumbnailImageSize(width: number, height: number, windowHeight return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; } -function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth, imageHeight, shouldDynamicallyResize = true, objectPositionTop = false}: ThumbnailImageProps) { +function ThumbnailImage({previewSourceURL, style, isAuthTokenRequired, imageWidth = 200, imageHeight = 200, shouldDynamicallyResize = true, objectPositionTop = false}: ThumbnailImageProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {windowHeight} = useWindowDimensions(); diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index caeb65fbaa3a..7286615e6ba6 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -291,7 +291,7 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom Navigation.navigate(ROUTES.HOME)}/>} + component={NotFoundPage} /> ); From 99c70920e29748b81f8aef54a607c3d9c3cfc51d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 15 Jan 2024 10:43:47 +0700 Subject: [PATCH 0003/1357] test --- .../ComposerWithSuggestionsEdit.tsx | 49 +++++++++++++++++++ .../report/ReportActionItemMessageEdit.tsx | 3 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx new file mode 100644 index 000000000000..2d742261419d --- /dev/null +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -0,0 +1,49 @@ +import { ComposerProps } from "@components/Composer/types"; +import Composer from "@components/Composer"; +import React, { ForwardedRef } from 'react'; +import { AnimatedProps } from "react-native-reanimated"; +import { TextInputProps } from "react-native"; + +type ComposerWithSuggestionsEditProps = { + +} + + +function ComposerWithSuggestionsEdit( + { + value, + maxLines = -1, + onKeyPress = () => {}, + style, + numberOfLines: numberOfLinesProp = 0, + onSelectionChange = () => {}, + selection = { + start: 0, + end: 0, + }, + onBlur = () => {}, + onFocus = () => {}, + onChangeText = () => {}, + id = undefined + }: ComposerWithSuggestionsEditProps & ComposerProps, + ref: ForwardedRef>>, +) { + return ( + + ) +} + +export default React.forwardRef(ComposerWithSuggestionsEdit); \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 5934c4c333cb..a4d8d5da4889 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -38,6 +38,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; +import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; type ReportActionItemMessageEditProps = { /** All the data of the action */ @@ -410,7 +411,7 @@ function ReportActionItemMessageEdit( - { textInputRef.current = el; From dee95c99adb4d6529d71d3ea2c45748c9ccbfef9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 15 Jan 2024 12:29:56 +0700 Subject: [PATCH 0004/1357] implement suggestion for edit composer --- src/pages/home/ReportScreen.js | 1 + .../ComposerWithSuggestionsEdit.tsx | 93 +++++++++++++++---- src/pages/home/report/ReportActionItem.js | 1 + .../report/ReportActionItemMessageEdit.tsx | 55 ++++++++++- src/pages/home/report/ReportActionsList.js | 2 + .../report/ReportActionsListItemRenderer.js | 3 + src/pages/home/report/ReportActionsView.js | 1 + 7 files changed, 136 insertions(+), 20 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 64e48ecd5509..59804340547a 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -523,6 +523,7 @@ function ReportScreen({ isLoadingOlderReportActions={reportMetadata.isLoadingOlderReportActions} isComposerFullSize={isComposerFullSize} policy={policy} + listHeight={listHeight} /> )} diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 2d742261419d..7acdb0c864ab 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,11 +1,25 @@ import { ComposerProps } from "@components/Composer/types"; import Composer from "@components/Composer"; -import React, { ForwardedRef } from 'react'; +import React, { Dispatch, ForwardedRef, MutableRefObject, SetStateAction, useRef, useState } from 'react'; import { AnimatedProps } from "react-native-reanimated"; import { TextInputProps } from "react-native"; +import Suggestions from '../Suggestions'; +import lodashGet from 'lodash/get'; +import * as SuggestionUtils from '@libs/SuggestionUtils'; +import useWindowDimensions from "@hooks/useWindowDimensions"; type ComposerWithSuggestionsEditProps = { - + setValue: Dispatch>, + setSelection: Dispatch>, + resetKeyboardInput: () => void, + isComposerFocused: boolean, + listHeight: number, + suggestionsRef: MutableRefObject, + updateDraft: (newValue: string) => void, + measureParentContainer: (callback: () => void) => void } @@ -24,25 +38,72 @@ function ComposerWithSuggestionsEdit( onBlur = () => {}, onFocus = () => {}, onChangeText = () => {}, + setValue = () => {}, + setSelection = () => {}, + resetKeyboardInput = () => {}, + isComposerFocused, + suggestionsRef, + listHeight, + updateDraft, + measureParentContainer, id = undefined }: ComposerWithSuggestionsEditProps & ComposerProps, ref: ForwardedRef>>, ) { + const {isSmallScreenWidth} = useWindowDimensions(); + + const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])(); + + const [composerHeight, setComposerHeight] = useState(0); + + const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length); + + console.log(hasEnoughSpaceForLargeSuggestion); + + const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion); + + return ( - + <> + { + const composerLayoutHeight = e.nativeEvent.layout.height; + if (composerHeight === composerLayoutHeight) { + return; + } + setComposerHeight(composerLayoutHeight); + }} + /> + + + + ) } diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index e490c4601d10..496e48ae3519 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -512,6 +512,7 @@ function ReportActionItem(props) { shouldDisableEmojiPicker={ (ReportUtils.chatIncludesConcierge(props.report) && User.isBlockedFromConcierge(props.blockedFromConcierge)) || ReportUtils.isArchivedRoom(props.report) } + listHeight={props.listHeight} /> )} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index a4d8d5da4889..ba247fc4be6d 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, View} from 'react-native'; +import {InteractionManager, Keyboard, NativeModules, View, findNodeHandle} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import Composer from '@components/Composer'; @@ -39,6 +39,11 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; +import convertToLTRForComposer from '@libs/convertToLTRForComposer'; +import getPlatform from '@libs/getPlatform'; + +const {RNTextInputReset} = NativeModules; + type ReportActionItemMessageEditProps = { /** All the data of the action */ @@ -62,6 +67,8 @@ type ReportActionItemMessageEditProps = { /** Stores user's preferred skin tone */ preferredSkinTone?: number; + + listHeight: number; }; // native ids @@ -69,9 +76,9 @@ const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; const isMobileSafari = Browser.isMobileSafari(); - +const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; function ReportActionItemMessageEdit( - {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, + {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, listHeight}: ReportActionItemMessageEditProps, forwardedRef: ForwardedRef, ) { const theme = useTheme(); @@ -124,6 +131,8 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); + const suggestionsRef = useRef(); + const containerRef = useRef(null); useEffect(() => { if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { @@ -253,6 +262,9 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { + if (suggestionsRef.current) { + suggestionsRef.current.resetSuggestions(); + } insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -354,6 +366,8 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { + suggestionsRef.current.triggerHotkeyActions(e); + if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; } @@ -369,6 +383,25 @@ function ReportActionItemMessageEdit( [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], ); + const resetKeyboardInput = useCallback(() => { + if (!RNTextInputReset) { + return; + } + RNTextInputReset.resetKeyboardInput(findNodeHandle(textInputRef.current)); + }, [textInputRef]); + + const measureContainer = useCallback( + (callback) => { + if (!containerRef.current) { + return; + } + containerRef.current.measureInWindow(callback); + }, + // We added isComposerFullSize in dependencies so that when this value changes, we recalculate the position of the popup + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + /** * Focus the composer text input */ @@ -378,10 +411,13 @@ function ReportActionItemMessageEdit( validateCommentMaxLength(draft); }, [draft, validateCommentMaxLength]); + + return ( <> setSelection(e.nativeEvent.selection)} + onSelectionChange={(e) => { + suggestionsRef.current.onSelectionChange(e) + setSelection(e.nativeEvent.selection) + }} + setValue={setDraft} + setSelection={setSelection} + isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} + resetKeyboardInput={resetKeyboardInput} + listHeight={listHeight} + suggestionsRef={suggestionsRef} + updateDraft={updateDraft} + measureParentContainer={measureContainer} /> diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index dba8ef2e11d0..4388e83ee728 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -136,6 +136,7 @@ function ReportActionsList({ loadOlderChats, onLayout, isComposerFullSize, + listHeight }) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -417,6 +418,7 @@ function ReportActionsList({ mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} + listHeight={listHeight} /> ), [report, linkedReportActionID, sortedReportActions, mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker], diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index a9ae2b4c73b9..6ebbe22c76f0 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -49,6 +49,7 @@ function ReportActionsListItemRenderer({ shouldHideThreadDividerLine, shouldDisplayNewMarker, linkedReportActionID, + listHeight }) { const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && @@ -62,6 +63,7 @@ function ReportActionsListItemRenderer({ parentReportID={`${report.parentReportID}`} shouldDisplayNewMarker={shouldDisplayNewMarker} index={index} + listHeight={listHeight} /> ) : ( ); } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 2758437a3962..490b86ada468 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -264,6 +264,7 @@ function ReportActionsView(props) { isLoadingOlderReportActions={props.isLoadingOlderReportActions} isLoadingNewerReportActions={props.isLoadingNewerReportActions} policy={props.policy} + listHeight={props.listHeight} /> From 5e5adb85b0a05d0d57d4fc3cbc7521d59edc0b59 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 24 Jan 2024 16:17:03 +0700 Subject: [PATCH 0005/1357] fix lint --- src/components/Image/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index edea86725ae7..1c7b239f6acc 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,12 +1,12 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; import React, {useEffect, useMemo, useState} from 'react'; +import {Image as RNImage} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -import {Image as RNImage} from 'react-native'; const dimensionsCache = new Map(); From 2e260e62b64f2c0d8601b006a59037b9fbd12fcc Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 24 Jan 2024 17:09:22 +0700 Subject: [PATCH 0006/1357] fix lint --- src/components/Image/index.native.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 1c7b239f6acc..f221a3a15019 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -41,7 +41,7 @@ function Image(props) { return {uri: `${source.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; } return source; - }, [source, isAuthTokenRequired]); + }, [source, isAuthTokenRequired, session]); useEffect(() => { if (props.onLoad == null) { @@ -54,7 +54,7 @@ function Image(props) { setAspectRatio(height ? width / height : 'auto'); } }); - }, [props.onLoad, newSource]); + }, [props.onLoad, newSource, props]); return ( Date: Thu, 25 Jan 2024 17:56:15 +0700 Subject: [PATCH 0007/1357] implement suggestion for edit composer --- src/components/Composer/types.ts | 4 ++- src/pages/home/ReportScreen.js | 1 - .../ComposerWithSuggestionsEdit.tsx | 36 ++++++------------- .../home/report/ReportActionCompose/types.ts | 14 ++++++++ src/pages/home/report/ReportActionItem.js | 1 - .../report/ReportActionItemMessageEdit.tsx | 24 ++++++------- src/pages/home/report/ReportActionsList.js | 2 -- .../report/ReportActionsListItemRenderer.js | 3 -- src/pages/home/report/ReportActionsView.js | 1 - 9 files changed, 38 insertions(+), 48 deletions(-) create mode 100644 src/pages/home/report/ReportActionCompose/types.ts diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index d8d88970ea78..6d4689ab8150 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -1,4 +1,4 @@ -import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; +import type {LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; type TextSelection = { start: number; @@ -80,6 +80,8 @@ type ComposerProps = { onBlur?: (event: NativeSyntheticEvent) => void; + onLayout?: (event: LayoutChangeEvent) => void; + /** Should make the input only scroll inside the element avoid scroll out to parent */ shouldContainScroll?: boolean; }; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index e5df63f0ff04..4d043f12351e 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -528,7 +528,6 @@ function ReportScreen({ isLoadingOlderReportActions={reportMetadata.isLoadingOlderReportActions} isComposerFullSize={isComposerFullSize} policy={policy} - listHeight={listHeight} /> )} diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 7acdb0c864ab..efc1370f7e23 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,12 +1,11 @@ -import { ComposerProps } from "@components/Composer/types"; +import type { ComposerProps } from "@components/Composer/types"; import Composer from "@components/Composer"; -import React, { Dispatch, ForwardedRef, MutableRefObject, SetStateAction, useRef, useState } from 'react'; -import { AnimatedProps } from "react-native-reanimated"; -import { TextInputProps } from "react-native"; -import Suggestions from '../Suggestions'; -import lodashGet from 'lodash/get'; -import * as SuggestionUtils from '@libs/SuggestionUtils'; -import useWindowDimensions from "@hooks/useWindowDimensions"; +import type { Dispatch, ForwardedRef, MutableRefObject, SetStateAction} from 'react'; +import React, { useState } from 'react'; +import type { AnimatedProps } from "react-native-reanimated"; +import type {TextInputProps} from "react-native"; +import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; +import type { SuggestionsRef } from "@pages/home/report/ReportActionCompose/types"; type ComposerWithSuggestionsEditProps = { setValue: Dispatch>, @@ -16,8 +15,7 @@ type ComposerWithSuggestionsEditProps = { }>>, resetKeyboardInput: () => void, isComposerFocused: boolean, - listHeight: number, - suggestionsRef: MutableRefObject, + suggestionsRef: MutableRefObject, updateDraft: (newValue: string) => void, measureParentContainer: (callback: () => void) => void } @@ -29,7 +27,6 @@ function ComposerWithSuggestionsEdit( maxLines = -1, onKeyPress = () => {}, style, - numberOfLines: numberOfLinesProp = 0, onSelectionChange = () => {}, selection = { start: 0, @@ -43,26 +40,14 @@ function ComposerWithSuggestionsEdit( resetKeyboardInput = () => {}, isComposerFocused, suggestionsRef, - listHeight, updateDraft, measureParentContainer, id = undefined }: ComposerWithSuggestionsEditProps & ComposerProps, ref: ForwardedRef>>, ) { - const {isSmallScreenWidth} = useWindowDimensions(); - - const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])(); - const [composerHeight, setComposerHeight] = useState(0); - const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length); - - console.log(hasEnoughSpaceForLargeSuggestion); - - const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion); - - return ( <> - ) } diff --git a/src/pages/home/report/ReportActionCompose/types.ts b/src/pages/home/report/ReportActionCompose/types.ts new file mode 100644 index 000000000000..8630189d45ec --- /dev/null +++ b/src/pages/home/report/ReportActionCompose/types.ts @@ -0,0 +1,14 @@ +import type { NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData } from "react-native"; + +type SuggestionsRef = { + getSuggestions: () => void; + resetSuggestions: () => void; + triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => void; + onSelectionChange: (event: NativeSyntheticEvent) => void; + updateShouldShowSuggestionMenuToFalse: () => void; + setShouldBlockSuggestionCalc: () => void; +} + + +// eslint-disable-next-line import/prefer-default-export +export type {SuggestionsRef}; \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f3a02cae06dd..80fb341a2cf8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -514,7 +514,6 @@ function ReportActionItem(props) { shouldDisableEmojiPicker={ (ReportUtils.chatIncludesConcierge(props.report) && User.isBlockedFromConcierge(props.blockedFromConcierge)) || ReportUtils.isArchivedRoom(props.report) } - listHeight={props.listHeight} /> )} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index ba247fc4be6d..7d13bd6335ca 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -6,7 +6,6 @@ import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} fr import {InteractionManager, Keyboard, NativeModules, View, findNodeHandle} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; -import Composer from '@components/Composer'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; import Icon from '@components/Icon'; @@ -39,8 +38,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import convertToLTRForComposer from '@libs/convertToLTRForComposer'; -import getPlatform from '@libs/getPlatform'; +import type { SuggestionsRef } from './ReportActionCompose/types'; const {RNTextInputReset} = NativeModules; @@ -67,8 +65,6 @@ type ReportActionItemMessageEditProps = { /** Stores user's preferred skin tone */ preferredSkinTone?: number; - - listHeight: number; }; // native ids @@ -76,9 +72,8 @@ const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; const isMobileSafari = Browser.isMobileSafari(); -const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; function ReportActionItemMessageEdit( - {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, listHeight}: ReportActionItemMessageEditProps, + {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, forwardedRef: ForwardedRef, ) { const theme = useTheme(); @@ -131,8 +126,8 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); - const suggestionsRef = useRef(); - const containerRef = useRef(null); + const suggestionsRef = useRef(); + const containerRef = useRef(null); useEffect(() => { if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { @@ -366,7 +361,9 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - suggestionsRef.current.triggerHotkeyActions(e); + if (suggestionsRef.current) { + suggestionsRef.current.triggerHotkeyActions(e); + } if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; @@ -391,7 +388,7 @@ function ReportActionItemMessageEdit( }, [textInputRef]); const measureContainer = useCallback( - (callback) => { + (callback: () => void) => { if (!containerRef.current) { return; } @@ -488,14 +485,15 @@ function ReportActionItemMessageEdit( }} selection={selection} onSelectionChange={(e) => { - suggestionsRef.current.onSelectionChange(e) + if (suggestionsRef.current) { + suggestionsRef.current.onSelectionChange(e) + } setSelection(e.nativeEvent.selection) }} setValue={setDraft} setSelection={setSelection} isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} resetKeyboardInput={resetKeyboardInput} - listHeight={listHeight} suggestionsRef={suggestionsRef} updateDraft={updateDraft} measureParentContainer={measureContainer} diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 322d9277af3f..ce8dcb10ef5f 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -136,7 +136,6 @@ function ReportActionsList({ loadOlderChats, onLayout, isComposerFullSize, - listHeight }) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -461,7 +460,6 @@ function ReportActionsList({ mostRecentIOUReportActionID={mostRecentIOUReportActionID} shouldHideThreadDividerLine={shouldHideThreadDividerLine} shouldDisplayNewMarker={shouldDisplayNewMarker(reportAction, index)} - listHeight={listHeight} /> ), [report, linkedReportActionID, sortedReportActions, mostRecentIOUReportActionID, shouldHideThreadDividerLine, shouldDisplayNewMarker], diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js index 6ebbe22c76f0..a9ae2b4c73b9 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.js +++ b/src/pages/home/report/ReportActionsListItemRenderer.js @@ -49,7 +49,6 @@ function ReportActionsListItemRenderer({ shouldHideThreadDividerLine, shouldDisplayNewMarker, linkedReportActionID, - listHeight }) { const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && @@ -63,7 +62,6 @@ function ReportActionsListItemRenderer({ parentReportID={`${report.parentReportID}`} shouldDisplayNewMarker={shouldDisplayNewMarker} index={index} - listHeight={listHeight} /> ) : ( ); } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 12179b211e6c..4843a29dde60 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -263,7 +263,6 @@ function ReportActionsView(props) { isLoadingOlderReportActions={props.isLoadingOlderReportActions} isLoadingNewerReportActions={props.isLoadingNewerReportActions} policy={props.policy} - listHeight={props.listHeight} /> From 6939350d98ef2bb731b1973febd417b365a6fffb Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 11:35:09 +0700 Subject: [PATCH 0008/1357] fix crash bug --- src/components/Image/index.js | 4 ++-- src/components/Image/index.native.js | 26 ++++---------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 58b7ec3383a4..f4962ad9b324 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -9,7 +9,7 @@ import RESIZE_MODES from './resizeModes'; function Image(props) { const {source: propsSource, isAuthTokenRequired, onLoad, session} = props; - const [aspectRatio, setAspectRatio] = useState(); + const [aspectRatio, setAspectRatio] = useState(null); /** * Check if the image source is a URL - if so the `encryptedAuthToken` is appended * to the source. @@ -54,7 +54,7 @@ function Image(props) { // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} source={source} - style={[forwardedProps.style, aspectRatio !== undefined && {aspectRatio, height: 'auto'}, props.objectPositionTop && !aspectRatio && {opacity: 0}]} + style={[forwardedProps.style, !!aspectRatio && {aspectRatio, height: 'auto'}, props.objectPositionTop && !aspectRatio && {opacity: 0}]} /> ); } diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index f221a3a15019..7da72c34a012 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -35,27 +35,6 @@ function Image(props) { }; } - const newSource = useMemo(() => { - if (isAuthTokenRequired) { - const authToken = lodashGet(session, 'encryptedAuthToken', null); - return {uri: `${source.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`}; - } - return source; - }, [source, isAuthTokenRequired, session]); - - useEffect(() => { - if (props.onLoad == null) { - return; - } - RNImage.getSize(newSource.uri, (width, height) => { - props.onLoad({nativeEvent: {width, height}}); - - if (props.objectPositionTop) { - setAspectRatio(height ? width / height : 'auto'); - } - }); - }, [props.onLoad, newSource, props]); - return ( ); } From 02732408714a4be850731bb3eb86f44960d58dbf Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 15:51:50 +0700 Subject: [PATCH 0009/1357] display suggestion below when composer at the top of the screen --- .../AutoCompleteSuggestions/index.tsx | 9 +++++-- .../AutoCompleteSuggestions/types.ts | 2 +- src/libs/SuggestionUtils.ts | 24 ++++++++++++++++++- src/styles/utils/index.ts | 3 ++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index baca4011a177..01aa6ea1fc17 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -6,6 +6,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; +import { measureHeightOfSuggestioContainer } from '@libs/SuggestionUtils'; /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, @@ -18,6 +19,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); + const suggestionContainerHeight = measureHeightOfSuggestioContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, @@ -41,9 +43,12 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} if (!measureParentContainer) { return; } - measureParentContainer((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); - }, [measureParentContainer, windowHeight, windowWidth]); + measureParentContainer((x, y, w, h) => { + const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; + setContainerState({left: x, bottom: currenBottom, width: w}) + }); + }, [measureParentContainer, windowHeight, windowWidth]); const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 61d614dcf2e4..20477af2e365 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -1,6 +1,6 @@ import type {ReactElement} from 'react'; -type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; +type MeasureParentContainerCallback = (x: number, y: number, width: number, height: number) => void; type RenderSuggestionMenuItemProps = { item: TSuggestion; diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index 96379ce49ef3..e920678b0ecc 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,4 +20,26 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu}; +const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { + const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; + let suggestionHeight = 0; + + if (isSuggestionPickerLarge) { + if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { + // On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available + suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } else { + suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } + } else { + if (numRows > 2) { + // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible + suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } else { + suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + } + } + return suggestionHeight + borderAndPadding; +}; + +export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestioContainer}; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index cdefc8bc1c91..289e3430ed40 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -782,7 +782,7 @@ type GetBaseAutoCompleteSuggestionContainerStyleParams = { */ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetBaseAutoCompleteSuggestionContainerStyleParams): ViewStyle { return { - ...positioning.pFixed, + // ...positioning.pFixed, bottom, left, width, @@ -797,6 +797,7 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING; + console.log(height); // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. From 31bf412c900678781408b37185939d8bf86de4c1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 16:52:11 +0700 Subject: [PATCH 0010/1357] fix edge case --- .../AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx | 3 ++- src/components/AutoCompleteSuggestions/index.tsx | 3 +++ src/components/AutoCompleteSuggestions/types.ts | 3 +++ src/styles/utils/index.ts | 5 ++--- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 5da9c6981603..f5e6cdc52574 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -39,6 +39,7 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, + shouldBelowParentContainer = false }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { @@ -67,7 +68,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBelowParentContainer)); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 01aa6ea1fc17..c6911366f102 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -25,6 +25,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} left: 0, bottom: 0, }); + const [shouldBelowContainer, setShouldBelowContainer] = React.useState(false); React.useEffect(() => { const container = containerRef.current; if (!container) { @@ -46,6 +47,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} measureParentContainer((x, y, w, h) => { const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; + setShouldBelowContainer(y < suggestionContainerHeight); setContainerState({left: x, bottom: currenBottom, width: w}) }); }, [measureParentContainer, windowHeight, windowWidth]); @@ -53,6 +55,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} // eslint-disable-next-line react/jsx-props-no-spreading {...props} + shouldBelowParentContainer={shouldBelowContainer} ref={containerRef} /> ); diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 20477af2e365..2f6f07d51be1 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -33,6 +33,9 @@ type AutoCompleteSuggestionsProps = { /** Meaures the parent container's position and dimensions. */ measureParentContainer?: (callback: MeasureParentContainerCallback) => void; + + /** Whether suggestion should be displayed below the parent container or not */ + shouldBelowParentContainer?: boolean }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 289e3430ed40..d8a616fa34c2 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -792,18 +792,17 @@ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetB /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBelowParentContainer: boolean): ViewStyle { 'worklet'; const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING; - console.log(height); // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth), + top: -(height + (shouldBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth)), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From 7c9c40ff933a7d2d47a39a5f391101863551aa00 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 17:01:55 +0700 Subject: [PATCH 0011/1357] fix lint --- src/components/Image/index.native.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 7da72c34a012..862807b909bb 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,7 +1,6 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; -import React, {useEffect, useMemo, useState} from 'react'; -import {Image as RNImage} from 'react-native'; +import React, { useState } from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; From a153e8a46181c676adfd995ce00759e7b4e6b5cb Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 26 Jan 2024 17:41:00 +0700 Subject: [PATCH 0012/1357] fix lint --- src/components/Image/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 862807b909bb..2791ea40925d 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -1,6 +1,6 @@ import {Image as ImageComponent} from 'expo-image'; import lodashGet from 'lodash/get'; -import React, { useState } from 'react'; +import React, {useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; From ee43388d477ef88da7414c15c69ffb1b34221325 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 29 Jan 2024 17:31:42 +0700 Subject: [PATCH 0013/1357] create global ref --- .../BaseAutoCompleteSuggestions.tsx | 2 +- .../AutoCompleteSuggestions/index.tsx | 7 +-- .../AutoCompleteSuggestions/types.ts | 2 +- src/components/EmojiSuggestions.tsx | 2 +- src/libs/SuggestionUtils.ts | 10 ++-- src/libs/actions/SuggestionsAction.ts | 50 +++++++++++++++++++ .../ComposerWithSuggestionsEdit.tsx | 45 +++++++++-------- .../home/report/ReportActionCompose/types.ts | 14 ------ .../report/ReportActionItemMessageEdit.tsx | 28 ++++------- src/pages/home/report/ReportActionsView.js | 2 + 10 files changed, 97 insertions(+), 65 deletions(-) create mode 100644 src/libs/actions/SuggestionsAction.ts delete mode 100644 src/pages/home/report/ReportActionCompose/types.ts diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index f5e6cdc52574..2b7750f732f2 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -39,7 +39,7 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, - shouldBelowParentContainer = false + shouldBelowParentContainer = false, }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index c6911366f102..bda8e0aa7711 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -4,9 +4,9 @@ import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {measureHeightOfSuggestioContainer} from '@libs/SuggestionUtils'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; -import { measureHeightOfSuggestioContainer } from '@libs/SuggestionUtils'; /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, @@ -48,9 +48,10 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} measureParentContainer((x, y, w, h) => { const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; setShouldBelowContainer(y < suggestionContainerHeight); - setContainerState({left: x, bottom: currenBottom, width: w}) + setContainerState({left: x, bottom: currenBottom, width: w}); }); - }, [measureParentContainer, windowHeight, windowWidth]); + }, [measureParentContainer, windowHeight, windowWidth, suggestionContainerHeight]); + const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 2f6f07d51be1..b837901026b2 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -35,7 +35,7 @@ type AutoCompleteSuggestionsProps = { measureParentContainer?: (callback: MeasureParentContainerCallback) => void; /** Whether suggestion should be displayed below the parent container or not */ - shouldBelowParentContainer?: boolean + shouldBelowParentContainer?: boolean; }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 1c0306741048..c95288f43164 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -9,7 +9,7 @@ import getStyledTextArray from '@libs/GetStyledTextArray'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; import Text from './Text'; -type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; +type MeasureParentContainerCallback = (x: number, y: number, width: number, height: number) => void; type EmojiSuggestionsProps = { /** The index of the highlighted emoji */ diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index e920678b0ecc..00039592ba77 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -31,13 +31,11 @@ const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLa } else { suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } + } else if (numRows > 2) { + // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible + suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } else { - if (numRows > 2) { - // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible - suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } else { - suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } + suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } return suggestionHeight + borderAndPadding; }; diff --git a/src/libs/actions/SuggestionsAction.ts b/src/libs/actions/SuggestionsAction.ts new file mode 100644 index 000000000000..9fecd18f7d10 --- /dev/null +++ b/src/libs/actions/SuggestionsAction.ts @@ -0,0 +1,50 @@ +import React from 'react'; +import type {NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; + +type SuggestionsRef = { + getSuggestions: () => void; + resetSuggestions: () => void; + triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => boolean; + onSelectionChange: (event: NativeSyntheticEvent) => void; + updateShouldShowSuggestionMenuToFalse: () => void; + setShouldBlockSuggestionCalc: () => void; +}; + +const suggestionsRef = React.createRef(); + +function resetSuggestions() { + if (!suggestionsRef.current) { + return; + } + + suggestionsRef.current.resetSuggestions(); +} + +function triggerHotkeyActions(event: NativeSyntheticEvent | KeyboardEvent): boolean { + if (!suggestionsRef.current) { + return false; + } + + return suggestionsRef.current.triggerHotkeyActions(event); +} + +function updateShouldShowSuggestionMenuToFalse() { + if (!suggestionsRef.current) { + return; + } + + suggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); +} + +function onSelectionChange(event: NativeSyntheticEvent) { + if (!suggestionsRef.current) { + return; + } + + suggestionsRef.current.onSelectionChange(event); +} + +export {suggestionsRef, resetSuggestions, triggerHotkeyActions, onSelectionChange, updateShouldShowSuggestionMenuToFalse}; + +// eslint-disable-next-line import/prefer-default-export +export type {SuggestionsRef}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index efc1370f7e23..927d160a8b7f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,25 +1,26 @@ -import type { ComposerProps } from "@components/Composer/types"; -import Composer from "@components/Composer"; -import type { Dispatch, ForwardedRef, MutableRefObject, SetStateAction} from 'react'; -import React, { useState } from 'react'; -import type { AnimatedProps } from "react-native-reanimated"; -import type {TextInputProps} from "react-native"; +import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; +import React, {useState} from 'react'; +import type {TextInputProps} from 'react-native'; +import type {AnimatedProps} from 'react-native-reanimated'; +import Composer from '@components/Composer'; +import type {ComposerProps} from '@components/Composer/types'; +import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; -import type { SuggestionsRef } from "@pages/home/report/ReportActionCompose/types"; type ComposerWithSuggestionsEditProps = { - setValue: Dispatch>, - setSelection: Dispatch>, - resetKeyboardInput: () => void, - isComposerFocused: boolean, - suggestionsRef: MutableRefObject, - updateDraft: (newValue: string) => void, - measureParentContainer: (callback: () => void) => void -} - + setValue: Dispatch>; + setSelection: Dispatch< + SetStateAction<{ + start: number; + end: number; + }> + >; + resetKeyboardInput: () => void; + isComposerFocused: boolean; + suggestionsRef: RefObject; + updateDraft: (newValue: string) => void; + measureParentContainer: (callback: () => void) => void; +}; function ComposerWithSuggestionsEdit( { @@ -42,7 +43,7 @@ function ComposerWithSuggestionsEdit( suggestionsRef, updateDraft, measureParentContainer, - id = undefined + id = undefined, }: ComposerWithSuggestionsEditProps & ComposerProps, ref: ForwardedRef>>, ) { @@ -88,7 +89,7 @@ function ComposerWithSuggestionsEdit( resetKeyboardInput={resetKeyboardInput} /> - ) + ); } -export default React.forwardRef(ComposerWithSuggestionsEdit); \ No newline at end of file +export default React.forwardRef(ComposerWithSuggestionsEdit); diff --git a/src/pages/home/report/ReportActionCompose/types.ts b/src/pages/home/report/ReportActionCompose/types.ts deleted file mode 100644 index 8630189d45ec..000000000000 --- a/src/pages/home/report/ReportActionCompose/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData } from "react-native"; - -type SuggestionsRef = { - getSuggestions: () => void; - resetSuggestions: () => void; - triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => void; - onSelectionChange: (event: NativeSyntheticEvent) => void; - updateShouldShowSuggestionMenuToFalse: () => void; - setShouldBlockSuggestionCalc: () => void; -} - - -// eslint-disable-next-line import/prefer-default-export -export type {SuggestionsRef}; \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 7d13bd6335ca..a6535b83d0f7 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {InteractionManager, Keyboard, NativeModules, View, findNodeHandle} from 'react-native'; +import {findNodeHandle, InteractionManager, Keyboard, NativeModules, View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; @@ -20,6 +20,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; @@ -38,11 +39,10 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import type { SuggestionsRef } from './ReportActionCompose/types'; +import { PortalHost } from '@gorhom/portal'; const {RNTextInputReset} = NativeModules; - type ReportActionItemMessageEditProps = { /** All the data of the action */ action: OnyxTypes.ReportAction; @@ -126,7 +126,6 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); - const suggestionsRef = useRef(); const containerRef = useRef(null); useEffect(() => { @@ -182,7 +181,7 @@ function ReportActionItemMessageEdit( // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), // so we need to ensure that it is only updated after focus. if (isMobileSafari) { - setDraft((prevDraft) => { + setDraft((prevDraft: string) => { setSelection({ start: prevDraft.length, end: prevDraft.length, @@ -257,9 +256,7 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { - if (suggestionsRef.current) { - suggestionsRef.current.resetSuggestions(); - } + SuggestionsAction.resetSuggestions(); insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -361,8 +358,8 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - if (suggestionsRef.current) { - suggestionsRef.current.triggerHotkeyActions(e); + if (SuggestionsAction.triggerHotkeyActions(e)) { + return; } if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { @@ -408,8 +405,6 @@ function ReportActionItemMessageEdit( validateCommentMaxLength(draft); }, [draft, validateCommentMaxLength]); - - return ( <> @@ -423,6 +418,7 @@ function ReportActionItemMessageEdit( hasExceededMaxCommentLength && styles.borderColorDanger, ]} > + { - if (suggestionsRef.current) { - suggestionsRef.current.onSelectionChange(e) - } - setSelection(e.nativeEvent.selection) + SuggestionsAction.onSelectionChange(e); + setSelection(e.nativeEvent.selection); }} setValue={setDraft} setSelection={setSelection} isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} resetKeyboardInput={resetKeyboardInput} - suggestionsRef={suggestionsRef} + suggestionsRef={SuggestionsAction.suggestionsRef} updateDraft={updateDraft} measureParentContainer={measureContainer} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 4843a29dde60..1c0719645d2f 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -11,6 +11,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import usePrevious from '@hooks/usePrevious'; +import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; @@ -255,6 +256,7 @@ function ReportActionsView(props) { Date: Tue, 30 Jan 2024 16:29:37 +0700 Subject: [PATCH 0014/1357] fix the case wide image --- src/components/Image/index.js | 4 ++++ src/components/Image/index.native.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/components/Image/index.js b/src/components/Image/index.js index f4962ad9b324..848e64f60350 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -41,6 +41,10 @@ function Image(props) { onLoad({nativeEvent: {width, height}}); if (props.objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } setAspectRatio(height ? width / height : 'auto'); } }); diff --git a/src/components/Image/index.native.js b/src/components/Image/index.native.js index 2791ea40925d..b6d5f5b2dcda 100644 --- a/src/components/Image/index.native.js +++ b/src/components/Image/index.native.js @@ -46,6 +46,10 @@ function Image(props) { props.onLoad({nativeEvent: {width, height}}); } if (props.objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } setAspectRatio(height ? width / height : 'auto'); } }} From 00ccc9bd4e75c8a480313f11ecf986725eb6a83e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 30 Jan 2024 16:34:02 +0700 Subject: [PATCH 0015/1357] add comment prop --- src/components/Image/imagePropTypes.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Image/imagePropTypes.js b/src/components/Image/imagePropTypes.js index 78bd48ba47ec..cc4c36e32387 100644 --- a/src/components/Image/imagePropTypes.js +++ b/src/components/Image/imagePropTypes.js @@ -34,6 +34,9 @@ const imagePropTypes = { /** Currently logged in user authToken */ authToken: PropTypes.string, }), + + /** Whether we should show the top of the image */ + objectPositionTop: PropTypes.string }; const defaultProps = { @@ -43,6 +46,7 @@ const defaultProps = { }, isAuthTokenRequired: false, resizeMode: RESIZE_MODES.cover, + objectPositionTop: false, onLoadStart: () => {}, onLoadEnd: () => {}, onLoad: () => {}, From c761e1c17e8795c514904e167f23e42c9fa7aa27 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 30 Jan 2024 18:08:45 +0700 Subject: [PATCH 0016/1357] merge main --- src/components/Image/imagePropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Image/imagePropTypes.js b/src/components/Image/imagePropTypes.js index cc4c36e32387..5d28b1a9f3f4 100644 --- a/src/components/Image/imagePropTypes.js +++ b/src/components/Image/imagePropTypes.js @@ -36,7 +36,7 @@ const imagePropTypes = { }), /** Whether we should show the top of the image */ - objectPositionTop: PropTypes.string + objectPositionTop: PropTypes.string, }; const defaultProps = { From d09ee205845c7b47eb4b6cdc49c803dc8be43918 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 2 Feb 2024 10:41:02 +0700 Subject: [PATCH 0017/1357] centralize logic --- src/components/Image/BaseImage.native.tsx | 16 +++------------- src/components/Image/BaseImage.tsx | 16 +++------------- src/components/Image/index.js | 22 ++++++++++++++++++++-- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/components/Image/BaseImage.native.tsx b/src/components/Image/BaseImage.native.tsx index e912bdda9eba..c517efd04515 100644 --- a/src/components/Image/BaseImage.native.tsx +++ b/src/components/Image/BaseImage.native.tsx @@ -1,10 +1,9 @@ import {Image as ExpoImage} from 'expo-image'; import type {ImageProps as ExpoImageProps, ImageLoadEventData} from 'expo-image'; -import {useCallback, useState} from 'react'; +import {useCallback} from 'react'; import type {BaseImageProps} from './types'; -function BaseImage({onLoad, objectPositionTop = false, style, ...props}: ExpoImageProps & BaseImageProps) { - const [aspectRatio, setAspectRatio] = useState(null); +function BaseImage({onLoad, ...props}: ExpoImageProps & BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: ImageLoadEventData) => { if (!onLoad) { @@ -14,23 +13,14 @@ function BaseImage({onLoad, objectPositionTop = false, style, ...props}: ExpoIma // We override `onLoad`, so both web and native have the same signature const {width, height} = event.source; onLoad({nativeEvent: {width, height}}); - - if (objectPositionTop) { - if (width > height) { - setAspectRatio(1); - return; - } - setAspectRatio(height ? width / height : 'auto'); - } }, - [onLoad, objectPositionTop], + [onLoad], ); return ( diff --git a/src/components/Image/BaseImage.tsx b/src/components/Image/BaseImage.tsx index 9a3f4668647e..ebdd76840267 100644 --- a/src/components/Image/BaseImage.tsx +++ b/src/components/Image/BaseImage.tsx @@ -1,10 +1,9 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback} from 'react'; import {Image as RNImage} from 'react-native'; import type {ImageLoadEventData, ImageProps as WebImageProps} from 'react-native'; import type {BaseImageProps} from './types'; -function BaseImage({onLoad, objectPositionTop = false, style, ...props}: WebImageProps & BaseImageProps) { - const [aspectRatio, setAspectRatio] = useState(null); +function BaseImage({onLoad, ...props}: WebImageProps & BaseImageProps) { const imageLoadedSuccessfully = useCallback( (event: {nativeEvent: ImageLoadEventData}) => { if (!onLoad) { @@ -14,23 +13,14 @@ function BaseImage({onLoad, objectPositionTop = false, style, ...props}: WebImag // We override `onLoad`, so both web and native have the same signature const {width, height} = event.nativeEvent.source; onLoad({nativeEvent: {width, height}}); - - if (objectPositionTop) { - if (width > height) { - setAspectRatio(1); - return; - } - setAspectRatio(height ? width / height : 'auto'); - } }, - [onLoad, objectPositionTop], + [onLoad], ); return ( diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 8cee1cf95e14..2788e85895f6 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -7,7 +7,9 @@ import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -function Image({source: propsSource, isAuthTokenRequired, session, ...forwardedProps}) { +function Image({source: propsSource, isAuthTokenRequired, session, onLoad, ...forwardedProps}) { + const [aspectRatio, setAspectRatio] = useState(null); + // Update the source to include the auth token if required const source = useMemo(() => { if (typeof lodashGet(propsSource, 'uri') === 'number') { @@ -28,11 +30,27 @@ function Image({source: propsSource, isAuthTokenRequired, session, ...forwardedP // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); + const imageLoadedSuccessfully = useCallback((event) => { + const {width, height} = event.nativeEvent; + + onLoad(event); + + if (objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } + setAspectRatio(height ? width / height : 'auto'); + } + }) + return ( ); } From 7cf8fd81cf99cd3a8cfa869f33be36c271081e65 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 2 Feb 2024 10:56:55 +0700 Subject: [PATCH 0018/1357] fix lint --- src/components/Image/imagePropTypes.js | 2 +- src/components/Image/index.js | 29 ++++++++++++++------------ src/components/Image/types.ts | 3 --- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/Image/imagePropTypes.js b/src/components/Image/imagePropTypes.js index 5d28b1a9f3f4..deb7d75da5ae 100644 --- a/src/components/Image/imagePropTypes.js +++ b/src/components/Image/imagePropTypes.js @@ -36,7 +36,7 @@ const imagePropTypes = { }), /** Whether we should show the top of the image */ - objectPositionTop: PropTypes.string, + objectPositionTop: PropTypes.bool, }; const defaultProps = { diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 2788e85895f6..6851604be8b1 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,5 @@ import lodashGet from 'lodash/get'; -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -7,8 +7,8 @@ import BaseImage from './BaseImage'; import {defaultProps, imagePropTypes} from './imagePropTypes'; import RESIZE_MODES from './resizeModes'; -function Image({source: propsSource, isAuthTokenRequired, session, onLoad, ...forwardedProps}) { - const [aspectRatio, setAspectRatio] = useState(null); +function Image({source: propsSource, isAuthTokenRequired, session, onLoad, style, objectPositionTop, ...forwardedProps}) { + const [aspectRatio, setAspectRatio] = useState(null); // Update the source to include the auth token if required const source = useMemo(() => { @@ -30,19 +30,22 @@ function Image({source: propsSource, isAuthTokenRequired, session, onLoad, ...fo // eslint-disable-next-line react-hooks/exhaustive-deps }, [propsSource, isAuthTokenRequired]); - const imageLoadedSuccessfully = useCallback((event) => { - const {width, height} = event.nativeEvent; + const imageLoadedSuccessfully = useCallback( + (event) => { + const {width, height} = event.nativeEvent; - onLoad(event); + onLoad(event); - if (objectPositionTop) { - if (width > height) { - setAspectRatio(1); - return; + if (objectPositionTop) { + if (width > height) { + setAspectRatio(1); + return; + } + setAspectRatio(height ? width / height : 'auto'); } - setAspectRatio(height ? width / height : 'auto'); - } - }) + }, + [onLoad, objectPositionTop], + ); return ( void; - - /** Whether we should show the top of the image */ - objectPositionTop?: boolean; }; export type {BaseImageProps}; From 86190f61d6f9f090ea0714939a15685c57cdc246 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 6 Feb 2024 01:01:01 +0700 Subject: [PATCH 0019/1357] revert hard code --- .../ReportActionItem/ReportActionItemImage.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 7dc4cdf07031..ed46e00c7923 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -73,13 +73,11 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr ); } else { receiptImageComponent = ( - - - + ); } From bd0ccb7e82426ce3ef30b1c8aa5ba6a569ec7eb5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 6 Feb 2024 23:42:11 +0700 Subject: [PATCH 0020/1357] merge main --- .../ComposerWithSuggestionsEdit.tsx | 5 ++--- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 927d160a8b7f..02a629c954e2 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,7 +1,6 @@ import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; import React, {useState} from 'react'; -import type {TextInputProps} from 'react-native'; -import type {AnimatedProps} from 'react-native-reanimated'; +import type {TextInput} from 'react-native'; import Composer from '@components/Composer'; import type {ComposerProps} from '@components/Composer/types'; import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; @@ -45,7 +44,7 @@ function ComposerWithSuggestionsEdit( measureParentContainer, id = undefined, }: ComposerWithSuggestionsEditProps & ComposerProps, - ref: ForwardedRef>>, + ref: ForwardedRef, ) { const [composerHeight, setComposerHeight] = useState(0); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 8dad9fce6003..a89b509a5f9a 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -1,3 +1,4 @@ +import {PortalHost} from '@gorhom/portal'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; @@ -39,7 +40,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import { PortalHost } from '@gorhom/portal'; const {RNTextInputReset} = NativeModules; From 3088672dda44f8eb7173c0545ef1c1adfebd4498 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 13 Feb 2024 18:44:45 +0500 Subject: [PATCH 0021/1357] perf: use context for shared reports access --- src/App.tsx | 4 + .../LHNOptionsList/LHNOptionsList.tsx | 6 +- src/components/LHNOptionsList/types.ts | 3 - src/hooks/useOrderedReportIDs.tsx | 145 +++++++++++ src/hooks/useReports.tsx | 44 ++++ .../AppNavigator/ReportScreenIDSetter.ts | 13 +- src/libs/SidebarUtils.ts | 57 ++--- src/pages/home/sidebar/SidebarLinksData.js | 228 +----------------- src/pages/workspace/WorkspacesListPage.tsx | 12 +- src/types/onyx/PriorityMode.ts | 6 + 10 files changed, 228 insertions(+), 290 deletions(-) create mode 100644 src/hooks/useOrderedReportIDs.tsx create mode 100644 src/hooks/useReports.tsx create mode 100644 src/types/onyx/PriorityMode.ts diff --git a/src/App.tsx b/src/App.tsx index 7c1ead1d86d3..712b82c21291 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,8 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; +import {OrderedReportIDsContextProvider} from './hooks/useOrderedReportIDs'; +import {ReportsContextProvider} from './hooks/useReports'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import * as Session from './libs/actions/Session'; import * as Environment from './libs/Environment/Environment'; @@ -79,6 +81,8 @@ function App({url}: AppProps) { EnvironmentProvider, CustomStatusBarAndBackgroundContextProvider, ActiveWorkspaceContextProvider, + ReportsContextProvider, + OrderedReportIDsContextProvider, ]} > diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index a55d329f39ee..5ab105eda8df 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,6 +5,7 @@ import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import usePermissions from '@hooks/usePermissions'; +import {useReports} from '@hooks/useReports'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; @@ -22,7 +23,6 @@ function LHNOptionsList({ onSelectRow, optionMode, shouldDisableFocusOptions = false, - reports = {}, reportActions = {}, policy = {}, preferredLocale = CONST.LOCALES.DEFAULT, @@ -33,6 +33,7 @@ function LHNOptionsList({ transactionViolations = {}, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { + const reports = useReports(); const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); @@ -134,9 +135,6 @@ LHNOptionsList.displayName = 'LHNOptionsList'; export default withCurrentReportID( withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, reportActions: { key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, }, diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 58bea97f04c9..c58770c8383f 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -15,9 +15,6 @@ type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxCollection; - /** All reports shared with the user */ - reports: OnyxCollection; - /** Array of report actions for this report */ reportActions: OnyxCollection; diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx new file mode 100644 index 000000000000..3217830054de --- /dev/null +++ b/src/hooks/useOrderedReportIDs.tsx @@ -0,0 +1,145 @@ +import React, {createContext, useContext, useMemo} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {getCurrentUserAccountID} from '@libs/actions/Report'; +import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import SidebarUtils from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Beta, Policy, PolicyMembers, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type PriorityMode from '@src/types/onyx/PriorityMode'; +import useActiveWorkspace from './useActiveWorkspace'; +import useCurrentReportID from './useCurrentReportID'; +import {useReports} from './useReports'; + +type OnyxProps = { + betas: OnyxEntry; + policies: OnyxCollection; + allReportActions: OnyxCollection; + transactionViolations: OnyxCollection; + policyMembers: OnyxCollection; + priorityMode: OnyxEntry; +}; + +type WithOrderedReportIDsContextProviderProps = OnyxProps & { + children: React.ReactNode; +}; + +const OrderedReportIDsContext = createContext({}); + +function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextProviderProps) { + const chatReports = useReports(); + const currentReportIDValue = useCurrentReportID(); + const {activeWorkspaceID} = useActiveWorkspace(); + + const policyMemberAccountIDs = useMemo( + () => getPolicyMembersByIdWithoutCurrentUser(props.policyMembers, activeWorkspaceID, getCurrentUserAccountID()), + [activeWorkspaceID, props.policyMembers], + ); + + const optionListItems = useMemo( + () => + SidebarUtils.getOrderedReportIDs( + null, + chatReports, + props.betas ?? [], + props.policies, + props.priorityMode, + props.allReportActions, + props.transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ), + [chatReports, props.betas, props.policies, props.priorityMode, props.allReportActions, props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs], + ); + + // We need to make sure the current report is in the list of reports, but we do not want + // to have to re-generate the list every time the currentReportID changes. To do that + // we first generate the list as if there was no current report, then here we check if + // the current report is missing from the list, which should very rarely happen. In this + // case we re-generate the list a 2nd time with the current report included. + const optionListItemsWithCurrentReport = useMemo(() => { + if (currentReportIDValue?.currentReportID && !optionListItems.includes(currentReportIDValue.currentReportID)) { + return SidebarUtils.getOrderedReportIDs( + currentReportIDValue.currentReportID, + chatReports, + props.betas ?? [], + props.policies, + props.priorityMode, + props.allReportActions, + props.transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ); + } + return optionListItems; + }, [ + activeWorkspaceID, + chatReports, + currentReportIDValue?.currentReportID, + optionListItems, + policyMemberAccountIDs, + props.allReportActions, + props.betas, + props.policies, + props.priorityMode, + props.transactionViolations, + ]); + + return {props.children}; +} + +const reportActionsSelector = (reportActions: OnyxEntry) => { + if (!reportActions) { + return []; + } + + return Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, originalMessage} = reportAction ?? {}; + const decision = reportAction?.message?.[0]?.moderationDecision?.decision; + return { + reportActionID, + actionName, + originalMessage, + message: [ + { + moderationDecision: {decision}, + }, + ], + }; + }); +}; + +const OrderedReportIDsContextProvider = withOnyx({ + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + initialValue: CONST.PRIORITY_MODE.DEFAULT, + }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + // @ts-expect-error Need some help in determining the correct type for this selector + selector: (actions) => reportActionsSelector(actions), + initialValue: {}, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + initialValue: {}, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, +})(WithOrderedReportIDsContextProvider); + +function useOrderedReportIDs() { + return useContext(OrderedReportIDsContext); +} + +export {OrderedReportIDsContextProvider, OrderedReportIDsContext, useOrderedReportIDs}; diff --git a/src/hooks/useReports.tsx b/src/hooks/useReports.tsx new file mode 100644 index 000000000000..c4082149cbf4 --- /dev/null +++ b/src/hooks/useReports.tsx @@ -0,0 +1,44 @@ +import React, {createContext, useContext, useEffect, useMemo, useState} from 'react'; +import Onyx from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; + +type Reports = OnyxCollection; +type ReportsContextValue = Reports; + +type ReportsContextProviderProps = { + children: React.ReactNode; +}; + +const ReportsContext = createContext(null); + +function ReportsContextProvider(props: ReportsContextProviderProps) { + const [reports, setReports] = useState(null); + + useEffect(() => { + // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs + const connID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (val) => { + setReports(val); + }, + }); + return () => { + Onyx.disconnect(connID); + }; + }, []); + + const contextValue = useMemo(() => reports ?? {}, [reports]); + + return {props.children}; +} + +function useReports() { + return useContext(ReportsContext); +} + +ReportsContextProvider.displayName = 'ReportsContextProvider'; + +export {ReportsContextProvider, ReportsContext, useReports}; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index b4bb56262860..83df18d5492d 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,6 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; +import {useReports} from '@hooks/useReports'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; @@ -11,9 +12,6 @@ import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/ony import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { - /** Available reports that would be displayed in this navigator */ - reports: OnyxCollection; - /** The policies which the user has access to */ policies: OnyxCollection; @@ -59,9 +57,10 @@ const getLastAccessedReportID = ( }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); + const reports = useReports(); useEffect(() => { // Don't update if there is a reportID in the params already @@ -83,7 +82,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, - !!reports?.params?.openOnAdminRoom, + !!route?.params?.openOnAdminRoom, reportMetadata, activeWorkspaceID, policyMemberAccountIDs, @@ -106,10 +105,6 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav ReportScreenIDSetter.displayName = 'ReportScreenIDSetter'; export default withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - allowStaleData: true, - }, policies: { key: ONYXKEYS.COLLECTION.POLICY, allowStaleData: true, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2347f5b9f5c5..e51276c5c28a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -2,12 +2,12 @@ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; import type Policy from '@src/types/onyx/Policy'; +import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; @@ -78,59 +78,23 @@ function setIsSidebarLoadedReady() { resolveSidebarIsReadyPromise(); } -// Define a cache object to store the memoized results -const reportIDsCache = new Map(); - -// Function to set a key-value pair while maintaining the maximum key limit -function setWithLimit(map: Map, key: TKey, value: TValue) { - if (map.size >= 5) { - // If the map has reached its limit, remove the first (oldest) key-value pair - const firstKey = map.keys().next().value; - map.delete(firstKey); - } - map.set(key, value); -} - -// Variable to verify if ONYX actions are loaded -let hasInitialReportActions = false; - /** * @returns An array of reportIDs sorted in the proper order */ function getOrderedReportIDs( currentReportId: string | null, - allReports: Record, + allReports: OnyxCollection, betas: Beta[], - policies: Record, - priorityMode: ValueOf, + policies: OnyxCollection, + priorityMode: OnyxEntry, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], ): string[] { - const currentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]; - let reportActionCount = currentReportActions?.length ?? 0; - reportActionCount = Math.max(reportActionCount, 1); - - // Generate a unique cache key based on the function arguments - const cachedReportsKey = JSON.stringify( - [currentReportId, allReports, betas, policies, priorityMode, reportActionCount, currentPolicyID, policyMemberAccountIDs], - // Exclude some properties not to overwhelm a cached key value with huge data, which we don't need to store in a cacheKey - (key, value: unknown) => (['participantAccountIDs', 'participants', 'lastMessageText', 'visibleChatMemberAccountIDs'].includes(key) ? undefined : value), - ); - - // Check if the result is already in the cache - const cachedIDs = reportIDsCache.get(cachedReportsKey); - if (cachedIDs && hasInitialReportActions) { - return cachedIDs; - } - - // This is needed to prevent caching when Onyx is empty for a second render - hasInitialReportActions = Object.values(lastReportActions).length > 0; - const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; - const allReportsDictValues = Object.values(allReports); + const allReportsDictValues = Object.values(allReports ?? {}); // Filter out all the reports that shouldn't be displayed let reportsToDisplay = allReportsDictValues.filter((report) => { @@ -173,10 +137,18 @@ function getOrderedReportIDs( const archivedReports: Report[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { - reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID)); + reportsToDisplay = reportsToDisplay.filter((report) => { + if (!report) { + return false; + } + return ReportUtils.doesReportBelongToWorkspace(report, policyMemberAccountIDs, currentPolicyID); + }); } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((report) => { + if (!report) { + return; + } // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. @@ -219,7 +191,6 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); - setWithLimit(reportIDsCache, cachedReportsKey, LHNReports); return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 3bd538e8beab..42681fea451b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -1,11 +1,8 @@ import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -13,13 +10,11 @@ import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalD import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; +import {useOrderedReportIDs} from '@hooks/useOrderedReportIDs'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import SidebarUtils from '@libs/SidebarUtils'; -import reportPropTypes from '@pages/reportPropTypes'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -28,107 +23,30 @@ import SidebarLinks, {basePropTypes} from './SidebarLinks'; const propTypes = { ...basePropTypes, - /* Onyx Props */ - /** List of reports */ - chatReports: PropTypes.objectOf(reportPropTypes), - - /** All report actions for all reports */ - - /** Object of report actions for this report */ - allReportActions: PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.arrayOf( - PropTypes.shape({ - moderationDecision: PropTypes.shape({ - decision: PropTypes.string, - }), - }), - ), - }), - ), - ), - /** Whether the reports are loading. When false it means they are ready to be used. */ isLoadingApp: PropTypes.bool, /** The chat priority mode */ priorityMode: PropTypes.string, - /** Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - network: networkPropTypes.isRequired, - /** The policies which the user has access to */ - // eslint-disable-next-line react/forbid-prop-types - policies: PropTypes.object, - - // eslint-disable-next-line react/forbid-prop-types - policyMembers: PropTypes.object, - /** Session info for the currently logged in user. */ session: PropTypes.shape({ /** Currently logged in user accountID */ accountID: PropTypes.number, }), - /** All of the transaction violations */ - transactionViolations: PropTypes.shape({ - violations: PropTypes.arrayOf( - PropTypes.shape({ - /** The transaction ID */ - transactionID: PropTypes.number, - - /** The transaction violation type */ - type: PropTypes.string, - - /** The transaction violation message */ - message: PropTypes.string, - - /** The transaction violation data */ - data: PropTypes.shape({ - /** The transaction violation data field */ - field: PropTypes.string, - - /** The transaction violation data value */ - value: PropTypes.string, - }), - }), - ), - }), }; const defaultProps = { - chatReports: {}, - allReportActions: {}, isLoadingApp: true, priorityMode: CONST.PRIORITY_MODE.DEFAULT, - betas: [], - policies: {}, - policyMembers: {}, session: { accountID: '', }, - transactionViolations: {}, }; -function SidebarLinksData({ - isFocused, - allReportActions, - betas, - chatReports, - currentReportID, - insets, - isLoadingApp, - onLinkClick, - policies, - priorityMode, - network, - policyMembers, - session: {accountID}, - transactionViolations, -}) { +function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, session: {accountID}}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); @@ -141,19 +59,8 @@ function SidebarLinksData({ const reportIDsRef = useRef(null); const isLoading = isLoadingApp; + const reportIDs = useOrderedReportIDs(); const optionListItems = useMemo(() => { - const reportIDs = SidebarUtils.getOrderedReportIDs( - null, - chatReports, - betas, - policies, - priorityMode, - allReportActions, - transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - ); - if (deepEqual(reportIDsRef.current, reportIDs)) { return reportIDsRef.current; } @@ -165,29 +72,7 @@ function SidebarLinksData({ reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; - }, [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, isLoading, network.isOffline, prevPriorityMode]); - - // We need to make sure the current report is in the list of reports, but we do not want - // to have to re-generate the list every time the currentReportID changes. To do that - // we first generate the list as if there was no current report, then here we check if - // the current report is missing from the list, which should very rarely happen. In this - // case we re-generate the list a 2nd time with the current report included. - const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportID && !_.contains(optionListItems, currentReportID)) { - return SidebarUtils.getOrderedReportIDs( - currentReportID, - chatReports, - betas, - policies, - priorityMode, - allReportActions, - transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - ); - } - return optionListItems; - }, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs]); + }, [reportIDs, isLoading, network.isOffline, prevPriorityMode, priorityMode]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; @@ -207,8 +92,8 @@ function SidebarLinksData({ // Data props: isActiveReport={isActiveReport} isLoading={isLoading} - optionListItems={optionListItemsWithCurrentReport} activeWorkspaceID={activeWorkspaceID} + optionListItems={optionListItems} /> ); @@ -218,97 +103,12 @@ SidebarLinksData.propTypes = propTypes; SidebarLinksData.defaultProps = defaultProps; SidebarLinksData.displayName = 'SidebarLinksData'; -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - * @param {Object} [report] - * @returns {Object|undefined} - */ -const chatReportSelector = (report) => - report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, - }, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }; - -/** - * @param {Object} [reportActions] - * @returns {Object|undefined} - */ -const reportActionsSelector = (reportActions) => - reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = []} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); - - return { - reportActionID, - parentReportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ], - }; - }); - -/** - * @param {Object} [policy] - * @returns {Object|undefined} - */ -const policySelector = (policy) => - policy && { - type: policy.type, - name: policy.name, - avatar: policy.avatar, - }; - export default compose( withCurrentReportID, withCurrentUserPersonalDetails, withNavigationFocus, withNetwork(), withOnyx({ - chatReports: { - key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, - initialValue: {}, - }, isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, }, @@ -316,26 +116,8 @@ export default compose( key: ONYXKEYS.NVP_PRIORITY_MODE, initialValue: CONST.PRIORITY_MODE.DEFAULT, }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], - }, - allReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, - initialValue: {}, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - selector: policySelector, - initialValue: {}, - }, policyMembers: { key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - initialValue: {}, - }, }), )(SidebarLinksData); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index aa4f1df1ba7a..5efc94ba5974 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -20,6 +20,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import {useReports} from '@hooks/useReports'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -33,7 +34,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report} from '@src/types/onyx'; +import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -72,9 +73,6 @@ type WorkspaceListPageOnyxProps = { /** A collection of objects for all policies which key policy member objects by accountIDs */ allPolicyMembers: OnyxCollection; - - /** All reports shared with the user (coming from Onyx) */ - reports: OnyxCollection; }; type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps; @@ -110,7 +108,8 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } -function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) { +function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: WorkspaceListPageProps) { + const reports = useReports(); const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -407,8 +406,5 @@ export default withPolicyAndFullscreenLoading( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, })(WorkspacesListPage), ); diff --git a/src/types/onyx/PriorityMode.ts b/src/types/onyx/PriorityMode.ts new file mode 100644 index 000000000000..5fef300ed014 --- /dev/null +++ b/src/types/onyx/PriorityMode.ts @@ -0,0 +1,6 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type PriorityMode = ValueOf; + +export default PriorityMode; From 6921051449b209820347247511a943d8b4624ffb Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 15 Feb 2024 19:19:58 +0500 Subject: [PATCH 0022/1357] perf: improve renderItem and use FlatList --- .../LHNOptionsList/LHNOptionsList.tsx | 114 +++--------------- .../LHNOptionsList/OptionRowLHNData.tsx | 51 +------- src/components/LHNOptionsList/types.ts | 3 +- src/components/withCurrentReportID.tsx | 10 +- src/hooks/useOrderedReportIDs.tsx | 38 +++++- src/libs/SidebarUtils.ts | 40 +++++- src/pages/home/sidebar/SidebarLinks.js | 4 +- src/pages/home/sidebar/SidebarLinksData.js | 2 +- src/pages/workspace/WorkspacesListPage.tsx | 4 +- 9 files changed, 111 insertions(+), 155 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index e0a869f5222a..c70e355c46c5 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,41 +1,27 @@ -import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; -import {StyleSheet, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import withCurrentReportID from '@components/withCurrentReportID'; -import usePermissions from '@hooks/usePermissions'; -import {useReports} from '@hooks/useReports'; +import React, {useCallback, memo} from 'react'; +import {FlatList, StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListProps, RenderItemProps} from './types'; +import { OrderedReports } from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import variables from '@styles/variables'; +import { FlashList } from '@shopify/flash-list'; -const keyExtractor = (item: string) => `report_${item}`; +const keyExtractor = (item: OrderedReports) => `report_${item?.reportID}`; function LHNOptionsList({ style, contentContainerStyles, data, onSelectRow, - optionMode, shouldDisableFocusOptions = false, - reportActions = {}, - policy = {}, - preferredLocale = CONST.LOCALES.DEFAULT, - personalDetails = {}, - transactions = {}, currentReportID = '', - draftComments = {}, - transactionViolations = {}, + optionMode, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { - const reports = useReports(); const styles = useThemeStyles(); - const {canUseViolations} = usePermissions(); // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. @@ -53,69 +39,29 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item: reportID}: RenderItemProps): ReactElement => { - const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; - const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; - const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); - const lastReportAction = sortedReportActions[0]; - - // Get the transaction for the last report action - let lastReportActionTransactionID = ''; - - if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? ''; - } - const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`] ?? {}; + ({item}: RenderItemProps): ReactElement => { return ( ); }, [ currentReportID, - draftComments, onSelectRow, - optionMode, - personalDetails, - policy, - preferredLocale, - reportActions, - reports, - shouldDisableFocusOptions, - transactions, - transactionViolations, - canUseViolations, onLayoutItem, ], ); return ( - @@ -133,30 +79,6 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default withCurrentReportID( - withOnyx({ - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policy: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - draftComments: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - }, - })(memo(LHNOptionsList)), -); +export default memo(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index a18d5a8ec1ec..622d7b9d9123 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -16,61 +16,12 @@ import type {OptionRowLHNDataProps} from './types'; */ function OptionRowLHNData({ isFocused = false, - fullReport, - reportActions, - personalDetails = {}, - preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy, - receiptTransactions, - parentReportAction, - transaction, - lastReportActionTransaction = {}, - transactionViolations, - canUseViolations, + optionItem, ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(); - - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null); - - const optionItem = useMemo(() => { - // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData({ - report: fullReport, - reportActions, - personalDetails, - preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, - policy, - parentReportAction, - hasViolations: !!hasViolations, - }); - if (deepEqual(item, optionItemRef.current)) { - return optionItemRef.current; - } - - optionItemRef.current = item; - - return item; - // Listen parentReportAction to update title of thread report when parentReportAction changed - // Listen to transaction to update title of transaction report when transaction changed - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - fullReport, - lastReportActionTransaction, - reportActions, - personalDetails, - preferredLocale, - policy, - parentReportAction, - transaction, - transactionViolations, - canUseViolations, - receiptTransactions, - ]); - useEffect(() => { if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c58770c8383f..4c79536571bf 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -8,6 +8,7 @@ import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import { OrderedReports } from '@libs/SidebarUtils'; type OptionMode = ValueOf; @@ -134,6 +135,6 @@ type OptionRowLHNProps = { onLayout?: (event: LayoutChangeEvent) => void; }; -type RenderItemProps = {item: string}; +type RenderItemProps = {item: OrderedReports}; export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index a452e7565b4e..cc49c44e0e77 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,7 +39,15 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); + const reportID = Navigation.getTopmostReportId(state) ?? ''; + /** + * This is to make sure we don't set the undefined as reportID when + * switching between chat list and settings->workspaces tab. + * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. + */ + if (reportID) { + setCurrentReportID(reportID); + } }, [setCurrentReportID], ); diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx index 3217830054de..01dab5a3231a 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportIDs.tsx @@ -1,6 +1,7 @@ import React, {createContext, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {usePersonalDetails} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import SidebarUtils from '@libs/SidebarUtils'; @@ -10,6 +11,7 @@ import type {Beta, Policy, PolicyMembers, ReportAction, ReportActions, Transacti import type PriorityMode from '@src/types/onyx/PriorityMode'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; +import usePermissions from './usePermissions'; import {useReports} from './useReports'; type OnyxProps = { @@ -31,6 +33,8 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP const chatReports = useReports(); const currentReportIDValue = useCurrentReportID(); const {activeWorkspaceID} = useActiveWorkspace(); + const personalDetails = usePersonalDetails(); + const {canUseViolations} = usePermissions(); const policyMemberAccountIDs = useMemo( () => getPolicyMembersByIdWithoutCurrentUser(props.policyMembers, activeWorkspaceID, getCurrentUserAccountID()), @@ -49,8 +53,25 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, ), - [chatReports, props.betas, props.policies, props.priorityMode, props.allReportActions, props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs], + [ + chatReports, + props.betas, + props.policies, + props.priorityMode, + props.allReportActions, + props.transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, + ], ); // We need to make sure the current report is in the list of reports, but we do not want @@ -70,6 +91,10 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.transactionViolations, activeWorkspaceID, policyMemberAccountIDs, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, ); } return optionListItems; @@ -84,6 +109,10 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.policies, props.priorityMode, props.transactionViolations, + personalDetails, + props.preferredLocale, + canUseViolations, + props.draftComments, ]); return {props.children}; @@ -136,6 +165,13 @@ const OrderedReportIDsContextProvider = withOnyx, currentPolicyID = '', policyMemberAccountIDs: number[] = [], -): string[] { + personalDetails: OnyxEntry, + preferredLocale: DeepValueOf, + canUseViolations: boolean, + draftComments: OnyxCollection, +): OrderedReports[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports ?? {}); @@ -190,7 +200,33 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); + const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => { + const itemFullReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`] ?? null; + const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`] ?? null; + const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report?.reportID}`] ?? ''; + + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); + + const item = getOptionData({ + report: itemFullReport, + reportActions: itemReportActions, + personalDetails, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, + policy: itemPolicy, + parentReportAction: itemParentReportAction, + hasViolations: !!hasViolations, + }); + + return { + reportID: report.reportID, + optionItem: item, + comment: itemComment, + } + }); + return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index e78e656e0b7e..4c05c3d44ae8 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -1,7 +1,7 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -179,5 +179,5 @@ export default withOnyx({ activePolicy: { key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, }, -})(SidebarLinks); +})(memo(SidebarLinks)); export {basePropTypes}; diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 42681fea451b..87006d3debe2 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -46,7 +46,7 @@ const defaultProps = { }, }; -function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, session: {accountID}}) { +function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, session: {accountID}}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index d778648ea998..968148a8becb 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -107,6 +107,8 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } +const stickyHeaderIndices = [0]; + function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: WorkspaceListPageProps) { const reports = useReports(); const theme = useTheme(); @@ -375,7 +377,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: data={workspaces} renderItem={getMenuItem} ListHeaderComponent={listHeaderComponent} - stickyHeaderIndices={[0]} + stickyHeaderIndices={stickyHeaderIndices} /> Date: Mon, 19 Feb 2024 13:35:45 +0700 Subject: [PATCH 0023/1357] fix lint --- .../AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx | 4 ++-- src/components/AutoCompleteSuggestions/index.tsx | 6 +++--- src/components/AutoCompleteSuggestions/types.ts | 2 +- src/libs/SuggestionUtils.ts | 4 ++-- src/pages/home/report/ReportActionItemMessageEdit.tsx | 3 +-- src/styles/utils/index.ts | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 2b7750f732f2..7b4d23d0f3e7 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -39,7 +39,7 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, - shouldBelowParentContainer = false, + shouldBeDisplayedBelowParentContainer = false, }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { @@ -68,7 +68,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBelowParentContainer)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer)); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index bda8e0aa7711..1f5d87b15962 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -4,7 +4,7 @@ import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import {measureHeightOfSuggestioContainer} from '@libs/SuggestionUtils'; +import {measureHeightOfSuggestionsContainer} from '@libs/SuggestionUtils'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; @@ -19,7 +19,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); - const suggestionContainerHeight = measureHeightOfSuggestioContainer(props.suggestions.length, props.isSuggestionPickerLarge); + const suggestionContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, @@ -56,7 +56,7 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} // eslint-disable-next-line react/jsx-props-no-spreading {...props} - shouldBelowParentContainer={shouldBelowContainer} + shouldBeDisplayedBelowParentContainer={shouldBelowContainer} ref={containerRef} /> ); diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index b837901026b2..d9824db1988d 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -35,7 +35,7 @@ type AutoCompleteSuggestionsProps = { measureParentContainer?: (callback: MeasureParentContainerCallback) => void; /** Whether suggestion should be displayed below the parent container or not */ - shouldBelowParentContainer?: boolean; + shouldBeDisplayedBelowParentContainer?: boolean; }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index 00039592ba77..f61989133409 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,7 +20,7 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { +const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; let suggestionHeight = 0; @@ -40,4 +40,4 @@ const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLa return suggestionHeight + borderAndPadding; }; -export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestioContainer}; +export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestionsContainer}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 1ef9406c6327..bdd41a27baa6 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -4,8 +4,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {findNodeHandle, InteractionManager, Keyboard, NativeModules, View} from 'react-native'; -import {Keyboard, View} from 'react-native'; +import {findNodeHandle, Keyboard, NativeModules, View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index ec3f088da537..8ca45907449d 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -793,7 +793,7 @@ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetB /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBelowParentContainer: boolean): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean): ViewStyle { 'worklet'; const borderWidth = 2; @@ -803,7 +803,7 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBelo // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + (shouldBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth)), + top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + borderWidth)), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From a7bcde62060bef47d9675cea3d300fb156fa170f Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Feb 2024 13:37:25 +0700 Subject: [PATCH 0024/1357] update suggestion --- .../actions/{SuggestionsAction.ts => SuggestionsActions.ts} | 0 .../ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx | 2 +- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- src/pages/home/report/ReportActionsView.js | 2 +- src/styles/utils/index.ts | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/libs/actions/{SuggestionsAction.ts => SuggestionsActions.ts} (100%) diff --git a/src/libs/actions/SuggestionsAction.ts b/src/libs/actions/SuggestionsActions.ts similarity index 100% rename from src/libs/actions/SuggestionsAction.ts rename to src/libs/actions/SuggestionsActions.ts diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 02a629c954e2..379928e8f28f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -3,7 +3,7 @@ import React, {useState} from 'react'; import type {TextInput} from 'react-native'; import Composer from '@components/Composer'; import type {ComposerProps} from '@components/Composer/types'; -import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; +import type {SuggestionsRef} from '@libs/actions/SuggestionsActions'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; type ComposerWithSuggestionsEditProps = { diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index bdd41a27baa6..226576e8b916 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -21,7 +21,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; +import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index a76814caf147..cf23fb76d052 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -11,7 +11,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import usePrevious from '@hooks/usePrevious'; -import * as SuggestionsAction from '@libs/actions/SuggestionsAction'; +import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 8ca45907449d..61ef018350df 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -783,7 +783,7 @@ type GetBaseAutoCompleteSuggestionContainerStyleParams = { */ function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetBaseAutoCompleteSuggestionContainerStyleParams): ViewStyle { return { - // ...positioning.pFixed, + ...positioning.pFixed, bottom, left, width, From be5faa2661dced9170ec02ff85c302d1f23eb726 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 19 Feb 2024 13:39:21 +0700 Subject: [PATCH 0025/1357] rename variable --- src/libs/SuggestionUtils.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index f61989133409..414244d77e13 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,24 +20,24 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { +const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionsPickerLarge: boolean): number => { const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; - let suggestionHeight = 0; + let suggestionsHeight = 0; - if (isSuggestionPickerLarge) { + if (isSuggestionsPickerLarge) { if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { // On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available - suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } else { - suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } } else if (numRows > 2) { // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible - suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } else { - suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; + suggestionsHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; } - return suggestionHeight + borderAndPadding; + return suggestionsHeight + borderAndPadding; }; export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestionsContainer}; From eb09849b6f8d18f52ab18e4942e5ed20b19118ae Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 26 Feb 2024 11:53:05 +0700 Subject: [PATCH 0026/1357] move portal of suggestion to the correct place --- src/pages/home/report/ReportActionItemMessageEdit.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 2c9190926d0d..6a182a6016c3 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -401,9 +401,12 @@ function ReportActionItemMessageEdit( return ( <> - + + - Date: Mon, 26 Feb 2024 17:14:29 +0100 Subject: [PATCH 0027/1357] feat: create new rate field --- .../MoneyRequestConfirmationList.js | 12 ++++++ ...oraryForRefactorRequestConfirmationList.js | 20 ++++++++++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/DistanceRequestUtils.ts | 39 ++++++++++++++++--- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index df2781d3ea89..7bb4bb868c99 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -730,6 +730,18 @@ function MoneyRequestConfirmationList(props) { interactive={!props.isReadOnly} /> )} + {props.isDistanceRequest && ( + Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))} + disabled={didConfirm || !isTypeRequest} + interactive={!props.isReadOnly} + /> + )} {shouldShowMerchant && ( {}} + // onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm || !isTypeRequest} + interactive={!isReadOnly} + /> + ), + // TODO: hide when betas ready + shouldShow: isDistanceRequest, + isSupplementary: true, + }, { item: ( Date: Mon, 26 Feb 2024 17:58:22 +0100 Subject: [PATCH 0028/1357] refactor: revert changes to withCurrentReportID --- src/components/withCurrentReportID.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index cc49c44e0e77..a452e7565b4e 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,15 +39,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - const reportID = Navigation.getTopmostReportId(state) ?? ''; - /** - * This is to make sure we don't set the undefined as reportID when - * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. - */ - if (reportID) { - setCurrentReportID(reportID); - } + setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); }, [setCurrentReportID], ); From 9ae8701c7ed4a5c98e69d689e63c4ac19694ebf2 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 26 Feb 2024 18:48:44 +0100 Subject: [PATCH 0029/1357] refactor: remove useReports context & hooks --- src/App.tsx | 2 - src/hooks/useReports.tsx | 44 ------------------- .../AppNavigator/ReportScreenIDSetter.ts | 13 ++++-- src/pages/workspace/WorkspacesListPage.tsx | 10 +++-- 4 files changed, 16 insertions(+), 53 deletions(-) delete mode 100644 src/hooks/useReports.tsx diff --git a/src/App.tsx b/src/App.tsx index 7cbf3478d16e..eb1750a7fe5f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,7 +30,6 @@ import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import {OrderedReportIDsContextProvider} from './hooks/useOrderedReportIDs'; -import {ReportsContextProvider} from './hooks/useReports'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; @@ -77,7 +76,6 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - ReportsContextProvider, OrderedReportIDsContextProvider, PlaybackContextProvider, VolumeContextProvider, diff --git a/src/hooks/useReports.tsx b/src/hooks/useReports.tsx deleted file mode 100644 index c4082149cbf4..000000000000 --- a/src/hooks/useReports.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, {createContext, useContext, useEffect, useMemo, useState} from 'react'; -import Onyx from 'react-native-onyx'; -import type {OnyxCollection} from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; - -type Reports = OnyxCollection; -type ReportsContextValue = Reports; - -type ReportsContextProviderProps = { - children: React.ReactNode; -}; - -const ReportsContext = createContext(null); - -function ReportsContextProvider(props: ReportsContextProviderProps) { - const [reports, setReports] = useState(null); - - useEffect(() => { - // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs - const connID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (val) => { - setReports(val); - }, - }); - return () => { - Onyx.disconnect(connID); - }; - }, []); - - const contextValue = useMemo(() => reports ?? {}, [reports]); - - return {props.children}; -} - -function useReports() { - return useContext(ReportsContext); -} - -ReportsContextProvider.displayName = 'ReportsContextProvider'; - -export {ReportsContextProvider, ReportsContext, useReports}; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 297313e513f6..529f0f3d31a7 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,7 +3,6 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; -import {useReports} from '@hooks/useReports'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -11,6 +10,9 @@ import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/ony import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { + /** Available reports that would be displayed in this navigator */ + reports: OnyxCollection; + /** The policies which the user has access to */ policies: OnyxCollection; @@ -56,10 +58,9 @@ const getLastAccessedReportID = ( }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); - const reports = useReports(); useEffect(() => { // Don't update if there is a reportID in the params already @@ -80,7 +81,7 @@ function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, !canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, - !!route?.params?.openOnAdminRoom, + !!reports?.params?.openOnAdminRoom, reportMetadata, activeWorkspaceID, policyMemberAccountIDs, @@ -101,6 +102,10 @@ function ReportScreenIDSetter({route, policies, policyMembers = {}, navigation, ReportScreenIDSetter.displayName = 'ReportScreenIDSetter'; export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + allowStaleData: true, + }, policies: { key: ONYXKEYS.COLLECTION.POLICY, allowStaleData: true, diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index f646366360eb..a51efd608444 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -20,7 +20,6 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import {useReports} from '@hooks/useReports'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -73,6 +72,9 @@ type WorkspaceListPageOnyxProps = { /** A collection of objects for all policies which key policy member objects by accountIDs */ allPolicyMembers: OnyxCollection; + + /** All reports shared with the user (coming from Onyx) */ + reports: OnyxCollection; }; type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps; @@ -110,8 +112,7 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi const stickyHeaderIndices = [0]; -function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount}: WorkspaceListPageProps) { - const reports = useReports(); +function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -409,5 +410,8 @@ export default withPolicyAndFullscreenLoading( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, })(WorkspacesListPage), ); From 2c19aa3e06b141a40667b337e8b93a629e62fb0d Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 26 Feb 2024 19:20:47 +0100 Subject: [PATCH 0030/1357] feat: create rate selection page --- src/ROUTES.ts | 5 ++ src/SCREENS.ts | 1 + ...oraryForRefactorRequestConfirmationList.js | 23 +++---- .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + .../iou/request/step/IOURequestStepRate.tsx | 65 +++++++++++++++++++ 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepRate.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a8786bda3ffb..c01fdac72aef 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -369,6 +369,11 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}`, backTo), }, + MONEY_REQUEST_STEP_RATE: { + route: ':action/:iouType/rate/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/rate/${transactionID}/${reportID}`, backTo), + }, MONEY_REQUEST_STEP_MERCHANT: { route: ':action/:iouType/merchant/:transactionID/:reportID', getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 520895c89c98..ddaf52a03a59 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -143,6 +143,7 @@ const SCREENS = { STEP_DATE: 'Money_Request_Step_Date', STEP_DESCRIPTION: 'Money_Request_Step_Description', STEP_DISTANCE: 'Money_Request_Step_Distance', + STEP_RATE: 'Money_Request_Step_Rate', STEP_MERCHANT: 'Money_Request_Step_Merchant', STEP_PARTICIPANTS: 'Money_Request_Step_Participants', STEP_SCAN: 'Money_Request_Step_Scan', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 6fb0e8bc0f08..f2fcb3ac96eb 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,10 +1,10 @@ -import {useIsFocused} from '@react-navigation/native'; -import {format} from 'date-fns'; +import { useIsFocused } from '@react-navigation/native'; +import { format } from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { withOnyx } from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -21,9 +21,9 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import playSound, {SOUNDS} from '@libs/Sound'; +import playSound, { SOUNDS } from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {policyPropTypes} from '@pages/workspace/withPolicy'; +import { policyPropTypes } from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,7 +44,8 @@ import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes } from './withCurrentUserPersonalDetails'; + const propTypes = { /** Callback to inform parent modal of success */ @@ -701,9 +702,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ description={translate('common.rate')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - // TODO: Add the onPress function - onPress={() => {}} - // onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + onPress={() => { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} disabled={didConfirm || !isTypeRequest} interactive={!isReadOnly} /> diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2be262aa5f0f..941900c3bf2c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -89,6 +89,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepDate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: () => require('../../../pages/iou/request/step/IOURequestStepDescription').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: () => require('../../../pages/iou/request/step/IOURequestStepDistance').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.STEP_RATE]: () => require('../../../pages/iou/request/step/IOURequestStepRate').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: () => require('../../../pages/iou/request/step/IOURequestStepMerchant').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: () => require('../../../pages/iou/request/step/IOURequestStepParticipants').default as React.ComponentType, [SCREENS.MONEY_REQUEST.STEP_SCAN]: () => require('../../../pages/iou/request/step/IOURequestStepScan').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 48d649cc4dd9..9163c0b9bfe5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -416,6 +416,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_DATE]: ROUTES.MONEY_REQUEST_STEP_DATE.route, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.route, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE.route, + [SCREENS.MONEY_REQUEST.STEP_RATE]: ROUTES.MONEY_REQUEST_STEP_RATE.route, [SCREENS.MONEY_REQUEST.HOLD]: ROUTES.MONEY_REQUEST_HOLD_REASON.route, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: ROUTES.MONEY_REQUEST_STEP_MERCHANT.route, [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.route, diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx new file mode 100644 index 000000000000..347e627624e9 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; +import type {Route} from '@src/ROUTES'; + +type Props = { + // eslint-disable-next-line react/no-unused-prop-types + lastSelectedDistanceRate: string; + /** The route object passed to this screen */ + route: { + /** The params passed to this screen */ + params: { + /** The route to go back to */ + backTo: Route; + }; + }; +}; + +const mockRates = [ + {text: 'Default Rate', alternateText: '0.656/mile', keyForList: 'DefaultRate'}, + {text: 'Custom Rate', alternateText: '0.700/mile', keyForList: 'CustomRate'}, +]; + +function IOURequestStepRate({ + route: { + params: {backTo}, + }, +}: Props) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + Navigation.goBack(backTo)} + shouldShowWrapper={Boolean(backTo)} + testID="rate" + > + {translate('themePage.chooseThemeBelowOrSync')} + + {}} + initiallyFocusedOptionKey="DefaultRate" + /> + + ); +} + +IOURequestStepRate.displayName = 'IOURequestStepRate'; + +// export default withOnyx({ +// lastSelectedDistanceRate: { +// key: 'ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATE', +// }, +// })(IOURequestStepRate); + +export default IOURequestStepRate; From b613e974a2f8e8b7c07e893e64bc6fdf377d08d1 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 15:54:37 +0100 Subject: [PATCH 0031/1357] refactor: use reports from onyx in useOrderedReportIDs --- src/hooks/useOrderedReportIDs.tsx | 69 +++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx index 01dab5a3231a..6b3ebd8d9e28 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportIDs.tsx @@ -4,23 +4,26 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {usePersonalDetails} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, PolicyMembers, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Locale, Policy, PolicyMembers, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; import usePermissions from './usePermissions'; -import {useReports} from './useReports'; type OnyxProps = { + chatReports: OnyxCollection; betas: OnyxEntry; policies: OnyxCollection; allReportActions: OnyxCollection; transactionViolations: OnyxCollection; policyMembers: OnyxCollection; priorityMode: OnyxEntry; + preferredLocale: OnyxEntry; + draftComments: OnyxCollection; }; type WithOrderedReportIDsContextProviderProps = OnyxProps & { @@ -30,7 +33,6 @@ type WithOrderedReportIDsContextProviderProps = OnyxProps & { const OrderedReportIDsContext = createContext({}); function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextProviderProps) { - const chatReports = useReports(); const currentReportIDValue = useCurrentReportID(); const {activeWorkspaceID} = useActiveWorkspace(); const personalDetails = usePersonalDetails(); @@ -45,7 +47,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP () => SidebarUtils.getOrderedReportIDs( null, - chatReports, + props.chatReports, props.betas ?? [], props.policies, props.priorityMode, @@ -59,7 +61,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP props.draftComments, ), [ - chatReports, + props.chatReports, props.betas, props.policies, props.priorityMode, @@ -83,7 +85,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP if (currentReportIDValue?.currentReportID && !optionListItems.includes(currentReportIDValue.currentReportID)) { return SidebarUtils.getOrderedReportIDs( currentReportIDValue.currentReportID, - chatReports, + props.chatReports, props.betas ?? [], props.policies, props.priorityMode, @@ -100,7 +102,7 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP return optionListItems; }, [ activeWorkspaceID, - chatReports, + props.chatReports, currentReportIDValue?.currentReportID, optionListItems, policyMemberAccountIDs, @@ -118,6 +120,52 @@ function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextP return {props.children}; } +/** + * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering + * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. + * @param {Object} [report] + * @returns {Object|undefined} + */ +const chatReportSelector = (report) => + report && { + reportID: report.reportID, + participantAccountIDs: report.participantAccountIDs, + hasDraft: report.hasDraft, + isPinned: report.isPinned, + isHidden: report.isHidden, + notificationPreference: report.notificationPreference, + errorFields: { + addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, + }, + lastMessageText: report.lastMessageText, + lastVisibleActionCreated: report.lastVisibleActionCreated, + iouReportID: report.iouReportID, + total: report.total, + nonReimbursableTotal: report.nonReimbursableTotal, + hasOutstandingChildRequest: report.hasOutstandingChildRequest, + isWaitingOnBankAccount: report.isWaitingOnBankAccount, + statusNum: report.statusNum, + stateNum: report.stateNum, + chatType: report.chatType, + type: report.type, + policyID: report.policyID, + visibility: report.visibility, + lastReadTime: report.lastReadTime, + // Needed for name sorting: + reportName: report.reportName, + policyName: report.policyName, + oldPolicyName: report.oldPolicyName, + // Other less obvious properites considered for sorting: + ownerAccountID: report.ownerAccountID, + currency: report.currency, + managerID: report.managerID, + // Other important less obivous properties for filtering: + parentReportActionID: report.parentReportActionID, + parentReportID: report.parentReportID, + isDeletedParentAction: report.isDeletedParentAction, + isUnreadWithMention: ReportUtils.isUnreadWithMention(report), + }; + const reportActionsSelector = (reportActions: OnyxEntry) => { if (!reportActions) { return []; @@ -140,6 +188,11 @@ const reportActionsSelector = (reportActions: OnyxEntry) => { }; const OrderedReportIDsContextProvider = withOnyx({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, + initialValue: {}, + }, priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, initialValue: CONST.PRIORITY_MODE.DEFAULT, @@ -151,7 +204,7 @@ const OrderedReportIDsContextProvider = withOnyx reportActionsSelector(actions), + selector: reportActionsSelector, initialValue: {}, }, policies: { From 7f15fbc34bdc512c6c72fe8b15232dc1ba53e756 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 17:32:07 +0100 Subject: [PATCH 0032/1357] refactor: move creating orderedReport objects from getOrderedReportIds to the context itself --- .../LHNOptionsList/LHNOptionsList.tsx | 48 +++--- src/hooks/useOrderedReportIDs.tsx | 140 ++++++++---------- src/libs/SidebarUtils.ts | 46 +----- src/pages/home/sidebar/SidebarLinksData.js | 18 ++- 4 files changed, 100 insertions(+), 152 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c70e355c46c5..8b18630a9646 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,13 +1,13 @@ +import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {useCallback, memo} from 'react'; -import {FlatList, StyleSheet, View} from 'react-native'; +import React, {memo, useCallback} from 'react'; +import {StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import {OrderedReports} from '@libs/SidebarUtils'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; import OptionRowLHNData from './OptionRowLHNData'; import type {LHNOptionsListProps, RenderItemProps} from './types'; -import { OrderedReports } from '@libs/SidebarUtils'; -import CONST from '@src/CONST'; -import variables from '@styles/variables'; -import { FlashList } from '@shopify/flash-list'; const keyExtractor = (item: OrderedReports) => `report_${item?.reportID}`; @@ -39,29 +39,23 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item}: RenderItemProps): ReactElement => { - - return ( - - ); - }, - [ - currentReportID, - onSelectRow, - onLayoutItem, - ], + ({item}: RenderItemProps): ReactElement => ( + + ), + [shouldDisableFocusOptions, currentReportID, onSelectRow, onLayoutItem, optionMode], ); return ( - diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportIDs.tsx index 6b3ebd8d9e28..8f071f036fc7 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportIDs.tsx @@ -1,4 +1,4 @@ -import React, {createContext, useContext, useMemo} from 'react'; +import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -32,101 +32,90 @@ type WithOrderedReportIDsContextProviderProps = OnyxProps & { const OrderedReportIDsContext = createContext({}); -function WithOrderedReportIDsContextProvider(props: WithOrderedReportIDsContextProviderProps) { +function WithOrderedReportIDsContextProvider({ + children, + chatReports, + betas, + policies, + allReportActions, + transactionViolations, + policyMembers, + priorityMode, + preferredLocale, + draftComments, +}: WithOrderedReportIDsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); - const {activeWorkspaceID} = useActiveWorkspace(); const personalDetails = usePersonalDetails(); const {canUseViolations} = usePermissions(); + const {activeWorkspaceID} = useActiveWorkspace(); - const policyMemberAccountIDs = useMemo( - () => getPolicyMembersByIdWithoutCurrentUser(props.policyMembers, activeWorkspaceID, getCurrentUserAccountID()), - [activeWorkspaceID, props.policyMembers], - ); + const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); - const optionListItems = useMemo( - () => + const getOrderedReportIDs = useCallback( + (currentReportID?: string) => SidebarUtils.getOrderedReportIDs( - null, - props.chatReports, - props.betas ?? [], - props.policies, - props.priorityMode, - props.allReportActions, - props.transactionViolations, + currentReportID ?? null, + chatReports, + betas ?? [], + policies, + priorityMode, + allReportActions, + transactionViolations, activeWorkspaceID, policyMemberAccountIDs, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, ), - [ - props.chatReports, - props.betas, - props.policies, - props.priorityMode, - props.allReportActions, - props.transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, - ], + [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs], ); + const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); + // We need to make sure the current report is in the list of reports, but we do not want // to have to re-generate the list every time the currentReportID changes. To do that // we first generate the list as if there was no current report, then here we check if // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. - const optionListItemsWithCurrentReport = useMemo(() => { - if (currentReportIDValue?.currentReportID && !optionListItems.includes(currentReportIDValue.currentReportID)) { - return SidebarUtils.getOrderedReportIDs( - currentReportIDValue.currentReportID, - props.chatReports, - props.betas ?? [], - props.policies, - props.priorityMode, - props.allReportActions, - props.transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, - ); + const orderedReportIDsWithCurrentReport = useMemo(() => { + if (currentReportIDValue?.currentReportID && !orderedReportIDs.includes(currentReportIDValue.currentReportID)) { + return getOrderedReportIDs(currentReportIDValue.currentReportID); } - return optionListItems; - }, [ - activeWorkspaceID, - props.chatReports, - currentReportIDValue?.currentReportID, - optionListItems, - policyMemberAccountIDs, - props.allReportActions, - props.betas, - props.policies, - props.priorityMode, - props.transactionViolations, - personalDetails, - props.preferredLocale, - canUseViolations, - props.draftComments, - ]); - - return {props.children}; + return orderedReportIDs; + }, [getOrderedReportIDs, currentReportIDValue?.currentReportID, orderedReportIDs]); + + const orderedReportListItems = useMemo( + () => + orderedReportIDsWithCurrentReport.map((reportID) => { + const itemFullReport = chatReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); + + const item = SidebarUtils.getOptionData({ + report: itemFullReport, + reportActions: itemReportActions, + personalDetails, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, + policy: itemPolicy, + parentReportAction: itemParentReportAction, + hasViolations: !!hasViolations, + }); + + return {reportID, optionItem: item, comment: itemComment}; + }), + [orderedReportIDsWithCurrentReport, canUseViolations, personalDetails, draftComments, preferredLocale, chatReports, allReportActions, policies, transactionViolations], + ); + + return {children}; } /** * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - * @param {Object} [report] - * @returns {Object|undefined} */ -const chatReportSelector = (report) => +const chatReportSelector = (report: OnyxEntry) => report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, @@ -134,9 +123,7 @@ const chatReportSelector = (report) => isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, - }, + errorFields: {addWorkspaceRoom: report.errorFields?.addWorkspaceRoom}, lastMessageText: report.lastMessageText, lastVisibleActionCreated: report.lastVisibleActionCreated, iouReportID: report.iouReportID, @@ -188,6 +175,7 @@ const reportActionsSelector = (reportActions: OnyxEntry) => { }; const OrderedReportIDsContextProvider = withOnyx({ + // @ts-expect-error Need some help in determining the correct type for this selector chatReports: { key: ONYXKEYS.COLLECTION.REPORT, selector: chatReportSelector, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index f99384487650..f36becf40715 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -59,30 +59,20 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { return 0; } -export type OrderedReports = { - reportID: string; - optionItem: ReportUtils.OptionData | undefined; - comment: string; -}; - /** * @returns An array of reportIDs sorted in the proper order */ function getOrderedReportIDs( currentReportId: string | null, allReports: OnyxCollection, - betas: Beta[], + betas: OnyxEntry, policies: OnyxCollection, priorityMode: OnyxEntry, allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], - personalDetails: OnyxEntry, - preferredLocale: DeepValueOf, - canUseViolations: boolean, - draftComments: OnyxCollection, -): OrderedReports[] { +): string[] { const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD; const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports ?? {}); @@ -93,12 +83,12 @@ function getOrderedReportIDs( const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); const doesReportHaveViolations = - betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); + !!betas && betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', isInGSDMode, - betas, + betas: betas ?? [], policies, excludeEmptyChats: true, doesReportHaveViolations, @@ -178,33 +168,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => { - const itemFullReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`] ?? null; - const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`] ?? null; - const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; - const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report?.reportID}`] ?? ''; - - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); - - const item = getOptionData({ - report: itemFullReport, - reportActions: itemReportActions, - personalDetails, - preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, - policy: itemPolicy, - parentReportAction: itemParentReportAction, - hasViolations: !!hasViolations, - }); - - return { - reportID: report.reportID, - optionItem: item, - comment: itemComment, - }; - }); - + const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID); return LHNReports; } diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 6d63a8a44fe1..4966a2414e97 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -51,22 +51,24 @@ function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onL // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); - const reportIDsRef = useRef(null); + const orderedReportListItemsRef = useRef(null); const isLoading = isLoadingApp; - const reportIDs = useOrderedReportIDs(); + const orderedReportListItems = useOrderedReportIDs(); + const optionListItems = useMemo(() => { - if (deepEqual(reportIDsRef.current, reportIDs)) { - return reportIDsRef.current; + // this can be very heavy because we are no longer comapring the IDS but the whole objects for the list + if (deepEqual(orderedReportListItemsRef.current, orderedReportListItems)) { + return orderedReportListItemsRef.current; } // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !reportIDsRef.current || network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { - reportIDsRef.current = reportIDs; + if (!isLoading || !orderedReportListItemsRef.current || network.isOffline || (orderedReportListItemsRef.current && prevPriorityMode !== priorityMode)) { + orderedReportListItemsRef.current = orderedReportListItems; } - return reportIDsRef.current || []; - }, [reportIDs, isLoading, network.isOffline, prevPriorityMode, priorityMode]); + return orderedReportListItemsRef.current || []; + }, [orderedReportListItems, isLoading, network.isOffline, prevPriorityMode, priorityMode]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; From 7fa44fc71ce4eb20d70b4da92094b9a5b6d50394 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 17:34:55 +0100 Subject: [PATCH 0033/1357] Revert "refactor: revert changes to withCurrentReportID" This reverts commit dc88950ac4dbc552e4ca8b705ae4faff05dd70da. --- src/components/withCurrentReportID.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index a452e7565b4e..cc49c44e0e77 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,7 +39,15 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); + const reportID = Navigation.getTopmostReportId(state) ?? ''; + /** + * This is to make sure we don't set the undefined as reportID when + * switching between chat list and settings->workspaces tab. + * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. + */ + if (reportID) { + setCurrentReportID(reportID); + } }, [setCurrentReportID], ); From ae12b008886272f08668a089342bc9bacbcfb831 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 17:47:45 +0100 Subject: [PATCH 0034/1357] refactor: remove comment in SidebarLinksData --- src/pages/home/sidebar/SidebarLinksData.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 4966a2414e97..5cedc9bbb7fd 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -56,7 +56,6 @@ function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onL const orderedReportListItems = useOrderedReportIDs(); const optionListItems = useMemo(() => { - // this can be very heavy because we are no longer comapring the IDS but the whole objects for the list if (deepEqual(orderedReportListItemsRef.current, orderedReportListItems)) { return orderedReportListItemsRef.current; } From c87f579bda3c94452f6b093edfbc159996a7eb30 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 18:00:43 +0100 Subject: [PATCH 0035/1357] refactor: rename useOrderedReportIDs to useOrderedReportListItems --- src/App.tsx | 4 ++-- src/components/withCurrentReportID.tsx | 2 +- ...tIDs.tsx => useOrderedReportListItems.tsx} | 20 +++++++++---------- src/pages/home/sidebar/SidebarLinksData.js | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/hooks/{useOrderedReportIDs.tsx => useOrderedReportListItems.tsx} (92%) diff --git a/src/App.tsx b/src/App.tsx index eb1750a7fe5f..5670859e8908 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; -import {OrderedReportIDsContextProvider} from './hooks/useOrderedReportIDs'; +import {OrderedReportListItemsContextProvider} from './hooks/useOrderedReportListItems'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; @@ -76,7 +76,7 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - OrderedReportIDsContextProvider, + OrderedReportListItemsContextProvider, PlaybackContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index cc49c44e0e77..d5c5a6896a9c 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -43,7 +43,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro /** * This is to make sure we don't set the undefined as reportID when * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useOrderedReportIDs`. + * and doing so avoid unnecessary re-render of `useOrderedReportListItems`. */ if (reportID) { setCurrentReportID(reportID); diff --git a/src/hooks/useOrderedReportIDs.tsx b/src/hooks/useOrderedReportListItems.tsx similarity index 92% rename from src/hooks/useOrderedReportIDs.tsx rename to src/hooks/useOrderedReportListItems.tsx index 8f071f036fc7..643fcf053012 100644 --- a/src/hooks/useOrderedReportIDs.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -26,13 +26,13 @@ type OnyxProps = { draftComments: OnyxCollection; }; -type WithOrderedReportIDsContextProviderProps = OnyxProps & { +type WithOrderedReportListItemsContextProviderProps = OnyxProps & { children: React.ReactNode; }; -const OrderedReportIDsContext = createContext({}); +const OrderedReportListItemsContext = createContext({}); -function WithOrderedReportIDsContextProvider({ +function WithOrderedReportListItemsContextProvider({ children, chatReports, betas, @@ -43,7 +43,7 @@ function WithOrderedReportIDsContextProvider({ priorityMode, preferredLocale, draftComments, -}: WithOrderedReportIDsContextProviderProps) { +}: WithOrderedReportListItemsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); const personalDetails = usePersonalDetails(); const {canUseViolations} = usePermissions(); @@ -108,7 +108,7 @@ function WithOrderedReportIDsContextProvider({ [orderedReportIDsWithCurrentReport, canUseViolations, personalDetails, draftComments, preferredLocale, chatReports, allReportActions, policies, transactionViolations], ); - return {children}; + return {children}; } /** @@ -174,7 +174,7 @@ const reportActionsSelector = (reportActions: OnyxEntry) => { }); }; -const OrderedReportIDsContextProvider = withOnyx({ +const OrderedReportListItemsContextProvider = withOnyx({ // @ts-expect-error Need some help in determining the correct type for this selector chatReports: { key: ONYXKEYS.COLLECTION.REPORT, @@ -213,10 +213,10 @@ const OrderedReportIDsContextProvider = withOnyx { if (deepEqual(orderedReportListItemsRef.current, orderedReportListItems)) { From df1069c1a1dc695f8faa0dcfd748541bbe1ea8bb Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 18:05:35 +0100 Subject: [PATCH 0036/1357] perf: use memo for extraData in LHNOptionList --- src/components/LHNOptionsList/LHNOptionsList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 8b18630a9646..8301bc034a74 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,6 +1,6 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; +import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import {OrderedReports} from '@libs/SidebarUtils'; @@ -53,6 +53,8 @@ function LHNOptionsList({ [shouldDisableFocusOptions, currentReportID, onSelectRow, onLayoutItem, optionMode], ); + const extraData = useMemo(() => [currentReportID], [currentReportID]); + return ( From 1d294bcd188dd768d7c917c48ba19f54bf5a0275 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 19:14:59 +0100 Subject: [PATCH 0037/1357] fix: typescript fixes --- .../LHNOptionsList/LHNOptionsList.tsx | 5 +- .../LHNOptionsList/OptionRowLHNData.tsx | 14 +--- src/components/LHNOptionsList/types.ts | 74 ++++--------------- src/hooks/useOrderedReportListItems.tsx | 3 +- src/libs/SidebarUtils.ts | 3 +- src/pages/home/sidebar/SidebarLinks.js | 2 +- src/pages/home/sidebar/SidebarLinksData.js | 4 + src/types/onyx/index.ts | 2 + 8 files changed, 27 insertions(+), 80 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 8301bc034a74..1884997a58fe 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -3,13 +3,12 @@ import type {ReactElement} from 'react'; import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import {OrderedReports} from '@libs/SidebarUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListProps, OptionListItem, RenderItemProps} from './types'; -const keyExtractor = (item: OrderedReports) => `report_${item?.reportID}`; +const keyExtractor = (item: OptionListItem) => `report_${item.reportID}`; function LHNOptionsList({ style, diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 622d7b9d9123..bc1a06ce5f67 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,10 +1,5 @@ -import {deepEqual} from 'fast-equals'; -import React, {useEffect, useMemo, useRef} from 'react'; -import * as ReportUtils from '@libs/ReportUtils'; -import SidebarUtils from '@libs/SidebarUtils'; +import React, {useEffect} from 'react'; import * as Report from '@userActions/Report'; -import CONST from '@src/CONST'; -import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; import type {OptionRowLHNDataProps} from './types'; @@ -14,12 +9,7 @@ import type {OptionRowLHNDataProps} from './types'; * The OptionRowLHN component is memoized, so it will only * re-render if the data really changed. */ -function OptionRowLHNData({ - isFocused = false, - comment, - optionItem, - ...propsToForward -}: OptionRowLHNDataProps) { +function OptionRowLHNData({isFocused = false, comment, optionItem, ...propsToForward}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; useEffect(() => { diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 4c79536571bf..3f72748ab8a8 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,38 +1,22 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; -import { OrderedReports } from '@libs/SidebarUtils'; type OptionMode = ValueOf; -type LHNOptionsListOnyxProps = { - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxCollection; - - /** Array of report actions for this report */ - reportActions: OnyxCollection; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry; - - /** List of users' personal details */ - personalDetails: OnyxEntry; - - /** The transaction from the parent report action */ - transactions: OnyxCollection; +type OptionListItem = { + /** The reportID of the report */ + reportID: string; - /** List of draft comments */ - draftComments: OnyxCollection; + /** The item that should be rendered */ + optionItem: OptionData | undefined; - /** The list of transaction violations */ - transactionViolations: OnyxCollection; + /** Comment added to report */ + comment: string; }; type CustomLHNOptionsListProps = { @@ -43,7 +27,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: string[]; + data: OptionListItem[]; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; @@ -58,51 +42,21 @@ type CustomLHNOptionsListProps = { onFirstItemRendered: () => void; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; type OptionRowLHNDataProps = { /** Whether row should be focused */ isFocused?: boolean; - /** List of users' personal details */ - personalDetails?: PersonalDetailsList; - - /** The preferred language for the app */ - preferredLocale?: OnyxEntry; - - /** The full data of the report */ - fullReport: OnyxEntry; - - /** The policy which the user has access to and which the report could be tied to */ - policy?: OnyxEntry; - - /** The action from the parent report */ - parentReportAction?: OnyxEntry; - - /** The transaction from the parent report action */ - transaction: OnyxEntry; - - /** The transaction linked to the report's last action */ - lastReportActionTransaction?: OnyxEntry; - /** Comment added to report */ comment: string; - /** The receipt transaction from the parent report action */ - receiptTransactions: OnyxCollection; + /** The item that should be rendered */ + optionItem: OptionData | undefined; /** The reportID of the report */ reportID: string; - /** Array of report actions for this report */ - reportActions: OnyxEntry; - - /** List of transaction violation */ - transactionViolations: OnyxCollection; - - /** Whether the user can use violations */ - canUseViolations: boolean | undefined; - /** Toggle between compact and default view */ viewMode?: OptionMode; @@ -130,11 +84,11 @@ type OptionRowLHNProps = { style?: StyleProp; /** The item that should be rendered */ - optionItem?: OptionData; + optionItem: OptionData | undefined; onLayout?: (event: LayoutChangeEvent) => void; }; -type RenderItemProps = {item: OrderedReports}; +type RenderItemProps = {item: OptionListItem}; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, OptionListItem, RenderItemProps}; diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index 643fcf053012..970f306c3a80 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -8,8 +8,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Locale, Policy, PolicyMembers, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; -import type PriorityMode from '@src/types/onyx/PriorityMode'; +import type {Beta, Locale, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; import usePermissions from './usePermissions'; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 777a61c70733..2e929639270e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -4,12 +4,11 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, ReportActions, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; import type Policy from '@src/types/onyx/Policy'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; -import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import * as CollectionUtils from './CollectionUtils'; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 8cda70d2486a..4c9803e257bc 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -33,7 +33,7 @@ const basePropTypes = { const propTypes = { ...basePropTypes, - optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, + optionListItems: PropTypes.arrayOf(PropTypes.object).isRequired, isLoading: PropTypes.bool.isRequired, diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 91de05450aae..088899425a0b 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -31,12 +31,16 @@ const propTypes = { network: networkPropTypes.isRequired, + // eslint-disable-next-line react/forbid-prop-types + policyMembers: PropTypes.object, + ...withCurrentUserPersonalDetailsPropTypes, }; const defaultProps = { isLoadingApp: true, priorityMode: CONST.PRIORITY_MODE.DEFAULT, + policyMembers: {}, ...withCurrentUserPersonalDetailsDefaultProps, }; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 6846fc302639..f6147a27a49b 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -37,6 +37,7 @@ import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type {PolicyReportField, PolicyReportFields} from './PolicyReportField'; import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; +import type PriorityMode from './PriorityMode'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -111,6 +112,7 @@ export type { PolicyTag, PolicyTags, PolicyTagList, + PriorityMode, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From 7dfba4379e6bc48ef256c6363743ea13ad65b614 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 28 Feb 2024 20:08:53 +0100 Subject: [PATCH 0038/1357] fix: update reportActionsSelector to match the one from SidebarLinksData --- src/hooks/useOrderedReportListItems.tsx | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index 970f306c3a80..fa2d18684464 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -1,3 +1,5 @@ +import lodashGet from 'lodash/get'; +import lodashMap from 'lodash/map'; import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -152,29 +154,27 @@ const chatReportSelector = (report: OnyxEntry) => isUnreadWithMention: ReportUtils.isUnreadWithMention(report), }; -const reportActionsSelector = (reportActions: OnyxEntry) => { - if (!reportActions) { - return []; - } +const reportActionsSelector = (reportActions: OnyxEntry) => + reportActions && + lodashMap(reportActions, (reportAction) => { + const {reportActionID, parentReportActionID, actionName, errors = [], originalMessage} = reportAction; + const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); - return Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, originalMessage} = reportAction ?? {}; - const decision = reportAction?.message?.[0]?.moderationDecision?.decision; return { reportActionID, + parentReportActionID, actionName, - originalMessage, + errors, message: [ { moderationDecision: {decision}, }, ], + originalMessage, }; }); -}; const OrderedReportListItemsContextProvider = withOnyx({ - // @ts-expect-error Need some help in determining the correct type for this selector chatReports: { key: ONYXKEYS.COLLECTION.REPORT, selector: chatReportSelector, @@ -190,7 +190,6 @@ const OrderedReportListItemsContextProvider = withOnyx Date: Thu, 29 Feb 2024 10:07:54 +0100 Subject: [PATCH 0039/1357] feat: add translation for the rate page --- src/languages/en.ts | 1 + src/languages/es.ts | 2 ++ src/pages/iou/request/step/IOURequestStepRate.tsx | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 920705883b02..d904a52c1b7a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -696,6 +696,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', + chooseARate: 'Choose a rate to use below', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1b858f707851..842f357639c8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -691,6 +691,8 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', + // TODO: check if this is the correct translation + chooseARate: 'Elige una tarifa para utilizar a continuación', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 347e627624e9..d82238f2c7ea 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -23,8 +23,8 @@ type Props = { }; const mockRates = [ - {text: 'Default Rate', alternateText: '0.656/mile', keyForList: 'DefaultRate'}, - {text: 'Custom Rate', alternateText: '0.700/mile', keyForList: 'CustomRate'}, + {text: 'Default Rate', alternateText: '$0.656 / mile', keyForList: 'DefaultRate'}, + {text: 'Custom Rate', alternateText: '$0.700 / mile', keyForList: 'CustomRate'}, ]; function IOURequestStepRate({ @@ -42,7 +42,7 @@ function IOURequestStepRate({ shouldShowWrapper={Boolean(backTo)} testID="rate" > - {translate('themePage.chooseThemeBelowOrSync')} + {translate('iou.chooseARate')} Date: Thu, 29 Feb 2024 18:01:07 +0500 Subject: [PATCH 0040/1357] fix: typescript issues --- src/hooks/useOrderedReportListItems.tsx | 70 +------------------------ src/libs/SidebarUtils.ts | 4 +- 2 files changed, 4 insertions(+), 70 deletions(-) diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index fa2d18684464..d368abd897af 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -1,9 +1,7 @@ -import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {usePersonalDetails} from '@components/OnyxProvider'; +import {usePersonalDetails, useReport} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -19,7 +17,7 @@ type OnyxProps = { chatReports: OnyxCollection; betas: OnyxEntry; policies: OnyxCollection; - allReportActions: OnyxCollection; + allReportActions: OnyxCollection; transactionViolations: OnyxCollection; policyMembers: OnyxCollection; priorityMode: OnyxEntry; @@ -112,72 +110,9 @@ function WithOrderedReportListItemsContextProvider({ return {children}; } -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - */ -const chatReportSelector = (report: OnyxEntry) => - report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: {addWorkspaceRoom: report.errorFields?.addWorkspaceRoom}, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }; - -const reportActionsSelector = (reportActions: OnyxEntry) => - reportActions && - lodashMap(reportActions, (reportAction) => { - const {reportActionID, parentReportActionID, actionName, errors = [], originalMessage} = reportAction; - const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision'); - - return { - reportActionID, - parentReportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ], - originalMessage, - }; - }); - const OrderedReportListItemsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, - selector: chatReportSelector, initialValue: {}, }, priorityMode: { @@ -190,7 +125,6 @@ const OrderedReportListItemsContextProvider = withOnyx, policies: OnyxCollection, priorityMode: OnyxEntry, - allReportActions: OnyxCollection, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], @@ -80,7 +80,7 @@ function getOrderedReportIDs( let reportsToDisplay = allReportsDictValues.filter((report) => { const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`; const parentReportActions = allReportActions?.[parentReportActionsKey]; - const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID); + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '']; const doesReportHaveViolations = !!betas && betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); return ReportUtils.shouldReportBeInOptionList({ From 6739b587e93144f2dfa7a2250ac15e3b11488289 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 29 Feb 2024 18:01:30 +0500 Subject: [PATCH 0041/1357] test: fix reassure failing test --- tests/perf-test/SidebarUtils.perf-test.ts | 26 ++++++--------------- tests/utils/LHNTestUtils.tsx | 3 ++- tests/utils/collections/createCollection.ts | 21 +++++++++++++++++ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 3aa65331b9c2..6fb63878a832 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -8,7 +8,7 @@ import type {PersonalDetails, TransactionViolation} from '@src/types/onyx'; import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; -import createCollection from '../utils/collections/createCollection'; +import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction from '../utils/collections/reportActions'; @@ -27,6 +27,12 @@ const reportActions = createCollection( (index) => createRandomReportAction(index), ); +const allReportActions = createNestedCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (item) => `${item.reportActionID}`, + (index) => createRandomReportAction(index), +); + const personalDetails = createCollection( (item) => item.accountID, (index) => createPersonalDetails(index), @@ -82,24 +88,6 @@ describe('SidebarUtils', () => { (index) => createRandomPolicy(index), ); - const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - key, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, - }, - }, - ], - }, - ], - ]), - ) as unknown as OnyxCollection; - await waitForBatchedUpdates(); await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions, transactionViolations)); }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 80f28002f975..a10dbbdc17f8 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -10,6 +10,7 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxProvider from '@components/OnyxProvider'; import {CurrentReportIDContextProvider} from '@components/withCurrentReportID'; import {EnvironmentProvider} from '@components/withEnvironment'; +import {OrderedReportListItemsContextProvider} from '@hooks/useOrderedReportListItems'; import DateUtils from '@libs/DateUtils'; import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; @@ -280,7 +281,7 @@ function getFakeAdvancedReportAction(actionName: ActionName = 'IOU', actor = 'em function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( - + {}} diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 848ef8f81f47..e62d9769bc53 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,3 +9,24 @@ export default function createCollection(createKey: (item: T, index: number) return map; } + +export function createNestedCollection( + createParentKey: (item: T, index: number) => string | number, + createKey: (item: T, index: number) => string | number, + createItem: (index: number) => T, + length = 500, +): Record> { + const map: Record> = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + const itemParentKey = createParentKey(item, i); + map[itemParentKey] = { + ...map[itemParentKey], + [itemKey]: item, + }; + } + + return map; +} From 92fae0c08c3c3ca517fb57313f95e4fd2e77abf0 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 29 Feb 2024 18:02:17 +0500 Subject: [PATCH 0042/1357] revert: move option item data calculation to the renderItem component --- .../LHNOptionsList/LHNOptionsList.tsx | 117 +++++++++++++++--- .../LHNOptionsList/OptionRowLHNData.tsx | 63 +++++++++- src/components/LHNOptionsList/types.ts | 74 +++++++++-- src/hooks/useOrderedReportListItems.tsx | 47 +------ 4 files changed, 226 insertions(+), 75 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 1884997a58fe..5569e53942aa 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -2,13 +2,18 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {memo, useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import withCurrentReportID from '@components/withCurrentReportID'; +import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListProps, OptionListItem, RenderItemProps} from './types'; +import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; -const keyExtractor = (item: OptionListItem) => `report_${item.reportID}`; +const keyExtractor = (item: string) => `report_${item}`; function LHNOptionsList({ style, @@ -18,9 +23,18 @@ function LHNOptionsList({ shouldDisableFocusOptions = false, currentReportID = '', optionMode, + reports = {}, + reportActions = {}, + policy = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + personalDetails = {}, + transactions = {}, + draftComments = {}, + transactionViolations = {}, onFirstItemRendered = () => {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + const {canUseViolations} = usePermissions(); // When the first item renders we want to call the onFirstItemRendered callback. // At this point in time we know that the list is actually displaying items. @@ -38,18 +52,64 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item}: RenderItemProps): ReactElement => ( - - ), - [shouldDisableFocusOptions, currentReportID, onSelectRow, onLayoutItem, optionMode], + ({item: reportID}: RenderItemProps): ReactElement => { + const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; + const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); + const lastReportAction = sortedReportActions[0]; + + // Get the transaction for the last report action + let lastReportActionTransactionID = ''; + + if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? ''; + } + const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`] ?? {}; + + return ( + + ); + }, + [ + currentReportID, + draftComments, + onSelectRow, + optionMode, + personalDetails, + policy, + preferredLocale, + reportActions, + reports, + shouldDisableFocusOptions, + transactions, + transactionViolations, + canUseViolations, + onLayoutItem, + ], ); const extraData = useMemo(() => [currentReportID], [currentReportID]); @@ -74,6 +134,33 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default memo(LHNOptionsList); +export default withCurrentReportID( + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policy: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, + draftComments: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, + })(memo(LHNOptionsList)), +); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index bc1a06ce5f67..a18d5a8ec1ec 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,5 +1,10 @@ -import React, {useEffect} from 'react'; +import {deepEqual} from 'fast-equals'; +import React, {useEffect, useMemo, useRef} from 'react'; +import * as ReportUtils from '@libs/ReportUtils'; +import SidebarUtils from '@libs/SidebarUtils'; import * as Report from '@userActions/Report'; +import CONST from '@src/CONST'; +import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; import type {OptionRowLHNDataProps} from './types'; @@ -9,9 +14,63 @@ import type {OptionRowLHNDataProps} from './types'; * The OptionRowLHN component is memoized, so it will only * re-render if the data really changed. */ -function OptionRowLHNData({isFocused = false, comment, optionItem, ...propsToForward}: OptionRowLHNDataProps) { +function OptionRowLHNData({ + isFocused = false, + fullReport, + reportActions, + personalDetails = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + comment, + policy, + receiptTransactions, + parentReportAction, + transaction, + lastReportActionTransaction = {}, + transactionViolations, + canUseViolations, + ...propsToForward +}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; + const optionItemRef = useRef(); + + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null); + + const optionItem = useMemo(() => { + // Note: ideally we'd have this as a dependent selector in onyx! + const item = SidebarUtils.getOptionData({ + report: fullReport, + reportActions, + personalDetails, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, + policy, + parentReportAction, + hasViolations: !!hasViolations, + }); + if (deepEqual(item, optionItemRef.current)) { + return optionItemRef.current; + } + + optionItemRef.current = item; + + return item; + // Listen parentReportAction to update title of thread report when parentReportAction changed + // Listen to transaction to update title of transaction report when transaction changed + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + fullReport, + lastReportActionTransaction, + reportActions, + personalDetails, + preferredLocale, + policy, + parentReportAction, + transaction, + transactionViolations, + canUseViolations, + receiptTransactions, + ]); + useEffect(() => { if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 3f72748ab8a8..e09e5cb6c8b5 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,22 +1,40 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; +import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; type OptionMode = ValueOf; -type OptionListItem = { - /** The reportID of the report */ - reportID: string; +type LHNOptionsListOnyxProps = { + /** The policy which the user has access to and which the report could be tied to */ + policy: OnyxCollection; - /** The item that should be rendered */ - optionItem: OptionData | undefined; + /** All reports shared with the user */ + reports: OnyxCollection; - /** Comment added to report */ - comment: string; + /** Array of report actions for this report */ + reportActions: OnyxCollection; + + /** Indicates which locale the user currently has selected */ + preferredLocale: OnyxEntry; + + /** List of users' personal details */ + personalDetails: OnyxEntry; + + /** The transaction from the parent report action */ + transactions: OnyxCollection; + + /** List of draft comments */ + draftComments: OnyxCollection; + + /** The list of transaction violations */ + transactionViolations: OnyxCollection; }; type CustomLHNOptionsListProps = { @@ -27,7 +45,7 @@ type CustomLHNOptionsListProps = { contentContainerStyles?: StyleProp; /** Sections for the section list */ - data: OptionListItem[]; + data: string[]; /** Callback to fire when a row is selected */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; @@ -42,21 +60,51 @@ type CustomLHNOptionsListProps = { onFirstItemRendered: () => void; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ isFocused?: boolean; + /** List of users' personal details */ + personalDetails?: PersonalDetailsList; + + /** The preferred language for the app */ + preferredLocale?: OnyxEntry; + + /** The full data of the report */ + fullReport: OnyxEntry; + + /** The policy which the user has access to and which the report could be tied to */ + policy?: OnyxEntry; + + /** The action from the parent report */ + parentReportAction?: OnyxEntry; + + /** The transaction from the parent report action */ + transaction: OnyxEntry; + + /** The transaction linked to the report's last action */ + lastReportActionTransaction?: OnyxEntry; + /** Comment added to report */ comment: string; - /** The item that should be rendered */ - optionItem: OptionData | undefined; + /** The receipt transaction from the parent report action */ + receiptTransactions: OnyxCollection; /** The reportID of the report */ reportID: string; + /** Array of report actions for this report */ + reportActions: OnyxEntry; + + /** List of transaction violation */ + transactionViolations: OnyxCollection; + + /** Whether the user can use violations */ + canUseViolations: boolean | undefined; + /** Toggle between compact and default view */ viewMode?: OptionMode; @@ -89,6 +137,6 @@ type OptionRowLHNProps = { onLayout?: (event: LayoutChangeEvent) => void; }; -type RenderItemProps = {item: OptionListItem}; +type RenderItemProps = {item: string}; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, OptionListItem, RenderItemProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index d368abd897af..f9aaeaf400c1 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -1,17 +1,14 @@ import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {usePersonalDetails, useReport} from '@components/OnyxProvider'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Locale, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; -import usePermissions from './usePermissions'; type OnyxProps = { chatReports: OnyxCollection; @@ -21,8 +18,6 @@ type OnyxProps = { transactionViolations: OnyxCollection; policyMembers: OnyxCollection; priorityMode: OnyxEntry; - preferredLocale: OnyxEntry; - draftComments: OnyxCollection; }; type WithOrderedReportListItemsContextProviderProps = OnyxProps & { @@ -40,12 +35,8 @@ function WithOrderedReportListItemsContextProvider({ transactionViolations, policyMembers, priorityMode, - preferredLocale, - draftComments, }: WithOrderedReportListItemsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); - const personalDetails = usePersonalDetails(); - const {canUseViolations} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); @@ -80,34 +71,7 @@ function WithOrderedReportListItemsContextProvider({ return orderedReportIDs; }, [getOrderedReportIDs, currentReportIDValue?.currentReportID, orderedReportIDs]); - const orderedReportListItems = useMemo( - () => - orderedReportIDsWithCurrentReport.map((reportID) => { - const itemFullReport = chatReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const itemReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; - const itemParentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; - const itemPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; - - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(itemFullReport, transactionViolations, itemParentReportAction ?? null); - - const item = SidebarUtils.getOptionData({ - report: itemFullReport, - reportActions: itemReportActions, - personalDetails, - preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, - policy: itemPolicy, - parentReportAction: itemParentReportAction, - hasViolations: !!hasViolations, - }); - - return {reportID, optionItem: item, comment: itemComment}; - }), - [orderedReportIDsWithCurrentReport, canUseViolations, personalDetails, draftComments, preferredLocale, chatReports, allReportActions, policies, transactionViolations], - ); - - return {children}; + return {children}; } const OrderedReportListItemsContextProvider = withOnyx({ @@ -138,13 +102,6 @@ const OrderedReportListItemsContextProvider = withOnyx Date: Thu, 29 Feb 2024 19:48:44 +0500 Subject: [PATCH 0043/1357] fix: linting --- src/hooks/useOrderedReportListItems.tsx | 2 +- src/pages/home/sidebar/SidebarLinks.js | 2 +- tests/utils/collections/createCollection.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index f9aaeaf400c1..bcd22320d1ca 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -6,7 +6,7 @@ import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 4c9803e257bc..8cda70d2486a 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -33,7 +33,7 @@ const basePropTypes = { const propTypes = { ...basePropTypes, - optionListItems: PropTypes.arrayOf(PropTypes.object).isRequired, + optionListItems: PropTypes.arrayOf(PropTypes.string).isRequired, isLoading: PropTypes.bool.isRequired, diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index e62d9769bc53..bcc37c301279 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -10,7 +10,7 @@ export default function createCollection(createKey: (item: T, index: number) return map; } -export function createNestedCollection( +function createNestedCollection( createParentKey: (item: T, index: number) => string | number, createKey: (item: T, index: number) => string | number, createItem: (index: number) => T, @@ -30,3 +30,7 @@ export function createNestedCollection( return map; } + +export { + createNestedCollection, +} \ No newline at end of file From 6e944446670094973945f38b05c5c26416902f70 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 29 Feb 2024 17:59:45 +0100 Subject: [PATCH 0044/1357] feat: wip - display rates for policy --- ...oraryForRefactorRequestConfirmationList.js | 4 +- src/libs/DistanceRequestUtils.ts | 119 +++++++++++++++--- .../iou/request/step/IOURequestStepRate.tsx | 49 +++++--- src/types/onyx/Policy.ts | 4 +- 4 files changed, 139 insertions(+), 37 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 46e4cb3d6733..267662f07157 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -681,7 +681,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + callback: (policy, key) => { + if (!policy || !key || !policy.name) { + return; + } + + policies[key] = policy; + }, +}); + /** * Retrieves the default mileage rate based on a given policy. * @@ -67,22 +81,21 @@ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { */ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - return convertedDistance.toFixed(2); + // TODO: add logic for currencies for which we need to round to 4 decimals + return convertedDistance.toFixed(3); } /** -* @param hasRoute Whether the route exists for the distance request -* @param distanceInMeters Distance traveled -* @param unit Unit that should be used to display the distance -* @param rate Expensable amount allowed per unit -* @param currency The currency associated with the rate -* @param translate Translate function -* @param toLocaleDigit Function to convert to localized digit -* @returns A string that describes the distance traveled and the rate used for expense calculation -*/ + * @param hasRoute Whether the route exists for the distance request + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param currency The currency associated with the rate + * @param translate Translate function + * @param toLocaleDigit Function to convert to localized digit + * @returns A string that describes the distance traveled and the rate used for expense calculation + */ function getRateForDisplay( hasRoute: boolean, - distanceInMeters: number, unit: Unit, rate: number, currency: string, @@ -101,6 +114,28 @@ function getRateForDisplay( return `${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } +// TODO: I wonder if it would be better to rfactor these functions to pass params in an object +/** + * @param hasRoute Whether the route exists for the distance request + * @param distanceInMeters Distance traveled + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param translate Translate function + * @returns A string that describes the distance traveled + */ +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, translate: LocaleContextProps['translate']): string { + if (!hasRoute) { + return translate('iou.routePending'); + } + + const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); + const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; + + return `${distanceInUnits} ${unitString}`; +} + /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -124,13 +159,52 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit); - const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); - const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); - const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; - const ratePerUnit = getRateForDisplay(hasRoute, distanceInMeters, unit, rate, currency, translate, toLocaleDigit); + const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, translate); + const ratePerUnit = getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + + return `${distanceInUnits} @ ${ratePerUnit}`; +} + +/** + * Retrieves the mileage rates for given policy. + * + * @param policyID - The policy ID from which to extract the mileage rates. + * + * @returns An array of mileage rates or an empty array if not found. + */ +function getMileageRates(policyID?: string): MileageRate[] | [] { + if (!policyID) { + return []; + } + + const mileageRates: MileageRate[] = []; + + const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; + + if (!policy || !policy?.customUnits) { + return mileageRates; + } + + const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceUnit?.rates) { + return mileageRates; + } + + const rates = Object.values(distanceUnit.rates); + + for (const rate of rates) { + if (rate.enabled) { + mileageRates.push({ + rate: rate.rate ?? 0, + name: rate.name, + currency: rate.currency ?? 'USD', + unit: distanceUnit.attributes.unit, + customUnitRateID: rate.customUnitRateID, + }); + } + } - return `${distanceInUnits} ${unitString} @ ${ratePerUnit}`; + return mileageRates; } /** @@ -147,4 +221,11 @@ function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): n return Math.round(roundedDistance * rate); } -export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount, getRateForDisplay}; +export default { + getDefaultMileageRate, + getDistanceMerchant, + getDistanceRequestAmount, + getRateForDisplay, + getMileageRates, + getDistanceForDisplay, +}; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index d82238f2c7ea..3b7889de9651 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -1,17 +1,27 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import compose from '@libs/compose'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; +import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; +import type {Policy} from '@src/types/onyx'; type Props = { // eslint-disable-next-line react/no-unused-prop-types lastSelectedDistanceRate: string; + + /** Policy details */ + policy: OnyxEntry; + /** The route object passed to this screen */ route: { /** The params passed to this screen */ @@ -22,18 +32,22 @@ type Props = { }; }; -const mockRates = [ - {text: 'Default Rate', alternateText: '$0.656 / mile', keyForList: 'DefaultRate'}, - {text: 'Custom Rate', alternateText: '$0.700 / mile', keyForList: 'CustomRate'}, -]; - function IOURequestStepRate({ + policy, route: { params: {backTo}, }, }: Props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); + const rates = DistanceRequestUtils.getMileageRates(policy?.id); + + const data = rates.map((rate) => ({ + text: rate.name ?? '', + alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), + keyForList: rate.name ?? '', + value: rate.customUnitRateID, + })); return ( {translate('iou.chooseARate')} {}} - initiallyFocusedOptionKey="DefaultRate" + // TODO: change for lastSelectedDistanceRates + initiallyFocusedOptionKey="Default Rate" /> ); @@ -56,10 +71,14 @@ function IOURequestStepRate({ IOURequestStepRate.displayName = 'IOURequestStepRate'; -// export default withOnyx({ -// lastSelectedDistanceRate: { -// key: 'ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATE', -// }, -// })(IOURequestStepRate); - -export default IOURequestStepRate; +export default compose( + withWritableReportOrNotFound, + withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, + // lastSelectedDistanceRates: { + // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + // }, + }), +)(IOURequestStepRate); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1eece2d3a1e0..8e408951a84f 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -19,8 +19,10 @@ type Attributes = { type MileageRate = { unit: Unit; - rate?: number; currency: string; + customUnitRateID?: string; + rate?: number; + name?: string; }; type CustomUnit = OnyxCommon.OnyxValueWithOfflineFeedback<{ From e735658f12826766791c2bfa1d3bb7b82b4559d6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 1 Mar 2024 13:30:58 +0500 Subject: [PATCH 0045/1357] test: fix failing test --- .../LHNOptionsList/LHNOptionsList.tsx | 2 +- src/components/withCurrentReportID.tsx | 2 +- src/hooks/useOrderedReportListItems.tsx | 18 +++++++-- tests/unit/SidebarOrderTest.ts | 6 +-- tests/utils/LHNTestUtils.tsx | 37 ++++++++++++------- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 5569e53942aa..d5b422122144 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -112,7 +112,7 @@ function LHNOptionsList({ ], ); - const extraData = useMemo(() => [currentReportID], [currentReportID]); + const extraData = useMemo(() => [reportActions, reports, policy, personalDetails], [reportActions, reports, policy, personalDetails]); return ( diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index d5c5a6896a9c..0d052f759f81 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -45,7 +45,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro * switching between chat list and settings->workspaces tab. * and doing so avoid unnecessary re-render of `useOrderedReportListItems`. */ - if (reportID) { + if (reportID !== undefined) { setCurrentReportID(reportID); } }, diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useOrderedReportListItems.tsx index bcd22320d1ca..b160c372679a 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useOrderedReportListItems.tsx @@ -22,6 +22,7 @@ type OnyxProps = { type WithOrderedReportListItemsContextProviderProps = OnyxProps & { children: React.ReactNode; + currentReportIDForTests?: string; }; const OrderedReportListItemsContext = createContext({}); @@ -35,8 +36,19 @@ function WithOrderedReportListItemsContextProvider({ transactionViolations, policyMembers, priorityMode, + /** + * Only required to make unit tests work, since we + * explicitly pass the currentReportID in LHNTestUtils + * to SidebarLinksData, so this context doesn't have an + * access to currentReportID in that case. + * + * So this is a work around to have currentReportID available + * only in testing environment. + */ + currentReportIDForTests, }: WithOrderedReportListItemsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); + const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; const {activeWorkspaceID} = useActiveWorkspace(); const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); @@ -65,11 +77,11 @@ function WithOrderedReportListItemsContextProvider({ // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const orderedReportIDsWithCurrentReport = useMemo(() => { - if (currentReportIDValue?.currentReportID && !orderedReportIDs.includes(currentReportIDValue.currentReportID)) { - return getOrderedReportIDs(currentReportIDValue.currentReportID); + if (derivedCurrentReportID && !orderedReportIDs.includes(derivedCurrentReportID)) { + return getOrderedReportIDs(derivedCurrentReportID); } return orderedReportIDs; - }, [getOrderedReportIDs, currentReportIDValue?.currentReportID, orderedReportIDs]); + }, [getOrderedReportIDs, derivedCurrentReportID, orderedReportIDs]); return {children}; } diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 27da8348f43d..10d30e4c6dc8 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -298,7 +298,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, }), ) @@ -362,7 +362,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, }), ) @@ -429,7 +429,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.POLICY}${fakeReport.policyID}`]: fakePolicy, ...reportCollectionDataSet, }), diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index a10dbbdc17f8..c8c3f145d951 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -281,19 +281,30 @@ function getFakeAdvancedReportAction(actionName: ActionName = 'IOU', actor = 'em function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( - - {}} - insets={{ - top: 0, - left: 0, - right: 0, - bottom: 0, - }} - isSmallScreenWidth={false} - currentReportID={currentReportID} - /> + + {/* + * Only required to make unit tests work, since we + * explicitly pass the currentReportID in LHNTestUtils + * to SidebarLinksData, so this context doesn't have an + * access to currentReportID in that case. + * + * So this is a work around to have currentReportID available + * only in testing environment. + * */} + + {}} + insets={{ + top: 0, + left: 0, + right: 0, + bottom: 0, + }} + isSmallScreenWidth={false} + currentReportID={currentReportID} + /> + ); } From 94fcc48f89f95ba50fc22a20ac04183dbd25cb8a Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 1 Mar 2024 13:31:12 +0500 Subject: [PATCH 0046/1357] fix: prettier --- tests/utils/collections/createCollection.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index bcc37c301279..4a2a1fa4eb78 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -31,6 +31,4 @@ function createNestedCollection( return map; } -export { - createNestedCollection, -} \ No newline at end of file +export {createNestedCollection}; From cde1d19d37cdd0203ca6023612dfe12ea0f46537 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 16:12:00 +0100 Subject: [PATCH 0047/1357] feat: wip - get rate for p2p --- ...oraryForRefactorRequestConfirmationList.js | 52 ++++++++++++++++--- src/libs/DistanceRequestUtils.ts | 13 +++-- ...yForRefactorRequestParticipantsSelector.js | 4 +- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 267662f07157..5a7019e63428 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -239,6 +239,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, + lastSelectedDistanceRate = {}, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -250,6 +251,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const {unit, rate, currency} = mileageRate; + + // const rate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' + + // const {unit, rate, currency} = lastSelectedDistanceRate || mileageRate; + + const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; const taxRates = lodashGet(policy, 'taxRates', {}); @@ -266,6 +273,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; + console.log({transaction}); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field @@ -622,6 +631,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] + // change for true for development + const canUseP2PDistanceRequests = true; + // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ @@ -681,7 +694,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ), - shouldShow: isDistanceRequest, + shouldShow: isDistanceRequest && !canUseP2PDistanceRequests, isSupplementary: true, }, { item: ( Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))} + disabled={didConfirm || !isTypeRequest} + interactive={!isReadOnly} + /> + ), + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, + isSupplementary: true, + }, + { + item: ( + { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_RATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()), + ); }} disabled={didConfirm || !isTypeRequest} - interactive={!isReadOnly} + interactive={!isReadOnly && isPolicyExpenseChat} /> ), - // TODO: hide when betas ready - shouldShow: isDistanceRequest, + shouldShow: isDistanceRequest && canUseP2PDistanceRequests, isSupplementary: true, }, { @@ -950,12 +982,16 @@ export default compose( policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, + // TODO: add NVP_LAST_SELECTED_DISTANCE_RATES + // lastSelectedDistanceRates: { + // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + // }, mileageRate: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - }, + } }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a605ace8da87..191edd93bfb9 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -12,6 +12,8 @@ type DefaultMileageRate = { rate?: number; currency?: string; unit: Unit; + name?: string; + customUnitRateID?: string; }; const policies: OnyxCollection = {}; @@ -55,6 +57,8 @@ function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | rate: distanceRate.rate, currency: distanceRate.currency, unit: distanceUnit.attributes.unit, + name: distanceRate.name, + customUnitRateID: distanceRate.customUnitRateID, }; } @@ -91,6 +95,7 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string return convertedDistance.toFixed(3); } +// TODO: I wonder if it would be better to refactor these functions to pass params in an object /** * @param hasRoute Whether the route exists for the distance request * @param unit Unit that should be used to display the distance @@ -120,7 +125,7 @@ function getRateForDisplay( return `${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } -// TODO: I wonder if it would be better to rfactor these functions to pass params in an object +// TODO: this function will be added in https://github.com/Expensify/App/pull/37185, remove it to avoid conflicts /** * @param hasRoute Whether the route exists for the distance request * @param distanceInMeters Distance traveled @@ -129,8 +134,8 @@ function getRateForDisplay( * @param translate Translate function * @returns A string that describes the distance traveled */ -function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, translate: LocaleContextProps['translate']): string { - if (!hasRoute) { +function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, rate: number, translate: LocaleContextProps['translate']): string { + if (!hasRoute || !rate) { return translate('iou.routePending'); } @@ -165,7 +170,7 @@ function getDistanceMerchant( return translate('iou.routePending'); } - const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, translate); + const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate); const ratePerUnit = getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); return `${distanceInUnits} @ ${ratePerUnit}`; diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 2865316b7fd5..b42b94afc686 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -121,7 +121,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType === CONST.IOU.TYPE.REQUEST, // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, false, {}, [], @@ -131,7 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, + true, false, ); From c3cb80bf507de6c4922b28e170893cdcaa126ebb Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 17:38:59 +0100 Subject: [PATCH 0048/1357] feat: wip - new rate field logic --- src/CONST.ts | 7 ++++ ...oraryForRefactorRequestConfirmationList.js | 40 +++++++++---------- src/libs/DistanceRequestUtils.ts | 39 +++++++++--------- src/libs/actions/IOU.ts | 13 ++++++ .../iou/request/step/IOURequestStepRate.tsx | 12 +++++- 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8d4eaac44a38..2bd047f6bf51 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1398,6 +1398,13 @@ const CONST = { RATE_DECIMALS: 3, }, + // TODO: remove this mock when https://github.com/Expensify/App/issues/36982 is done + CURRENCY_TO_DEFAULT_MILEAGE_RATE: { + USD: { unit: "mile", rate: 0.5 }, + EUR: { unit: "kilometer", rate: 0.8 }, + GBP: { unit: "mile", rate: 0.45 }, + }, + TERMS: { CFPB_PREPAID: 'cfpb.gov/prepaid', CFPB_COMPLAINT: 'cfpb.gov/complaint', diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 5a7019e63428..9c2d77239015 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -1,10 +1,10 @@ -import { useIsFocused } from '@react-navigation/native'; -import { format } from 'date-fns'; +import {useIsFocused} from '@react-navigation/native'; +import {format} from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; -import { View } from 'react-native'; -import { withOnyx } from 'react-native-onyx'; +import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; @@ -21,9 +21,9 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import playSound, { SOUNDS } from '@libs/Sound'; +import playSound, {SOUNDS} from '@libs/Sound'; import * as TransactionUtils from '@libs/TransactionUtils'; -import { policyPropTypes } from '@pages/workspace/withPolicy'; +import {policyPropTypes} from '@pages/workspace/withPolicy'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,8 +44,7 @@ import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; -import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes } from './withCurrentUserPersonalDetails'; - +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; const propTypes = { /** Callback to inform parent modal of success */ @@ -239,7 +238,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - lastSelectedDistanceRate = {}, + mileageRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -250,12 +249,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; const isTypeSend = iouType === CONST.IOU.TYPE.SEND; + // TODO: uncomment after Splits and P2P are enabled https://github.com/Expensify/App/pull/37185, mileageRate prop should be be removed + // const mileageRate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; const {unit, rate, currency} = mileageRate; - // const rate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' - - // const {unit, rate, currency} = lastSelectedDistanceRate || mileageRate; - + // will hardcoded rates will have a currency property? If not we have to get currency other way const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; @@ -273,8 +271,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; - console.log({transaction}); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field @@ -633,7 +629,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] // change for true for development - const canUseP2PDistanceRequests = true; + const canUseP2PDistanceRequests = false; // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. @@ -982,16 +978,16 @@ export default compose( policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, - // TODO: add NVP_LAST_SELECTED_DISTANCE_RATES - // lastSelectedDistanceRates: { - // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - // }, mileageRate: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, + mileageRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + selector: (policy) => DistanceRequestUtils.getMileageRates(policy ? policy.id : ''), + }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - } + }, }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 191edd93bfb9..dd95612af3e6 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,4 +1,4 @@ -import type {OnyxEntry, OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; @@ -183,13 +183,13 @@ function getDistanceMerchant( * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policyID?: string): DefaultMileageRate[] | [] { +function getMileageRates(policyID?: string): Record { + const mileageRates = {}; + if (!policyID) { - return []; + return mileageRates; } - const mileageRates: DefaultMileageRate[] = []; - const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? null; if (!policy || !policy?.customUnits) { @@ -201,23 +201,25 @@ function getMileageRates(policyID?: string): DefaultMileageRate[] | [] { return mileageRates; } - const rates = Object.values(distanceUnit.rates); - - for (const rate of rates) { - if (rate.enabled) { - mileageRates.push({ - rate: rate.rate ?? 0, - name: rate.name, - currency: rate.currency ?? 'USD', - unit: distanceUnit.attributes.unit, - customUnitRateID: rate.customUnitRateID, - }); - } - } + Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { + // TODO: fix TS error + mileageRates[rateID] = { + rate: rate.rate, + currency: rate.currency, + unit: distanceUnit.attributes.unit, + name: rate.name, + customUnitRateID: rate.customUnitRateID, + }; + }); return mileageRates; } +// TODO: probably will need to be changed +function getRateForP2P(currency) { + return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; +} + /** * Calculates the request amount based on distance, unit, and rate. * @@ -239,4 +241,5 @@ export default { getRateForDisplay, getMileageRates, getDistanceForDisplay, + getRateForP2P, }; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5f9657755b02..91dd95b12592 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -355,6 +355,17 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: }); } +/** Set the last selected distance rate for policy */ +// TODO: probably need to be changed +function setLastSelectedDistanceRates(policyID: string, rateID: string) { + Onyx.merge('lastSelectedDistanceRates', {[policyID]: rateID}); +} + +/** Update transaction distance rate */ +function updateDistanceRequestRate(transactionID: string, rateID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {customUnit: {customUnitRateID: rateID}}}); +} + /** Reset money request info from the store with its initial value */ function resetMoneyRequestInfo(id = '') { // Disabling this line since currentDate can be an empty string @@ -4327,4 +4338,6 @@ export { cancelPayment, navigateToStartStepIfScanFileCannotBeRead, savePreferredPaymentMethod, + setLastSelectedDistanceRates, + updateDistanceRequestRate, }; diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 3b7889de9651..a2cf7a0af1c8 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -9,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as IOU from '@libs/actions/IOU'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -42,13 +43,20 @@ function IOURequestStepRate({ const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); - const data = rates.map((rate) => ({ + const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), keyForList: rate.name ?? '', value: rate.customUnitRateID, })); + const selectDistanceRate = (customUnitRateID) => { + IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); + // TODO: get a proper transaction ID + IOU.updateDistanceRequestRate('1', customUnitRateID); + Navigation.goBack(backTo); + } + return ( {}} + onSelectRow={({value}) => selectDistanceRate(value)} // TODO: change for lastSelectedDistanceRates initiallyFocusedOptionKey="Default Rate" /> From 5bf399af20bdb7816bedf1ed40ee37fa83e3fffa Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 1 Mar 2024 17:51:11 +0100 Subject: [PATCH 0049/1357] fix: revert filtering changes --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index b42b94afc686..2865316b7fd5 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -121,7 +121,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ iouType === CONST.IOU.TYPE.REQUEST, // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - true, + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, {}, [], @@ -131,7 +131,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. // This functionality is being built here: https://github.com/Expensify/App/issues/23291 - true, + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, false, ); From 930eb47f5222088e8f687d1be63707d83ec239e2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 4 Mar 2024 17:18:45 +0700 Subject: [PATCH 0050/1357] remove global ref and create suggestion context --- src/App.tsx | 2 + src/libs/actions/SuggestionsActions.ts | 50 ------------------- .../ComposerWithSuggestionsEdit.tsx | 25 +++++----- .../SuggestionsContext.tsx | 50 +++++++++++++++++++ .../report/ReportActionItemMessageEdit.tsx | 21 +++++--- src/pages/home/report/ReportActionsView.js | 10 +++- 6 files changed, 87 insertions(+), 71 deletions(-) delete mode 100644 src/libs/actions/SuggestionsActions.ts create mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx diff --git a/src/App.tsx b/src/App.tsx index cbe5948f8d4e..03dc3c328488 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,7 @@ import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; +import {SuggestionsContextProvider} from './pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; @@ -76,6 +77,7 @@ function App({url}: AppProps) { ActiveElementRoleProvider, ActiveWorkspaceContextProvider, PlaybackContextProvider, + SuggestionsContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, ]} diff --git a/src/libs/actions/SuggestionsActions.ts b/src/libs/actions/SuggestionsActions.ts deleted file mode 100644 index 9fecd18f7d10..000000000000 --- a/src/libs/actions/SuggestionsActions.ts +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import type {NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; - -type SuggestionsRef = { - getSuggestions: () => void; - resetSuggestions: () => void; - triggerHotkeyActions: (event: NativeSyntheticEvent | KeyboardEvent) => boolean; - onSelectionChange: (event: NativeSyntheticEvent) => void; - updateShouldShowSuggestionMenuToFalse: () => void; - setShouldBlockSuggestionCalc: () => void; -}; - -const suggestionsRef = React.createRef(); - -function resetSuggestions() { - if (!suggestionsRef.current) { - return; - } - - suggestionsRef.current.resetSuggestions(); -} - -function triggerHotkeyActions(event: NativeSyntheticEvent | KeyboardEvent): boolean { - if (!suggestionsRef.current) { - return false; - } - - return suggestionsRef.current.triggerHotkeyActions(event); -} - -function updateShouldShowSuggestionMenuToFalse() { - if (!suggestionsRef.current) { - return; - } - - suggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); -} - -function onSelectionChange(event: NativeSyntheticEvent) { - if (!suggestionsRef.current) { - return; - } - - suggestionsRef.current.onSelectionChange(event); -} - -export {suggestionsRef, resetSuggestions, triggerHotkeyActions, onSelectionChange, updateShouldShowSuggestionMenuToFalse}; - -// eslint-disable-next-line import/prefer-default-export -export type {SuggestionsRef}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx index 379928e8f28f..ce8cfa1dc356 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx @@ -1,24 +1,26 @@ import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; import React, {useState} from 'react'; -import type {TextInput} from 'react-native'; +import type {MeasureInWindowOnSuccessCallback, TextInput} from 'react-native'; import Composer from '@components/Composer'; import type {ComposerProps} from '@components/Composer/types'; -import type {SuggestionsRef} from '@libs/actions/SuggestionsActions'; +import type {SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; -type ComposerWithSuggestionsEditProps = { +type Selection = { + start: number; + end: number; +}; + +type ComposerWithSuggestionsEditProps = ComposerProps & { setValue: Dispatch>; - setSelection: Dispatch< - SetStateAction<{ - start: number; - end: number; - }> - >; + setSelection: Dispatch>; resetKeyboardInput: () => void; isComposerFocused: boolean; suggestionsRef: RefObject; updateDraft: (newValue: string) => void; - measureParentContainer: (callback: () => void) => void; + measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; + value: string; + selection: Selection; }; function ComposerWithSuggestionsEdit( @@ -43,7 +45,7 @@ function ComposerWithSuggestionsEdit( updateDraft, measureParentContainer, id = undefined, - }: ComposerWithSuggestionsEditProps & ComposerProps, + }: ComposerWithSuggestionsEditProps, ref: ForwardedRef, ) { const [composerHeight, setComposerHeight] = useState(0); @@ -74,7 +76,6 @@ function ComposerWithSuggestionsEdit( ; + updateCurrentActiveSuggestionsRef: (ref: SuggestionsRef | null, id: string) => void; + clearActiveSuggestionsRef: () => void; +}; + +const SuggestionsContext = createContext({ + currentActiveSuggestionsRef: {current: null}, + updateCurrentActiveSuggestionsRef: () => {}, + clearActiveSuggestionsRef: () => {}, +}); + +function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) { + const currentActiveSuggestionsRef = useRef(null); + const [activeID, setActiveID] = useState(null); + + const updateCurrentActiveSuggestionsRef = useCallback((ref: SuggestionsRef | null, id: string) => { + currentActiveSuggestionsRef.current = ref; + setActiveID(id); + }, []); + + const clearActiveSuggestionsRef = useCallback(() => { + currentActiveSuggestionsRef.current = null; + setActiveID(null); + }, []); + + const contextValue = useMemo( + () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef}), + [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef], + ); + + return {children}; +} + +function useSuggestionsContext() { + const context = useContext(SuggestionsContext); + return context; +} + +SuggestionsContextProvider.displayName = 'PlaybackContextProvider'; + +export {SuggestionsContextProvider, useSuggestionsContext}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 6a182a6016c3..ad516946fecf 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -5,7 +5,7 @@ import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {findNodeHandle, Keyboard, NativeModules, View} from 'react-native'; -import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; +import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; @@ -21,7 +21,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; @@ -42,6 +41,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; +import {useSuggestionsContext} from './ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; +import type {SuggestionsRef} from './ReportActionCompose/ReportActionCompose'; const {RNTextInputReset} = NativeModules; @@ -81,6 +82,7 @@ function ReportActionItemMessageEdit( const {translate, preferredLocale} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); const {isSmallScreenWidth} = useWindowDimensions(); + const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef} = useSuggestionsContext(); const getInitialDraft = () => { if (draftMessage === action?.message?.[0].html) { @@ -125,6 +127,7 @@ function ReportActionItemMessageEdit( const insertedEmojis = useRef([]); const draftRef = useRef(draft); const containerRef = useRef(null); + const suggestionsRef = useRef(null); useEffect(() => { if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { @@ -257,7 +260,7 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { - SuggestionsAction.resetSuggestions(); + suggestionsRef.current?.resetSuggestions(); insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -352,7 +355,7 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - if (SuggestionsAction.triggerHotkeyActions(e)) { + if (suggestionsRef.current?.triggerHotkeyActions(e as KeyboardEvent)) { return; } @@ -379,7 +382,7 @@ function ReportActionItemMessageEdit( }, [textInputRef]); const measureContainer = useCallback( - (callback: () => void) => { + (callback: MeasureInWindowOnSuccessCallback) => { if (!containerRef.current) { return; } @@ -465,11 +468,15 @@ function ReportActionItemMessageEdit( if (!ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } + + updateCurrentActiveSuggestionsRef(suggestionsRef.current, action.reportActionID); }} onBlur={(event: NativeSyntheticEvent) => { setIsFocused(false); // @ts-expect-error TODO: TextInputFocusEventData doesn't contain relatedTarget. const relatedTargetId = event.nativeEvent?.relatedTarget?.id; + suggestionsRef.current?.resetSuggestions(); + clearActiveSuggestionsRef(); if (relatedTargetId && [messageEditInput, emojiButtonID].includes(relatedTargetId)) { return; } @@ -477,14 +484,14 @@ function ReportActionItemMessageEdit( }} selection={selection} onSelectionChange={(e) => { - SuggestionsAction.onSelectionChange(e); + suggestionsRef.current?.onSelectionChange?.(e); setSelection(e.nativeEvent.selection); }} setValue={setDraft} setSelection={setSelection} isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} resetKeyboardInput={resetKeyboardInput} - suggestionsRef={SuggestionsAction.suggestionsRef} + suggestionsRef={suggestionsRef} updateDraft={updateDraft} measureParentContainer={measureContainer} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 5a167dbb376f..a7584a3570dd 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -11,7 +11,6 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withW import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; import usePrevious from '@hooks/usePrevious'; -import * as SuggestionsAction from '@libs/actions/SuggestionsActions'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; @@ -25,6 +24,7 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PopoverReactionList from './ReactionList/PopoverReactionList'; +import {useSuggestionsContext} from './ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import reportActionPropTypes from './reportActionPropTypes'; import ReportActionsList from './ReportActionsList'; @@ -88,6 +88,7 @@ const defaultProps = { function ReportActionsView(props) { useCopySelectionHelper(); const reactionListRef = useContext(ReactionListContext); + const {currentActiveSuggestionsRef} = useSuggestionsContext(); const didLayout = useRef(false); const didSubscribeToReportTypingEvents = useRef(false); const isFirstRender = useRef(true); @@ -261,7 +262,12 @@ function ReportActionsView(props) { report={props.report} parentReportAction={props.parentReportAction} onLayout={recordTimeToMeasureItemLayout} - onScroll={SuggestionsAction.updateShouldShowSuggestionMenuToFalse} + onScroll={() => { + if (!currentActiveSuggestionsRef.current) { + return; + } + currentActiveSuggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); + }} sortedReportActions={props.reportActions} mostRecentIOUReportActionID={mostRecentIOUReportActionID} loadOlderChats={loadOlderChats} From e0673e0baf75e6610c39c234cd758e8958b6157c Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 14:50:54 +0500 Subject: [PATCH 0051/1357] refactor: rename to useReportIDs --- src/App.tsx | 4 +- ...edReportListItems.tsx => useReportIDs.tsx} | 66 +++++++++++-------- src/pages/home/sidebar/SidebarLinksData.js | 4 +- tests/utils/LHNTestUtils.tsx | 6 +- 4 files changed, 46 insertions(+), 34 deletions(-) rename src/hooks/{useOrderedReportListItems.tsx => useReportIDs.tsx} (72%) diff --git a/src/App.tsx b/src/App.tsx index 3a294757e149..6cfc2587074b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; -import {OrderedReportListItemsContextProvider} from './hooks/useOrderedReportListItems'; +import {ReportIDsContextProvider} from './hooks/useReportIDs'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; @@ -76,7 +76,7 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - OrderedReportListItemsContextProvider, + ReportIDsContextProvider, PlaybackContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, diff --git a/src/hooks/useOrderedReportListItems.tsx b/src/hooks/useReportIDs.tsx similarity index 72% rename from src/hooks/useOrderedReportListItems.tsx rename to src/hooks/useReportIDs.tsx index 4580df82ed34..651e21f08b24 100644 --- a/src/hooks/useOrderedReportListItems.tsx +++ b/src/hooks/useReportIDs.tsx @@ -1,7 +1,7 @@ +import _ from 'lodash'; import React, {createContext, useCallback, useContext, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import _ from 'underscore'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; @@ -9,6 +9,7 @@ import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; import usePermissions from './usePermissions'; @@ -24,14 +25,22 @@ type OnyxProps = { allTransactions: OnyxCollection; }; -type WithOrderedReportListItemsContextProviderProps = OnyxProps & { +type WithReportIDsContextProviderProps = OnyxProps & { children: React.ReactNode; currentReportIDForTests?: string; }; -const OrderedReportListItemsContext = createContext({}); +type ReportIDsContextValue = { + orderedReportIDs: string[]; + reportIDsWithErrors: Record; +}; + +const ReportIDsContext = createContext({ + orderedReportIDs: [], + reportIDsWithErrors: {}, +}); -function WithOrderedReportListItemsContextProvider({ +function WithReportIDsContextProvider({ children, chatReports, betas, @@ -51,7 +60,7 @@ function WithOrderedReportListItemsContextProvider({ * only in testing environment. */ currentReportIDForTests, -}: WithOrderedReportListItemsContextProviderProps) { +}: WithReportIDsContextProviderProps) { const currentReportIDValue = useCurrentReportID(); const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; const {activeWorkspaceID} = useActiveWorkspace(); @@ -59,22 +68,25 @@ function WithOrderedReportListItemsContextProvider({ const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); - const chatReportsKeys = useMemo(() => _.keys(chatReports), [chatReports]); - const reportIDsWithErrors = useMemo(() => { - return _.reduce( - chatReportsKeys, - (errorsMap, reportKey) => { - const report = chatReports && chatReports[reportKey]; - const allReportsActions = allReportActions && allReportActions[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)]; - const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions) || {}; - if (_.size(errors) === 0) { - return errorsMap; - } - return {...errorsMap, [reportKey.replace(ONYXKEYS.COLLECTION.REPORT, '')]: errors}; - }, - {}, - ); - }, [chatReportsKeys, allReportActions, allTransactions, chatReports]); + const chatReportsKeys = useMemo(() => Object.keys(chatReports ?? {}), [chatReports]); + // eslint-disable-next-line you-dont-need-lodash-underscore/reduce + const reportIDsWithErrors = useMemo( + () => + _.reduce( + chatReportsKeys, + (errorsMap, reportKey) => { + const report = chatReports?.[reportKey] ?? null; + const allReportsActions = allReportActions?.[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)] ?? null; + const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions, allTransactions) || {}; + if (Object.values(errors).length === 0) { + return errorsMap; + } + return {...errorsMap, [reportKey.replace(ONYXKEYS.COLLECTION.REPORT, '')]: errors}; + }, + {}, + ), + [chatReportsKeys, allReportActions, allTransactions, chatReports], + ); const getOrderedReportIDs = useCallback( (currentReportID?: string) => @@ -116,10 +128,10 @@ function WithOrderedReportListItemsContextProvider({ [orderedReportIDsWithCurrentReport, reportIDsWithErrors], ); - return {children}; + return {children}; } -const OrderedReportListItemsContextProvider = withOnyx({ +const ReportIDsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, initialValue: {}, @@ -151,10 +163,10 @@ const OrderedReportListItemsContextProvider = withOnyx { if (deepEqual(orderedReportIDsRef.current, orderedReportIDs)) { diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index c8c3f145d951..04344ba71184 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -10,7 +10,7 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxProvider from '@components/OnyxProvider'; import {CurrentReportIDContextProvider} from '@components/withCurrentReportID'; import {EnvironmentProvider} from '@components/withEnvironment'; -import {OrderedReportListItemsContextProvider} from '@hooks/useOrderedReportListItems'; +import {ReportIDsContextProvider} from '@hooks/useReportIDs'; import DateUtils from '@libs/DateUtils'; import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; @@ -291,7 +291,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { * So this is a work around to have currentReportID available * only in testing environment. * */} - + {}} @@ -304,7 +304,7 @@ function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { isSmallScreenWidth={false} currentReportID={currentReportID} /> - + ); } From 30ca3030ee04390772e1a03bc69305fbf4160600 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 14:51:43 +0500 Subject: [PATCH 0052/1357] refactor: don't set currentReportID if it's on workspaces screen --- src/components/withCurrentReportID.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 0d052f759f81..54cdc84f127a 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -4,6 +4,7 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {createContext, forwardRef, useCallback, useMemo, useState} from 'react'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import Navigation from '@libs/Navigation/Navigation'; +import SCREENS from '@src/SCREENS'; type CurrentReportIDContextValue = { updateCurrentReportID: (state: NavigationState) => void; @@ -40,14 +41,17 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro const updateCurrentReportID = useCallback( (state: NavigationState) => { const reportID = Navigation.getTopmostReportId(state) ?? ''; + /** * This is to make sure we don't set the undefined as reportID when * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useOrderedReportListItems`. + * and doing so avoid unnecessary re-render of `useReportIDs`. */ - if (reportID !== undefined) { - setCurrentReportID(reportID); + const params = state.routes[state.index].params; + if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { + return; } + setCurrentReportID(reportID); }, [setCurrentReportID], ); From 121b5378606b353ecc014df082c140dfe5737d4f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 14:51:53 +0500 Subject: [PATCH 0053/1357] refactor: remove dead code --- tests/perf-test/SidebarUtils.perf-test.ts | 8 +------ tests/utils/collections/createCollection.ts | 23 --------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 8672d7c0cafe..2b2bdbc6b57a 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -10,7 +10,7 @@ import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; +import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction from '../utils/collections/reportActions'; @@ -29,12 +29,6 @@ const reportActions = createCollection( (index) => createRandomReportAction(index), ); -// const allReportActions = createNestedCollection( -// (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, -// (item) => `${item.reportActionID}`, -// (index) => createRandomReportAction(index), -// ); - const personalDetails = createCollection( (item) => item.accountID, (index) => createPersonalDetails(index), diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 4a2a1fa4eb78..848ef8f81f47 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,26 +9,3 @@ export default function createCollection(createKey: (item: T, index: number) return map; } - -function createNestedCollection( - createParentKey: (item: T, index: number) => string | number, - createKey: (item: T, index: number) => string | number, - createItem: (index: number) => T, - length = 500, -): Record> { - const map: Record> = {}; - - for (let i = 0; i < length; i++) { - const item = createItem(i); - const itemKey = createKey(item, i); - const itemParentKey = createParentKey(item, i); - map[itemParentKey] = { - ...map[itemParentKey], - [itemKey]: item, - }; - } - - return map; -} - -export {createNestedCollection}; From 9e41603f9da5a3eb38873a8ab24476c2eadacdaf Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 15:13:57 +0500 Subject: [PATCH 0054/1357] fix: linting --- src/hooks/useReportIDs.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 651e21f08b24..2e7c63182646 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -67,11 +67,10 @@ function WithReportIDsContextProvider({ const {canUseViolations} = usePermissions(); const policyMemberAccountIDs = useMemo(() => getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, getCurrentUserAccountID()), [activeWorkspaceID, policyMembers]); - const chatReportsKeys = useMemo(() => Object.keys(chatReports ?? {}), [chatReports]); - // eslint-disable-next-line you-dont-need-lodash-underscore/reduce const reportIDsWithErrors = useMemo( () => + // eslint-disable-next-line you-dont-need-lodash-underscore/reduce _.reduce( chatReportsKeys, (errorsMap, reportKey) => { From 4a3284161b02ea616cc40a1bc741221672e6e777 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Tue, 5 Mar 2024 15:17:33 +0500 Subject: [PATCH 0055/1357] refactor: remove irrelevant changes --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- src/components/LHNOptionsList/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 31af022a12aa..be8ce677b641 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -20,15 +20,15 @@ function LHNOptionsList({ contentContainerStyles, data, onSelectRow, - shouldDisableFocusOptions = false, - currentReportID = '', optionMode, + shouldDisableFocusOptions = false, reports = {}, reportActions = {}, policy = {}, preferredLocale = CONST.LOCALES.DEFAULT, personalDetails = {}, transactions = {}, + currentReportID = '', draftComments = {}, transactionViolations = {}, onFirstItemRendered = () => {}, diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 0d1bda775255..c122ab018392 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -139,7 +139,7 @@ type OptionRowLHNProps = { style?: StyleProp; /** The item that should be rendered */ - optionItem: OptionData | undefined; + optionItem?: OptionData; onLayout?: (event: LayoutChangeEvent) => void; }; From 581d09d7a3dec1f1c301c7dccfba4877e60cb7c4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 11 Mar 2024 11:33:46 +0500 Subject: [PATCH 0056/1357] fix: comments --- src/components/withCurrentReportID.tsx | 2 +- src/hooks/useReportIDs.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 54cdc84f127a..bb3283a21d25 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -45,7 +45,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro /** * This is to make sure we don't set the undefined as reportID when * switching between chat list and settings->workspaces tab. - * and doing so avoid unnecessary re-render of `useReportIDs`. + * and doing so avoids an unnecessary re-render of `useReportIDs`. */ const params = state.routes[state.index].params; if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 2e7c63182646..547975ae1cd0 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -56,8 +56,7 @@ function WithReportIDsContextProvider({ * to SidebarLinksData, so this context doesn't have an * access to currentReportID in that case. * - * So this is a work around to have currentReportID available - * only in testing environment. + * This is a workaround to have currentReportID available in testing environment. */ currentReportIDForTests, }: WithReportIDsContextProviderProps) { @@ -109,7 +108,7 @@ function WithReportIDsContextProvider({ // We need to make sure the current report is in the list of reports, but we do not want // to have to re-generate the list every time the currentReportID changes. To do that - // we first generate the list as if there was no current report, then here we check if + // we first generate the list as if there was no current report, then we check if // the current report is missing from the list, which should very rarely happen. In this // case we re-generate the list a 2nd time with the current report included. const orderedReportIDsWithCurrentReport = useMemo(() => { From fffe41ac7ed346c542da74cbe7952e581be0eb1c Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 11 Mar 2024 11:58:40 +0500 Subject: [PATCH 0057/1357] fix: safely check the nested properties --- src/components/withCurrentReportID.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index bb3283a21d25..22f68de9f57a 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -47,7 +47,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro * switching between chat list and settings->workspaces tab. * and doing so avoids an unnecessary re-render of `useReportIDs`. */ - const params = state.routes[state.index].params; + const params = state?.routes?.[state.index]?.params; if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { return; } From 6b41bf7819af297e0c3b7b32ced187b119c68193 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Mon, 11 Mar 2024 12:11:17 +0500 Subject: [PATCH 0058/1357] feat: add comments for extraData prop --- src/components/LHNOptionsList/LHNOptionsList.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index be8ce677b641..d141a5bbb3f4 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -129,6 +129,12 @@ function LHNOptionsList({ keyExtractor={keyExtractor} renderItem={renderItem} estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight} + // Previously, we were passing `extraData={[currentReportID]}`, which upon every render, was causing the + // re-render because of the new array reference. FlashList's children actually don't depend on the + // `currentReportID` prop but they depend on the `reportActions`, `reports`, `policy`, `personalDetails`. + // Previously it was working for us because of the new array reference. Even if you only pass an empty + // array, it will still work because of the new reference. But it's better to pass the actual dependencies + // to avoid unnecessary re-renders. extraData={extraData} showsVerticalScrollIndicator={false} /> From ef7b30e6a95bc95ed2ead63f27e25636398f7c6b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 10:19:52 +0100 Subject: [PATCH 0059/1357] refactor: use canUseP2PDistanceRequests from usePermissions --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 182aa5464772..7333d405363b 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -664,10 +664,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ ); }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); - // TODO: change for a value from usePermissions [will be added in this PR https://github.com/Expensify/App/pull/37185] - // change for true for development - const canUseP2PDistanceRequests = false; - // An intermediate structure that helps us classify the fields as "primary" and "supplementary". // The primary fields are always shown to the user, while an extra action is needed to reveal the supplementary ones. const classifiedFields = [ From 64127110f19b7df5f74d3a6cb207303fd190cd4d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 13 Mar 2024 16:44:48 +0500 Subject: [PATCH 0060/1357] fix: reassure tests --- tests/perf-test/SidebarUtils.perf-test.ts | 25 +++++---------------- tests/utils/collections/createCollection.ts | 25 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 8566abb97c7f..a30c298f7471 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -9,7 +9,7 @@ import type {PersonalDetails, TransactionViolation} from '@src/types/onyx'; import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; -import createCollection from '../utils/collections/createCollection'; +import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction, {getRandomDate} from '../utils/collections/reportActions'; @@ -51,24 +51,11 @@ const policies = createCollection( const mockedBetas = Object.values(CONST.BETAS); -const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${key}`, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, - }, - }, - ], - reportActionID: reportActions[key].reportActionID, - }, - ], - ]), -) as unknown as OnyxCollection; +const allReportActions = createNestedCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (item) => `${item.reportActionID}`, + (index) => createRandomReportAction(index), +); const currentReportId = '1'; const transactionViolations = {} as OnyxCollection; diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 848ef8f81f47..ddb0b68742a4 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,3 +9,28 @@ export default function createCollection(createKey: (item: T, index: number) return map; } + +function createNestedCollection( + createParentKey: (item: T, index: number) => string | number, + createKey: (item: T, index: number) => string | number, + createItem: (index: number) => T, + length = 500, +): Record> { + const map: Record> = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + const itemParentKey = createParentKey(item, i); + map[itemParentKey] = { + ...map[itemParentKey], + [itemKey]: item, + }; + } + + return map; +} + +export { + createNestedCollection, +}; \ No newline at end of file From e3477b2e751fe03702029de13aa8cee9f0ef68c6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Wed, 13 Mar 2024 16:51:56 +0500 Subject: [PATCH 0061/1357] fix: apply prettier --- tests/utils/collections/createCollection.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index ddb0b68742a4..4a2a1fa4eb78 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -31,6 +31,4 @@ function createNestedCollection( return map; } -export { - createNestedCollection, -}; \ No newline at end of file +export {createNestedCollection}; From 6f3afddef1d036658a48b38ca8f0caf6c696c85f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 17:28:24 +0100 Subject: [PATCH 0062/1357] fix: remove mock rates --- src/CONST.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9b2502cb17d1..d4fbd0ff6ef3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1431,13 +1431,6 @@ const CONST = { FAKE_P2P_ID: '_FAKE_P2P_ID_', }, - // TODO: remove this mock when https://github.com/Expensify/App/issues/36982 is done - CURRENCY_TO_DEFAULT_MILEAGE_RATE: { - USD: { unit: "mile", rate: 0.5 }, - EUR: { unit: "kilometer", rate: 0.8 }, - GBP: { unit: "mile", rate: 0.45 }, - }, - TERMS: { CFPB_PREPAID: 'cfpb.gov/prepaid', CFPB_COMPLAINT: 'cfpb.gov/complaint', From 50174f38629f23ef3363e51613755b05e30ec3d0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 13 Mar 2024 17:29:24 +0100 Subject: [PATCH 0063/1357] feat: create function for getting personal policy --- src/libs/PolicyUtils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4689fd03ebd0..6a3829b10934 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1,5 +1,6 @@ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -12,6 +13,14 @@ import Navigation from './Navigation/Navigation'; type MemberEmailsToAccountIDs = Record; type UnitRate = {rate: number}; +let allPolicies: OnyxCollection; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + waitForCollectionCallback: true, + callback: (value) => (allPolicies = value), +}); + /** * Filter out the active policies, which will exclude policies with pending deletion * These are policies that we can use to create reports with in NewDot. @@ -265,6 +274,10 @@ function goBackFromInvalidPolicy() { Navigation.navigateWithSwitchPolicyID({route: ROUTES.ALL_SETTINGS}); } +function getPersonalPolicy() { + return Object.values(allPolicies ?? {}).find((policy) => policy?.type === CONST.POLICY.TYPE.PERSONAL); +} + export { getActivePolicies, hasAccountingConnections, @@ -295,6 +308,7 @@ export { getPathWithoutPolicyID, getPolicyMembersByIdWithoutCurrentUser, goBackFromInvalidPolicy, + getPersonalPolicy }; export type {MemberEmailsToAccountIDs}; From 30b319f5e5b6ec940e9101bf11fcb5bfc28eef77 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 14 Mar 2024 13:41:19 +0500 Subject: [PATCH 0064/1357] perf: check for the settings tab existence in screen params and early return --- src/components/withCurrentReportID.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 22f68de9f57a..55b542ccacb7 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -4,7 +4,6 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {createContext, forwardRef, useCallback, useMemo, useState} from 'react'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import Navigation from '@libs/Navigation/Navigation'; -import SCREENS from '@src/SCREENS'; type CurrentReportIDContextValue = { updateCurrentReportID: (state: NavigationState) => void; @@ -44,11 +43,14 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro /** * This is to make sure we don't set the undefined as reportID when - * switching between chat list and settings->workspaces tab. - * and doing so avoids an unnecessary re-render of `useReportIDs`. + * switching between chat list and settings tab. The settings tab + * includes multiple screens and we don't want to set the reportID + * to falsy value when switching between them. + * + * Doing so avoids an unnecessary re-render of `useReportIDs`. */ const params = state?.routes?.[state.index]?.params; - if (params && 'screen' in params && params.screen === SCREENS.SETTINGS.WORKSPACES) { + if (params && 'screen' in params && typeof params.screen === 'string' && params.screen.indexOf('Settings_') !== -1) { return; } setCurrentReportID(reportID); From eb3aff8ee97f1f055d22376ea7004ba575ff1cb9 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 14 Mar 2024 19:15:31 +0100 Subject: [PATCH 0065/1357] feat: add rate field to MoneyRequestView --- .../ReportActionItem/MoneyRequestView.tsx | 68 +++++++++++++++---- src/libs/DistanceRequestUtils.ts | 17 ++--- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 70c65d1d66ce..bcaa7f830925 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -21,6 +21,7 @@ import type {ViolationField} from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; @@ -89,8 +90,8 @@ function MoneyRequestView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); - const {canUseViolations} = usePermissions(); + const {translate, toLocaleDigit} = useLocalize(); + const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(); const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null; const moneyRequestReport = parentReport; const { @@ -151,6 +152,20 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; + const hasRoute = TransactionUtils.hasRoute(transaction); + const distance = transaction?.routes?.route0?.distance ?? 0; + const rateID = transaction?.comment.customUnit?.customUnitRateID ?? '0'; + + const rates = DistanceRequestUtils.getMileageRates(policy?.id); + const {unit, currency, rate} = rates[rateID as string] ?? { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + currency: CONST.CURRENCY.USD, + rate: 0, + }; + + const rateToDisplay = DistanceRequestUtils.getRateForDisplay(hasRoute, unit, rate, currency, translate, toLocaleDigit); + const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); + const saveBillable = useCallback( (newBillable: boolean) => { // If the value hasn't changed, don't request to save changes on the server and just close the modal @@ -236,6 +251,44 @@ function MoneyRequestView({ [transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], ); + const distanceRequestFields = canUseP2PDistanceRequests ? ( + <> + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + + {}} + /> + + + ) : ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + ) + return ( @@ -320,16 +373,7 @@ function MoneyRequestView({ /> {isDistanceRequest ? ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - + distanceRequestFields ) : ( { - const mileageRates = {}; + const mileageRates: Record = {}; if (!policyID) { return mileageRates; @@ -202,7 +201,6 @@ function getMileageRates(policyID?: string): Record } Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { - // TODO: fix TS error mileageRates[rateID] = { rate: rate.rate, currency: rate.currency, @@ -215,8 +213,7 @@ function getMileageRates(policyID?: string): Record return mileageRates; } -// TODO: probably will need to be changed -function getRateForP2P(currency) { +function getRateForP2P(currency: string) { return CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; } From 8dba89bbcad90b6e13b9f55732376d8fe24fee4a Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 15 Mar 2024 13:29:59 +0500 Subject: [PATCH 0066/1357] feat: add selector from SidebarLinksData --- src/hooks/useReportIDs.tsx | 114 +++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 918a459e300a..f9b59be8e14c 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -3,21 +3,37 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; +import type {PolicySelector} from '@pages/home/sidebar/SidebarLinksData'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; type OnyxProps = { - chatReports: OnyxCollection; - betas: OnyxEntry; - policies: OnyxCollection; - allReportActions: OnyxCollection; - transactionViolations: OnyxCollection; - policyMembers: OnyxCollection; - priorityMode: PriorityMode; + /** List of reports */ + chatReports: OnyxCollection; + + /** Beta features list */ + betas: OnyxEntry; + + /** The policies which the user has access to */ + policies: OnyxCollection; + + /** All report actions for all reports */ + allReportActions: OnyxCollection; + + /** All of the transaction violations */ + transactionViolations: OnyxCollection; + + /** All policy members */ + policyMembers: OnyxCollection; + + /** The chat priority mode */ + priorityMode: OnyxTypes.PriorityMode; }; type WithReportIDsContextProviderProps = OnyxProps & { @@ -63,10 +79,10 @@ function WithReportIDsContextProvider({ SidebarUtils.getOrderedReportIDs( currentReportID ?? null, chatReports, - betas ?? [], - policies, + betas, + policies as OnyxCollection, priorityMode, - allReportActions, + allReportActions as OnyxCollection, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -98,10 +114,84 @@ function WithReportIDsContextProvider({ return {children}; } +type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; +type ReportActionsSelector = Array>; + +/** + * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering + * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. + */ +const chatReportSelector = (report: OnyxEntry): ChatReportSelector => + (report && { + reportID: report.reportID, + participantAccountIDs: report.participantAccountIDs, + hasDraft: report.hasDraft, + isPinned: report.isPinned, + isHidden: report.isHidden, + notificationPreference: report.notificationPreference, + errorFields: { + addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, + }, + lastMessageText: report.lastMessageText, + lastVisibleActionCreated: report.lastVisibleActionCreated, + iouReportID: report.iouReportID, + total: report.total, + nonReimbursableTotal: report.nonReimbursableTotal, + hasOutstandingChildRequest: report.hasOutstandingChildRequest, + isWaitingOnBankAccount: report.isWaitingOnBankAccount, + statusNum: report.statusNum, + stateNum: report.stateNum, + chatType: report.chatType, + type: report.type, + policyID: report.policyID, + visibility: report.visibility, + lastReadTime: report.lastReadTime, + // Needed for name sorting: + reportName: report.reportName, + policyName: report.policyName, + oldPolicyName: report.oldPolicyName, + // Other less obvious properites considered for sorting: + ownerAccountID: report.ownerAccountID, + currency: report.currency, + managerID: report.managerID, + // Other important less obivous properties for filtering: + parentReportActionID: report.parentReportActionID, + parentReportID: report.parentReportID, + isDeletedParentAction: report.isDeletedParentAction, + isUnreadWithMention: ReportUtils.isUnreadWithMention(report), + }) as ChatReportSelector; + +const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => + (reportActions && + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors = [], originalMessage} = reportAction; + const decision = reportAction.message?.[0].moderationDecision?.decision; + + return { + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + }, + ] as Message[], + originalMessage, + }; + })) as ReportActionsSelector; + +const policySelector = (policy: OnyxEntry): PolicySelector => + (policy && { + type: policy.type, + name: policy.name, + avatar: policy.avatar, + }) as PolicySelector; + const ReportIDsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, initialValue: {}, + selector: chatReportSelector, }, priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, @@ -114,10 +204,12 @@ const ReportIDsContextProvider = withOnyx Date: Fri, 15 Mar 2024 16:39:49 +0700 Subject: [PATCH 0067/1357] fix the suggestion padding for edit composer --- src/CONST.ts | 1 + .../BaseAutoCompleteSuggestions.tsx | 4 +++- .../ComposerWithSuggestionsEdit/SuggestionsContext.tsx | 10 ++++++++-- src/pages/home/report/ReportActionItemMessageEdit.tsx | 5 ++++- src/styles/utils/index.ts | 5 +++-- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa44cda20720..68fb72541845 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1065,6 +1065,7 @@ const CONST = { EMOJI_PICKER_HEADER_HEIGHT: 32, RECIPIENT_LOCAL_TIME_HEIGHT: 25, AUTO_COMPLETE_SUGGESTER: { + EDIT_SUGGESTER_PADDING: 8, SUGGESTER_PADDING: 6, SUGGESTER_INNER_PADDING: 8, SUGGESTION_ROW_HEIGHT: 40, diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 6c65afa9c2a8..a11fb2a01cc8 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -10,6 +10,7 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {useSuggestionsContext} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import viewForwardedRef from '@src/types/utils/viewForwardedRef'; @@ -48,6 +49,7 @@ function BaseAutoCompleteSuggestions( const StyleUtils = useStyleUtils(); const rowHeight = useSharedValue(0); const scrollRef = useRef>(null); + const {activeID} = useSuggestionsContext(); /** * Render a suggestion menu item component. */ @@ -69,7 +71,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer)); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer, Boolean(activeID))); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx index 9118e80ef230..36ed962010ab 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx @@ -7,15 +7,19 @@ type SuggestionsContextProviderProps = { }; type SuggestionsContextProps = { + activeID: string | null; currentActiveSuggestionsRef: MutableRefObject; updateCurrentActiveSuggestionsRef: (ref: SuggestionsRef | null, id: string) => void; clearActiveSuggestionsRef: () => void; + isActiveSuggestions: (id: string) => boolean; }; const SuggestionsContext = createContext({ + activeID: null, currentActiveSuggestionsRef: {current: null}, updateCurrentActiveSuggestionsRef: () => {}, clearActiveSuggestionsRef: () => {}, + isActiveSuggestions: () => false, }); function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) { @@ -32,9 +36,11 @@ function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) setActiveID(null); }, []); + const isActiveSuggestions = useCallback((id: string) => id === activeID, [activeID]); + const contextValue = useMemo( - () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef}), - [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef], + () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions}), + [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions], ); return {children}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 858f33a5a8dc..294f7e83d66e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -83,7 +83,7 @@ function ReportActionItemMessageEdit( const {translate, preferredLocale} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); const {isSmallScreenWidth} = useWindowDimensions(); - const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef} = useSuggestionsContext(); + const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions} = useSuggestionsContext(); const getInitialDraft = () => { if (draftMessage === action?.message?.[0].html) { @@ -212,6 +212,9 @@ function ReportActionItemMessageEdit( if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } + if (isActiveSuggestions(action.reportActionID)) { + clearActiveSuggestionsRef(); + } // Show the main composer when the focused message is deleted from another client // to prevent the main composer stays hidden until we swtich to another chat. diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 2a05278dec6c..1fde098c4aeb 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -844,17 +844,18 @@ const shouldPreventScroll = shouldPreventScrollOnAutoCompleteSuggestion(); /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean, isEditComposer: boolean): ViewStyle { 'worklet'; const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING + (shouldPreventScroll ? borderWidth : 0); + const suggestionsPadding = isEditComposer ? CONST.AUTO_COMPLETE_SUGGESTER.EDIT_SUGGESTER_PADDING : CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING; // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + (shouldPreventScroll ? 0 : borderWidth))), + top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (suggestionsPadding + (shouldPreventScroll ? 0 : borderWidth))), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From f333232781b6b64566801dc1958c05284c90081d Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 15 Mar 2024 18:40:10 +0500 Subject: [PATCH 0068/1357] fix: reassure tests --- tests/perf-test/SidebarUtils.perf-test.ts | 26 +++++---------------- tests/utils/collections/createCollection.ts | 23 ++++++++++++++++++ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 8566abb97c7f..4bd04e823f63 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -9,7 +9,7 @@ import type {PersonalDetails, TransactionViolation} from '@src/types/onyx'; import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; -import createCollection from '../utils/collections/createCollection'; +import createCollection, {createNestedCollection} from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; import createRandomReportAction, {getRandomDate} from '../utils/collections/reportActions'; @@ -51,25 +51,11 @@ const policies = createCollection( const mockedBetas = Object.values(CONST.BETAS); -const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${key}`, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, - }, - }, - ], - reportActionID: reportActions[key].reportActionID, - }, - ], - ]), -) as unknown as OnyxCollection; - +const allReportActions = createNestedCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (item) => `${item.reportActionID}`, + (index) => createRandomReportAction(index), +); const currentReportId = '1'; const transactionViolations = {} as OnyxCollection; diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts index 848ef8f81f47..4a2a1fa4eb78 100644 --- a/tests/utils/collections/createCollection.ts +++ b/tests/utils/collections/createCollection.ts @@ -9,3 +9,26 @@ export default function createCollection(createKey: (item: T, index: number) return map; } + +function createNestedCollection( + createParentKey: (item: T, index: number) => string | number, + createKey: (item: T, index: number) => string | number, + createItem: (index: number) => T, + length = 500, +): Record> { + const map: Record> = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + const itemParentKey = createParentKey(item, i); + map[itemParentKey] = { + ...map[itemParentKey], + [itemKey]: item, + }; + } + + return map; +} + +export {createNestedCollection}; From 3557027d7ccadd1dfa89cb3d2042e1c5ae927523 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Fri, 15 Mar 2024 18:40:36 +0500 Subject: [PATCH 0069/1357] fix: resolve merge conflicts --- src/hooks/useReportIDs.tsx | 114 ++++--------------------------------- 1 file changed, 11 insertions(+), 103 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index f9b59be8e14c..422febfca140 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -3,37 +3,21 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {getCurrentUserAccountID} from '@libs/actions/Report'; import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; -import type {PolicySelector} from '@pages/home/sidebar/SidebarLinksData'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {Message} from '@src/types/onyx/ReportAction'; +import type {Beta, Policy, PolicyMembers, PriorityMode, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; import useActiveWorkspace from './useActiveWorkspace'; import useCurrentReportID from './useCurrentReportID'; type OnyxProps = { - /** List of reports */ - chatReports: OnyxCollection; - - /** Beta features list */ - betas: OnyxEntry; - - /** The policies which the user has access to */ - policies: OnyxCollection; - - /** All report actions for all reports */ - allReportActions: OnyxCollection; - - /** All of the transaction violations */ - transactionViolations: OnyxCollection; - - /** All policy members */ - policyMembers: OnyxCollection; - - /** The chat priority mode */ - priorityMode: OnyxTypes.PriorityMode; + chatReports: OnyxCollection; + betas: OnyxEntry; + policies: OnyxCollection; + allReportActions: OnyxCollection; + transactionViolations: OnyxCollection; + policyMembers: OnyxCollection; + priorityMode: OnyxEntry; }; type WithReportIDsContextProviderProps = OnyxProps & { @@ -79,10 +63,10 @@ function WithReportIDsContextProvider({ SidebarUtils.getOrderedReportIDs( currentReportID ?? null, chatReports, - betas, - policies as OnyxCollection, + betas ?? [], + policies, priorityMode, - allReportActions as OnyxCollection, + allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, @@ -114,84 +98,10 @@ function WithReportIDsContextProvider({ return {children}; } -type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; -type ReportActionsSelector = Array>; - -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - */ -const chatReportSelector = (report: OnyxEntry): ChatReportSelector => - (report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, - }, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }) as ChatReportSelector; - -const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => - (reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors = [], originalMessage} = reportAction; - const decision = reportAction.message?.[0].moderationDecision?.decision; - - return { - reportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ] as Message[], - originalMessage, - }; - })) as ReportActionsSelector; - -const policySelector = (policy: OnyxEntry): PolicySelector => - (policy && { - type: policy.type, - name: policy.name, - avatar: policy.avatar, - }) as PolicySelector; - const ReportIDsContextProvider = withOnyx({ chatReports: { key: ONYXKEYS.COLLECTION.REPORT, initialValue: {}, - selector: chatReportSelector, }, priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, @@ -204,12 +114,10 @@ const ReportIDsContextProvider = withOnyx Date: Mon, 18 Mar 2024 19:57:19 -0300 Subject: [PATCH 0070/1357] feat: change customRateID when participant changes, other improvements --- ...oraryForRefactorRequestConfirmationList.js | 25 ++++--- .../ReportActionItem/MoneyRequestView.tsx | 72 +++++++++---------- src/libs/DistanceRequestUtils.ts | 3 +- src/libs/TransactionUtils.ts | 9 +++ src/libs/actions/IOU.ts | 6 +- ...yForRefactorRequestParticipantsSelector.js | 2 +- .../step/IOURequestStepParticipants.js | 35 ++++++++- .../iou/request/step/IOURequestStepRate.tsx | 28 +++++--- 8 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 93831606416e..ce5668924271 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -224,7 +224,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ isReadOnly, isScanRequest, listStyles, - mileageRate, + mileageRates, onConfirm, onSelectParticipant, onSendMoney, @@ -242,7 +242,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, - mileageRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -254,14 +253,19 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isTypeSend = iouType === CONST.IOU.TYPE.SEND; const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit); - // TODO: uncomment after Splits and P2P are enabled https://github.com/Expensify/App/pull/37185, mileageRate prop should be be removed - // const mileageRate = transaction.comment.customUnit.customUnitRateID === '_FAKE_P2P_ID_' ? DistanceRequestUtils.getRateForP2P(policy.outputCurrency) : mileageRates[transaction.comment.customUnit.customUnitRateID]; - const {unit, rate, currency} = mileageRate; + const personalPolicy = policyID === CONST.POLICY.ID_FAKE ? PolicyUtils.getPersonalPolicy() : policy; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(personalPolicy.outputCurrency) + : mileageRates[transaction.comment.customUnit.customUnitRateID]; - // will hardcoded rates will have a currency property? If not we have to get currency other way + const {unit, rate} = mileageRate || { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * 100, + }; + + const currency = personalPolicy.outputCurrency; const distance = lodashGet(transaction, 'routes.route0.distance', 0); - const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; const taxRates = lodashGet(policy, 'taxRates', {}); // A flag for showing the categories field @@ -292,7 +296,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const formattedAmount = isDistanceRequestWithPendingRoute ? '' : CurrencyUtils.convertToDisplayString( - shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, + isDistanceRequest && iouAmount === 0 ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); @@ -359,13 +363,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit, isMerchantRequired, merchantError]); useEffect(() => { - if (!shouldCalculateDistanceAmount) { + if (!isDistanceRequest) { return; } const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate); IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency); - }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); + }, [isDistanceRequest, distance, rate, unit, transaction, currency]); /** * Returns the participants with amount @@ -757,7 +761,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ item: ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - - - {}} - /> - - - ) : ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} - /> - - ) + <> + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + + {}} + /> + + + ) : ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + /> + + ); return ( diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e954ce4d266d..28714a92ed84 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -91,8 +91,7 @@ function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { */ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - // TODO: add logic for currencies for which we need to round to 4 decimals - return convertedDistance.toFixed(3); + return convertedDistance.toFixed(2); } // TODO: I wonder if it would be better to refactor these functions to pass params in an object diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index bc94c8fee8fc..f694199fbeb4 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -584,6 +584,14 @@ function getEnabledTaxRateCount(options: TaxRates) { return Object.values(options).filter((option: TaxRate) => !option.isDisabled).length; } +/** + * Check if the customUnitRateID has a value default for P2P distance requests + */ + +function isCustomUnitRateIDForP2P(transaction: Transaction): boolean { + return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; +} + export { buildOptimisticTransaction, calculateTaxAmount, @@ -633,6 +641,7 @@ export { waypointHasValidAddress, getRecentTransactions, hasViolation, + isCustomUnitRateIDForP2P, }; export type {TransactionChanges}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 477fb99900b8..31cd49269e2c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -253,8 +253,9 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry, waypoint1: {}, }; const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; - if (ReportUtils.isPolicyExpenseChat(report)) { + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? ''; } comment.customUnit = {customUnitRateID}; @@ -373,9 +374,8 @@ function setMoneyRequestReceipt(transactionID: string, source: string, filename: } /** Set the last selected distance rate for policy */ -// TODO: probably need to be changed function setLastSelectedDistanceRates(policyID: string, rateID: string) { - Onyx.merge('lastSelectedDistanceRates', {[policyID]: rateID}); + Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policyID]: rateID}); } /** Update transaction distance rate */ diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index cdc3e72f98f4..7bdcafef61e6 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -190,7 +190,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const addSingleParticipant = (option) => { onParticipantsAdded([ { - ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), + ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText', 'policyID'), selected: true, }, ]); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 5ca465d8fb78..8c92961a28c4 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -1,16 +1,21 @@ import {useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; import StepScreenWrapper from './StepScreenWrapper'; @@ -24,6 +29,9 @@ const propTypes = { /* Onyx Props */ /** The transaction object being modified in Onyx */ transaction: transactionPropTypes, + + // eslint-disable-next-line + lastSelectedDistanceRates: PropTypes.object, }; const defaultProps = { @@ -36,6 +44,7 @@ function IOURequestStepParticipants({ }, transaction, transaction: {participants = []}, + lastSelectedDistanceRates = {}, }) { const {translate} = useLocalize(); const navigation = useNavigation(); @@ -100,6 +109,22 @@ function IOURequestStepParticipants({ } IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); + + // change customUnitRateID when choosing + if (val[0].isPolicyExpenseChat && TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + let customUnitRateID = ''; + if (val[0].policyID && lastSelectedDistanceRates[val[0].policyID]) { + customUnitRateID = lastSelectedDistanceRates[val[0].policyID]; + } else { + const policy = ReportUtils.getPolicy(val[0].policyID); + const defaultRate = DistanceRequestUtils.getDefaultMileageRate(policy); + customUnitRateID = defaultRate ? defaultRate.customUnitRateID : ''; + } + IOU.updateDistanceRequestRate(transactionID, customUnitRateID); + } else if (!TransactionUtils.isCustomUnitRateIDForP2P(transaction)) { + IOU.updateDistanceRequestRate(transactionID, CONST.CUSTOM_UNITS.FAKE_P2P_ID); + } + numberOfParticipants.current = val.length; // When multiple participants are selected, the reportID is generated at the end of the confirmation step. @@ -163,4 +188,12 @@ IOURequestStepParticipants.displayName = 'IOURequestStepParticipants'; IOURequestStepParticipants.propTypes = propTypes; IOURequestStepParticipants.defaultProps = defaultProps; -export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepParticipants); +export default compose( + withWritableReportOrNotFound, + withFullTransactionOrNotFound, + withOnyx({ + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, + }), +)(IOURequestStepParticipants); diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index a2cf7a0af1c8..10513f7a44ca 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -6,19 +6,21 @@ import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as IOU from '@libs/actions/IOU'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; + type Props = { - // eslint-disable-next-line react/no-unused-prop-types - lastSelectedDistanceRate: string; + /** Object of last selected rates for the policies */ + lastSelectedDistanceRates: Record; /** Policy details */ policy: OnyxEntry; @@ -38,11 +40,14 @@ function IOURequestStepRate({ route: { params: {backTo}, }, + lastSelectedDistanceRates = {}, }: Props) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const rates = DistanceRequestUtils.getMileageRates(policy?.id); + const lastSelectedRate = lastSelectedDistanceRates[policy?.id ?? '0'] ?? '0'; + const data = Object.values(rates).map((rate) => ({ text: rate.name ?? '', alternateText: DistanceRequestUtils.getRateForDisplay(true, rate.unit, rate.rate, rate.currency, translate, toLocaleDigit), @@ -50,9 +55,10 @@ function IOURequestStepRate({ value: rate.customUnitRateID, })); - const selectDistanceRate = (customUnitRateID) => { + const initiallyFocusedOption = rates[lastSelectedRate]?.name ?? CONST.CUSTOM_UNITS.DEFAULT_RATE; + + function selectDistanceRate(customUnitRateID = '0') { IOU.setLastSelectedDistanceRates(policy?.id ?? '', customUnitRateID); - // TODO: get a proper transaction ID IOU.updateDistanceRequestRate('1', customUnitRateID); Navigation.goBack(backTo); } @@ -70,8 +76,7 @@ function IOURequestStepRate({ sections={[{data}]} ListItem={RadioListItem} onSelectRow={({value}) => selectDistanceRate(value)} - // TODO: change for lastSelectedDistanceRates - initiallyFocusedOptionKey="Default Rate" + initiallyFocusedOptionKey={initiallyFocusedOption} /> ); @@ -82,11 +87,14 @@ IOURequestStepRate.displayName = 'IOURequestStepRate'; export default compose( withWritableReportOrNotFound, withOnyx({ + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS policy: { + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, - // lastSelectedDistanceRates: { - // key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, - // }, + // @ts-expect-error TODO: fix when withWritableReportOrNotFound will be migrated to TS + lastSelectedDistanceRates: { + key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, + }, }), )(IOURequestStepRate); From 07db2abc4a3a1d5e6d64ce12cc0ec21274946d96 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 19 Mar 2024 17:48:30 +0700 Subject: [PATCH 0071/1357] update variable name --- src/components/AutoCompleteSuggestions/index.tsx | 14 +++++++------- .../home/report/ReportActionItemMessageEdit.tsx | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 1f5d87b15962..66ea0de6f9f3 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -19,13 +19,13 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); - const suggestionContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); + const suggestionsContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, bottom: 0, }); - const [shouldBelowContainer, setShouldBelowContainer] = React.useState(false); + const [shouldShowBelowContainer, setShouldShowBelowContainer] = React.useState(false); React.useEffect(() => { const container = containerRef.current; if (!container) { @@ -46,17 +46,17 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} } measureParentContainer((x, y, w, h) => { - const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; - setShouldBelowContainer(y < suggestionContainerHeight); - setContainerState({left: x, bottom: currenBottom, width: w}); + const currentBottom = y < suggestionsContainerHeight ? windowHeight - y - suggestionsContainerHeight - h : windowHeight - y; + setShouldShowBelowContainer(y < suggestionsContainerHeight); + setContainerState({left: x, bottom: currentBottom, width: w}); }); - }, [measureParentContainer, windowHeight, windowWidth, suggestionContainerHeight]); + }, [measureParentContainer, windowHeight, windowWidth, suggestionsContainerHeight]); const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading {...props} - shouldBeDisplayedBelowParentContainer={shouldBelowContainer} + shouldBeDisplayedBelowParentContainer={shouldShowBelowContainer} ref={containerRef} /> ); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 294f7e83d66e..85f206cb94b0 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -392,7 +392,6 @@ function ReportActionItemMessageEdit( } containerRef.current.measureInWindow(callback); }, - // We added isComposerFullSize in dependencies so that when this value changes, we recalculate the position of the popup // eslint-disable-next-line react-hooks/exhaustive-deps [], ); From a0227e07854c76bbc9039d5df2971de4c5294a5f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 19 Mar 2024 09:42:20 -0300 Subject: [PATCH 0072/1357] fix: run prettier --- src/pages/iou/request/step/IOURequestStepRate.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 10513f7a44ca..583fa8c055c7 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -17,7 +17,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; - type Props = { /** Object of last selected rates for the policies */ lastSelectedDistanceRates: Record; From eb350eec27ba9858929864cdc29aada4453b88e1 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 19 Mar 2024 09:53:29 -0300 Subject: [PATCH 0073/1357] fix: lint fix --- src/pages/iou/request/step/IOURequestStepParticipants.js | 2 +- src/pages/iou/request/step/IOURequestStepRate.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 8c92961a28c4..a66387709f61 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -137,7 +137,7 @@ function IOURequestStepParticipants({ // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. selectedReportID.current = lodashGet(val, '[0].reportID', reportID); }, - [reportID, transactionID, iouType, participants, updateRouteParams], + [iouType, participants, transactionID, transaction, reportID, updateRouteParams, lastSelectedDistanceRates], ); const goToNextStep = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepRate.tsx b/src/pages/iou/request/step/IOURequestStepRate.tsx index 583fa8c055c7..de570ca3c67f 100644 --- a/src/pages/iou/request/step/IOURequestStepRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepRate.tsx @@ -10,12 +10,12 @@ import * as IOU from '@libs/actions/IOU'; import compose from '@libs/compose'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; -import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import type {Policy} from '@src/types/onyx'; +import StepScreenWrapper from './StepScreenWrapper'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; type Props = { /** Object of last selected rates for the policies */ From 54d0bb9d340926b8997c8b1d584321554d49ecec Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 20 Mar 2024 12:08:37 +0700 Subject: [PATCH 0074/1357] fix lint --- src/components/Image/index.tsx | 36 +++++++++++++++++++-- src/components/Image/types.ts | 7 ++-- src/components/ImageWithSizeCalculation.tsx | 5 +-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index f4e5bf4834e0..c8c78b54de06 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -1,11 +1,39 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import BaseImage from './BaseImage'; -import type {ImageOnyxProps, ImageOwnProps, ImageProps} from './types'; +import type {ImageOnLoadEvent, ImageOnyxProps, ImageOwnProps, ImageProps} from './types'; -function Image({source: propsSource, isAuthTokenRequired = false, session, ...forwardedProps}: ImageProps) { +function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPositionTop, style, ...forwardedProps}: ImageProps) { + const [aspectRatio, setAspectRatio] = useState(null); + + const updateAspectRatio = useCallback( + (width: number, height: number) => { + if (!objectPositionTop) { + return; + } + + if (width > height) { + setAspectRatio(1); + return; + } + + setAspectRatio(height ? width / height : 'auto'); + }, + [objectPositionTop], + ); + + const handleLoad = useCallback( + (event: ImageOnLoadEvent) => { + const {width, height} = event.nativeEvent; + + onLoad?.(event); + + updateAspectRatio(width, height); + }, + [onLoad, updateAspectRatio], + ); /** * Check if the image source is a URL - if so the `encryptedAuthToken` is appended * to the source. @@ -34,6 +62,8 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, ...fo ); diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts index 2a5fcbb19324..c2f73e74ee34 100644 --- a/src/components/Image/types.ts +++ b/src/components/Image/types.ts @@ -23,12 +23,12 @@ type BaseImageProps = { /** Event for when the image is fully loaded and returns the natural dimensions of the image */ onLoad?: (event: ImageOnLoadEvent) => void; -}; -type ImageOwnProps = BaseImageProps & { /** Styles for the Image */ style?: StyleProp; +}; +type ImageOwnProps = BaseImageProps & { /** Should an auth token be included in the image request */ isAuthTokenRequired?: boolean; @@ -46,6 +46,9 @@ type ImageOwnProps = BaseImageProps & { /** Progress events while the image is downloading */ onProgress?: () => void; + + /** Whether we should show the top of the image */ + objectPositionTop?: boolean; }; type ImageProps = ImageOnyxProps & ImageOwnProps; diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index 46e1308692ba..ffa9517299cc 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -33,7 +33,8 @@ type ImageWithSizeCalculationProps = { /** Whether the image requires an authToken */ isAuthTokenRequired: boolean; - objectPositionTop: boolean; + /** Whether we should show the top of the image */ + objectPositionTop?: boolean; }; /** @@ -42,7 +43,7 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPositionTop}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPositionTop = false}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const isLoadedRef = useRef(null); const [isImageCached, setIsImageCached] = useState(true); From c093a99e4fcbf720603f836be8cadca5d71217c1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 20 Mar 2024 14:54:49 +0700 Subject: [PATCH 0075/1357] fix portal on native --- src/components/AutoCompleteSuggestions/index.native.tsx | 4 +++- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/AutoCompleteSuggestions/index.native.tsx b/src/components/AutoCompleteSuggestions/index.native.tsx index fbfa7d953581..526954d370e3 100644 --- a/src/components/AutoCompleteSuggestions/index.native.tsx +++ b/src/components/AutoCompleteSuggestions/index.native.tsx @@ -1,11 +1,13 @@ import {Portal} from '@gorhom/portal'; import React from 'react'; +import {useSuggestionsContext} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; function AutoCompleteSuggestions({measureParentContainer, ...props}: AutoCompleteSuggestionsProps) { + const {activeID} = useSuggestionsContext(); return ( - + {/* eslint-disable-next-line react/jsx-props-no-spreading */} {...props} /> diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 1e0e322be258..4c767eeb7966 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -364,7 +364,7 @@ function ReportActionCompose({ {shouldShowReportRecipientLocalTime && hasReportRecipient && } - + - + Date: Wed, 20 Mar 2024 15:02:00 +0700 Subject: [PATCH 0076/1357] rename --- .../ComposerWithSuggestionsEdit/SuggestionsContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx index 36ed962010ab..ceecb56af450 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx @@ -51,6 +51,6 @@ function useSuggestionsContext() { return context; } -SuggestionsContextProvider.displayName = 'PlaybackContextProvider'; +SuggestionsContextProvider.displayName = 'SuggestionsContextProvider'; export {SuggestionsContextProvider, useSuggestionsContext}; From 61ef58859587485c314e8a97d7116c8e2f75373f Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 15:13:38 +0500 Subject: [PATCH 0077/1357] refactor: use currentReportID from useReportIDs --- src/hooks/useReportIDs.tsx | 5 ++++- src/pages/home/sidebar/SidebarLinksData.js | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 422febfca140..bdb9bbf9ada3 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -27,10 +27,12 @@ type WithReportIDsContextProviderProps = OnyxProps & { type ReportIDsContextValue = { orderedReportIDs: string[]; + currentReportID: string; }; const ReportIDsContext = createContext({ orderedReportIDs: [], + currentReportID: '', }); function WithReportIDsContextProvider({ @@ -91,8 +93,9 @@ function WithReportIDsContextProvider({ const contextValue = useMemo( () => ({ orderedReportIDs: orderedReportIDsWithCurrentReport, + currentReportID: derivedCurrentReportID ?? '', }), - [orderedReportIDsWithCurrentReport], + [orderedReportIDsWithCurrentReport, derivedCurrentReportID], ); return {children}; diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 35c4a2c59cd2..1f4a8cf343d4 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -5,7 +5,6 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; -import withCurrentReportID from '@components/withCurrentReportID'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; @@ -44,7 +43,7 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; -function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, currentUserPersonalDetails}) { +function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, currentUserPersonalDetails}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); @@ -57,7 +56,7 @@ function SidebarLinksData({isFocused, currentReportID, insets, isLoadingApp, onL const orderedReportIDsRef = useRef(null); const isLoading = isLoadingApp; - const {orderedReportIDs} = useReportIDs(); + const {orderedReportIDs, currentReportID} = useReportIDs(); const optionListItems = useMemo(() => { if (deepEqual(orderedReportIDsRef.current, orderedReportIDs)) { @@ -103,7 +102,6 @@ SidebarLinksData.defaultProps = defaultProps; SidebarLinksData.displayName = 'SidebarLinksData'; export default compose( - withCurrentReportID, withCurrentUserPersonalDetails, withNavigationFocus, withNetwork(), From 5b1c8835707f9c7c8daf6ed6283a2e97f6d75707 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 17:33:55 +0700 Subject: [PATCH 0078/1357] replace objectPositionTop with objectPosition --- src/CONST.ts | 5 +++++ src/components/DistanceEReceipt.tsx | 2 +- src/components/Image/index.tsx | 9 +++++---- src/components/Image/types.ts | 10 +++++++--- src/components/ImageWithSizeCalculation.tsx | 10 ++++++---- src/components/MoneyRequestConfirmationList.tsx | 2 +- ...MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- .../ReportActionItem/ReportActionItemImage.tsx | 4 ++-- src/components/ThumbnailImage.tsx | 10 +++++++--- 9 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index bb191ac5e028..3cd473f2f451 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1095,6 +1095,11 @@ const CONST = { JPEG: 'image/jpeg', }, + IMAGE_OBJECT_POSITION: { + TOP: 'top', + INITIAL: 'initial', + }, + FILE_TYPE_REGEX: { // Image MimeTypes allowed by iOS photos app. IMAGE: /\.(jpg|jpeg|png|webp|gif|tiff|bmp|heic|heif)$/, diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 864dc831f74c..03ae1cdef5ad 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -63,7 +63,7 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) { style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} - objectPositionTop + objectPosition /> )} diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index c8c78b54de06..e6cecdc0d5ec 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -5,12 +5,13 @@ import ONYXKEYS from '@src/ONYXKEYS'; import BaseImage from './BaseImage'; import type {ImageOnLoadEvent, ImageOnyxProps, ImageOwnProps, ImageProps} from './types'; -function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPositionTop, style, ...forwardedProps}: ImageProps) { +function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, style, ...forwardedProps}: ImageProps) { const [aspectRatio, setAspectRatio] = useState(null); + const isObjectPositionTop = objectPosition === CONST.IMAGE_OBJECT_POSITION.TOP; const updateAspectRatio = useCallback( (width: number, height: number) => { - if (!objectPositionTop) { + if (!isObjectPositionTop) { return; } @@ -21,7 +22,7 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, onLoa setAspectRatio(height ? width / height : 'auto'); }, - [objectPositionTop], + [isObjectPositionTop], ); const handleLoad = useCallback( @@ -63,7 +64,7 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, onLoa // eslint-disable-next-line react/jsx-props-no-spreading {...forwardedProps} onLoad={handleLoad} - style={[style, aspectRatio ? {aspectRatio, height: 'auto'} : {}, objectPositionTop && !aspectRatio && {opacity: 0}]} + style={[style, aspectRatio ? {aspectRatio, height: 'auto'} : {}, isObjectPositionTop && !aspectRatio && {opacity: 0}]} source={source} /> ); diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts index c2f73e74ee34..27964d8a6764 100644 --- a/src/components/Image/types.ts +++ b/src/components/Image/types.ts @@ -1,10 +1,14 @@ import type {ImageSource} from 'expo-image'; import type {ImageRequireSource, ImageResizeMode, ImageStyle, ImageURISource, StyleProp} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; import type {Session} from '@src/types/onyx'; type ExpoImageSource = ImageSource | number | ImageSource[]; +type ImageObjectPosition = ValueOf; + type ImageOnyxProps = { /** Session info for the currently logged in user. */ session: OnyxEntry; @@ -47,10 +51,10 @@ type ImageOwnProps = BaseImageProps & { /** Progress events while the image is downloading */ onProgress?: () => void; - /** Whether we should show the top of the image */ - objectPositionTop?: boolean; + /** The object position of image */ + objectPosition?: ImageObjectPosition; }; type ImageProps = ImageOnyxProps & ImageOwnProps; -export type {BaseImageProps, ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent}; +export type {BaseImageProps, ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent, ImageObjectPosition}; diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index ffa9517299cc..eac5c676370b 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -5,9 +5,11 @@ import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; +import CONST from '@src/CONST'; import FullscreenLoadingIndicator from './FullscreenLoadingIndicator'; import Image from './Image'; import RESIZE_MODES from './Image/resizeModes'; +import type {ImageObjectPosition} from './Image/types'; type OnMeasure = (args: {width: number; height: number}) => void; @@ -33,8 +35,8 @@ type ImageWithSizeCalculationProps = { /** Whether the image requires an authToken */ isAuthTokenRequired: boolean; - /** Whether we should show the top of the image */ - objectPositionTop?: boolean; + /** The object position of image */ + objectPosition?: ImageObjectPosition; }; /** @@ -43,7 +45,7 @@ type ImageWithSizeCalculationProps = { * performing some calculation on a network image after fetching dimensions so * it can be appropriately resized. */ -function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPositionTop = false}: ImageWithSizeCalculationProps) { +function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthTokenRequired, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL}: ImageWithSizeCalculationProps) { const styles = useThemeStyles(); const isLoadedRef = useRef(null); const [isImageCached, setIsImageCached] = useState(true); @@ -104,7 +106,7 @@ function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthT }} onError={onError} onLoad={imageLoadedSuccessfully} - objectPositionTop={objectPositionTop} + objectPosition={objectPosition} /> {isLoading && !isImageCached && } diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b2ea75cae8b0..75d156e58eaa 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -614,7 +614,7 @@ function MoneyRequestConfirmationList({ // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!!receiptThumbnail} - objectPositionTop + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> ) : ( // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 08957c96128a..c32241d92096 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -906,7 +906,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // but we don't need it to load the blob:// or file:// image when starting a money request / split bill // So if we have a thumbnail, it means we're retrieving the image from the server isAuthTokenRequired={!_.isEmpty(receiptThumbnail)} - objectPositionTop + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> ), [receiptFilename, receiptImage, styles, receiptThumbnail, isLocalFile, isAttachmentInvalid], diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index b39cabf4e74e..3c6f6e7e2421 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -85,7 +85,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr fallbackIcon={Expensicons.Receipt} fallbackIconSize={isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge} shouldDynamicallyResize={false} - objectPositionTop + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> ); } else if (isLocalFile && filename && Str.isPDF(filename) && typeof attachmentModalSource === 'string') { @@ -100,7 +100,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr ); } diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index d1bdfdf3ca9f..8b8da81c727a 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -6,9 +6,11 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useThumbnailDimensions from '@hooks/useThumbnailDimensions'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import type {ImageObjectPosition} from './Image/types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; type ThumbnailImageProps = { @@ -35,7 +37,9 @@ type ThumbnailImageProps = { /** Should the image be resized on load or just fit container */ shouldDynamicallyResize?: boolean; - objectPositionTop?: boolean; + + /** The object position of image */ + objectPosition?: ImageObjectPosition; }; type UpdateImageSizeParams = { @@ -52,7 +56,7 @@ function ThumbnailImage({ shouldDynamicallyResize = true, fallbackIcon = Expensicons.Gallery, fallbackIconSize = variables.iconSizeSuperLarge, - objectPositionTop = false, + objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, }: ThumbnailImageProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -104,7 +108,7 @@ function ThumbnailImage({ onMeasure={updateImageSize} onLoadFailure={() => setFailedToLoad(true)} isAuthTokenRequired={isAuthTokenRequired} - objectPositionTop={objectPositionTop} + objectPosition={objectPosition} /> From 115ec923108600f1ad167783d5acb23417effe5c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 17:37:50 +0700 Subject: [PATCH 0079/1357] update objectPosition prop --- src/components/DistanceEReceipt.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 03ae1cdef5ad..3d2a6a3e57b6 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -18,6 +18,7 @@ import PendingMapView from './MapView/PendingMapView'; import ScrollView from './ScrollView'; import Text from './Text'; import ThumbnailImage from './ThumbnailImage'; +import CONST from '@src/CONST'; type DistanceEReceiptProps = { /** The transaction for the distance request */ @@ -63,7 +64,7 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) { style={[styles.w100, styles.h100]} isAuthTokenRequired shouldDynamicallyResize={false} - objectPosition + objectPosition={CONST.IMAGE_OBJECT_POSITION.TOP} /> )} From 1562a88172728730d3ad58cad1abc0e1529f7a28 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 18:05:42 +0700 Subject: [PATCH 0080/1357] fix order import --- src/components/DistanceEReceipt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 3d2a6a3e57b6..2d6e3e56210f 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -8,6 +8,7 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; +import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {Transaction} from '@src/types/onyx'; import type {WaypointCollection} from '@src/types/onyx/Transaction'; @@ -18,7 +19,6 @@ import PendingMapView from './MapView/PendingMapView'; import ScrollView from './ScrollView'; import Text from './Text'; import ThumbnailImage from './ThumbnailImage'; -import CONST from '@src/CONST'; type DistanceEReceiptProps = { /** The transaction for the distance request */ From 84aa5ac37af2faf3eeb547b600627ef9c40fa920 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 16:10:24 +0500 Subject: [PATCH 0081/1357] refactor: remove unnecessary ref --- src/pages/home/sidebar/SidebarLinksData.js | 29 +++------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 1f4a8cf343d4..207eed55586a 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -1,15 +1,11 @@ -import {deepEqual} from 'fast-equals'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import withNavigationFocus from '@components/withNavigationFocus'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; import {useReportIDs} from '@hooks/useReportIDs'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; @@ -28,8 +24,6 @@ const propTypes = { /** The chat priority mode */ priorityMode: PropTypes.string, - network: networkPropTypes.isRequired, - // eslint-disable-next-line react/forbid-prop-types policyMembers: PropTypes.object, @@ -43,35 +37,19 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; -function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorityMode, network, policyMembers, currentUserPersonalDetails}) { +function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorityMode, policyMembers, currentUserPersonalDetails}) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); - const prevPriorityMode = usePrevious(priorityMode); const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, currentUserPersonalDetails.accountID); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]); - const orderedReportIDsRef = useRef(null); const isLoading = isLoadingApp; const {orderedReportIDs, currentReportID} = useReportIDs(); - const optionListItems = useMemo(() => { - if (deepEqual(orderedReportIDsRef.current, orderedReportIDs)) { - return orderedReportIDsRef.current; - } - - // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 - // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. - // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete - if (!isLoading || !orderedReportIDsRef.current || network.isOffline || (orderedReportIDsRef.current && prevPriorityMode !== priorityMode)) { - orderedReportIDsRef.current = orderedReportIDs; - } - return orderedReportIDsRef.current || []; - }, [orderedReportIDs, isLoading, network.isOffline, prevPriorityMode, priorityMode]); - const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; const isActiveReport = useCallback((reportID) => currentReportIDRef.current === reportID, []); @@ -91,7 +69,7 @@ function SidebarLinksData({isFocused, insets, isLoadingApp, onLinkClick, priorit isActiveReport={isActiveReport} isLoading={isLoading} activeWorkspaceID={activeWorkspaceID} - optionListItems={optionListItems} + optionListItems={orderedReportIDs} /> ); @@ -104,7 +82,6 @@ SidebarLinksData.displayName = 'SidebarLinksData'; export default compose( withCurrentUserPersonalDetails, withNavigationFocus, - withNetwork(), withOnyx({ isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, From 8532c0864d9b6a2ffb88ae3759801781d6bf23af Mon Sep 17 00:00:00 2001 From: Muhammad Hur Ali Date: Thu, 21 Mar 2024 16:16:02 +0500 Subject: [PATCH 0082/1357] update comment is updateCurrentReportID function Co-authored-by: Rory Abraham <47436092+roryabraham@users.noreply.github.com> --- src/components/withCurrentReportID.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 55b542ccacb7..27a965a151de 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -42,7 +42,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro const reportID = Navigation.getTopmostReportId(state) ?? ''; /** - * This is to make sure we don't set the undefined as reportID when + * This is to make sure we don't set the reportID as undefined when * switching between chat list and settings tab. The settings tab * includes multiple screens and we don't want to set the reportID * to falsy value when switching between them. From ac465f3976aef5d891f7ba4e7eb4bc2043ca13a3 Mon Sep 17 00:00:00 2001 From: Muhammad Hur Ali Date: Thu, 21 Mar 2024 16:16:46 +0500 Subject: [PATCH 0083/1357] update comment in useReportIDs hook Co-authored-by: Rory Abraham <47436092+roryabraham@users.noreply.github.com> --- src/hooks/useReportIDs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index bdb9bbf9ada3..4d7474905a2e 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -47,7 +47,7 @@ function WithReportIDsContextProvider({ /** * Only required to make unit tests work, since we * explicitly pass the currentReportID in LHNTestUtils - * to SidebarLinksData, so this context doesn't have an + * to SidebarLinksData, so this context doesn't have * access to currentReportID in that case. * * This is a workaround to have currentReportID available in testing environment. From 886622e7940a7b28e11e299d0aa89a6e34030cc6 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 16:20:12 +0500 Subject: [PATCH 0084/1357] refactor: make comment less verbose --- src/components/withCurrentReportID.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index 27a965a151de..a72063913283 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -41,13 +41,9 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro (state: NavigationState) => { const reportID = Navigation.getTopmostReportId(state) ?? ''; - /** - * This is to make sure we don't set the reportID as undefined when - * switching between chat list and settings tab. The settings tab - * includes multiple screens and we don't want to set the reportID - * to falsy value when switching between them. - * - * Doing so avoids an unnecessary re-render of `useReportIDs`. + /* + * Make sure we don't make the reportID undefined when switching between the chat list and settings tab. + * This helps prevent unnecessary re-renders. */ const params = state?.routes?.[state.index]?.params; if (params && 'screen' in params && typeof params.screen === 'string' && params.screen.indexOf('Settings_') !== -1) { From 6d17116c0e2af4fbadbe79b366fb16c7434b51be Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Mar 2024 16:20:46 +0500 Subject: [PATCH 0085/1357] refactor: remove irrelevant comment --- src/components/LHNOptionsList/LHNOptionsList.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c9052567ac36..fa4c89216d08 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -122,12 +122,6 @@ function LHNOptionsList({ keyExtractor={keyExtractor} renderItem={renderItem} estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight} - // Previously, we were passing `extraData={[currentReportID]}`, which upon every render, was causing the - // re-render because of the new array reference. FlashList's children actually don't depend on the - // `currentReportID` prop but they depend on the `reportActions`, `reports`, `policy`, `personalDetails`. - // Previously it was working for us because of the new array reference. Even if you only pass an empty - // array, it will still work because of the new reference. But it's better to pass the actual dependencies - // to avoid unnecessary re-renders. extraData={extraData} showsVerticalScrollIndicator={false} /> From d296c84255fe8a96ff0f43e8b22aff1d95608f08 Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Thu, 21 Mar 2024 18:12:58 +0100 Subject: [PATCH 0086/1357] Turn on Onboarding Flow Stage 1 --- src/ROUTES.ts | 4 - src/SCREENS.ts | 7 - src/languages/en.ts | 22 --- src/languages/es.ts | 22 --- .../AppNavigator/ModalStackNavigators.tsx | 8 - .../Navigators/RightModalNavigator.tsx | 4 - .../BottomTabBar.tsx | 12 +- src/libs/Navigation/linkingConfig/config.ts | 7 - src/libs/Navigation/types.ts | 8 - src/libs/actions/Welcome.ts | 6 +- .../ExpensifyClassicPage.tsx | 75 --------- .../ManageTeamsExpensesPage.tsx | 122 -------------- .../PurposeForUsingExpensifyPage.tsx | 155 ------------------ src/styles/theme/themes/dark.ts | 8 - src/styles/theme/themes/light.ts | 8 - 15 files changed, 2 insertions(+), 466 deletions(-) delete mode 100644 src/pages/OnboardEngagement/ExpensifyClassicPage.tsx delete mode 100644 src/pages/OnboardEngagement/ManageTeamsExpensesPage.tsx delete mode 100644 src/pages/OnboardEngagement/PurposeForUsingExpensifyPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c326b1e69e08..83aff058a41a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -420,10 +420,6 @@ const ROUTES = { NEW_TASK_TITLE: 'new/task/title', NEW_TASK_DESCRIPTION: 'new/task/description', - ONBOARD: 'onboard', - ONBOARD_MANAGE_EXPENSES: 'onboard/manage-expenses', - ONBOARD_EXPENSIFY_CLASSIC: 'onboard/expensify-classic', - TEACHERS_UNITE: 'teachersunite', I_KNOW_A_TEACHER: 'teachersunite/i-know-a-teacher', I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 3cf747aa3859..45d4c79ca764 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -109,7 +109,6 @@ const SCREENS = { PARTICIPANTS: 'Participants', MONEY_REQUEST: 'MoneyRequest', NEW_TASK: 'NewTask', - ONBOARD_ENGAGEMENT: 'Onboard_Engagement', TEACHERS_UNITE: 'TeachersUnite', TASK_DETAILS: 'Task_Details', ENABLE_PAYMENTS: 'EnablePayments', @@ -274,12 +273,6 @@ const SCREENS = { PURPOSE: 'Onboarding_Purpose', }, - ONBOARD_ENGAGEMENT: { - ROOT: 'Onboard_Engagement_Root', - MANAGE_TEAMS_EXPENSES: 'Manage_Teams_Expenses', - EXPENSIFY_CLASSIC: 'Expenisfy_Classic', - }, - WELCOME_VIDEO: { ROOT: 'Welcome_Video_Root', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 92522b51d1f6..37b4b26ea45b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2512,28 +2512,6 @@ export default { }, copyReferralLink: 'Copy invite link', }, - purposeForExpensify: { - [CONST.INTRO_CHOICES.TRACK]: 'Track business spend for taxes', - [CONST.INTRO_CHOICES.SUBMIT]: 'Get paid back by my employer', - [CONST.INTRO_CHOICES.MANAGE_TEAM]: "Manage my team's expenses", - [CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chat and split bills with friends', - welcomeMessage: 'Welcome to Expensify', - welcomeSubtitle: 'What would you like to do?', - }, - manageTeams: { - [CONST.MANAGE_TEAMS_CHOICE.MULTI_LEVEL]: 'Multi level approval', - [CONST.MANAGE_TEAMS_CHOICE.CUSTOM_EXPENSE]: 'Custom expense coding', - [CONST.MANAGE_TEAMS_CHOICE.CARD_TRACKING]: 'Company card tracking', - [CONST.MANAGE_TEAMS_CHOICE.ACCOUNTING]: 'Accounting integrations', - [CONST.MANAGE_TEAMS_CHOICE.RULE]: 'Rule enforcement', - title: 'Do you require any of the following features?', - }, - expensifyClassic: { - title: "Expensify Classic has everything you'll need", - firstDescription: "While we're busy working on New Expensify, it currently doesn't support some of the features you're looking for.", - secondDescription: "Don't worry, Expensify Classic has everything you need.", - buttonText: 'Take me to Expensify Classic', - }, violations: { allTagLevelsRequired: 'All tags required', autoReportedRejectedExpense: ({rejectReason, rejectedBy}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rejected this expense with the comment "${rejectReason}"`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 95a84bcf59a0..f4f7b02124eb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3004,28 +3004,6 @@ export default { }, copyReferralLink: 'Copiar enlace de invitación', }, - purposeForExpensify: { - [CONST.INTRO_CHOICES.TRACK]: 'Seguimiento de los gastos de empresa para fines fiscales', - [CONST.INTRO_CHOICES.SUBMIT]: 'Reclamar gastos a mi empleador', - [CONST.INTRO_CHOICES.MANAGE_TEAM]: 'Gestionar los gastos de mi equipo', - [CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chatea y divide gastos con tus amigos', - welcomeMessage: 'Bienvenido a Expensify', - welcomeSubtitle: '¿Qué te gustaría hacer?', - }, - manageTeams: { - [CONST.MANAGE_TEAMS_CHOICE.MULTI_LEVEL]: 'Aprobación multinivel', - [CONST.MANAGE_TEAMS_CHOICE.CUSTOM_EXPENSE]: 'Codificación personalizada de gastos', - [CONST.MANAGE_TEAMS_CHOICE.CARD_TRACKING]: 'Seguimiento de tarjetas corporativas', - [CONST.MANAGE_TEAMS_CHOICE.ACCOUNTING]: 'Integraciones de contaduría', - [CONST.MANAGE_TEAMS_CHOICE.RULE]: 'Aplicación de reglas', - title: '¿Necesitas alguna de las siguientes funciones?', - }, - expensifyClassic: { - title: 'Expensify Classic tiene todo lo que necesitas', - firstDescription: 'Aunque estamos ocupados trabajando en el Nuevo Expensify, actualmente no soporta algunas de las funciones que estás buscando.', - secondDescription: 'No te preocupes, Expensify Classic tiene todo lo que necesitas.', - buttonText: 'Llévame a Expensify Classic', - }, violations: { allTagLevelsRequired: 'Todas las etiquetas son obligatorias', autoReportedRejectedExpense: ({rejectedBy, rejectReason}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rechazó la solicitud y comentó "${rejectReason}"`, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index bd5bfc46134a..370324602a0a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -12,7 +12,6 @@ import type { MoneyRequestNavigatorParamList, NewChatNavigatorParamList, NewTaskNavigatorParamList, - OnboardEngagementNavigatorParamList, ParticipantsNavigatorParamList, PrivateNotesNavigatorParamList, ProfileNavigatorParamList, @@ -172,12 +171,6 @@ const NewTaskModalStackNavigator = createModalStackNavigator require('../../../pages/tasks/NewTaskDescriptionPage').default as React.ComponentType, }); -const OnboardEngagementModalStackNavigator = createModalStackNavigator({ - [SCREENS.ONBOARD_ENGAGEMENT.ROOT]: () => require('../../../pages/OnboardEngagement/PurposeForUsingExpensifyPage').default as React.ComponentType, - [SCREENS.ONBOARD_ENGAGEMENT.MANAGE_TEAMS_EXPENSES]: () => require('../../../pages/OnboardEngagement/ManageTeamsExpensesPage').default as React.ComponentType, - [SCREENS.ONBOARD_ENGAGEMENT.EXPENSIFY_CLASSIC]: () => require('../../../pages/OnboardEngagement/ExpensifyClassicPage').default as React.ComponentType, -}); - const NewTeachersUniteNavigator = createModalStackNavigator({ [SCREENS.SAVE_THE_WORLD.ROOT]: () => require('../../../pages/TeachersUnite/SaveTheWorldPage').default as React.ComponentType, [SCREENS.I_KNOW_A_TEACHER]: () => require('../../../pages/TeachersUnite/KnowATeacherPage').default as React.ComponentType, @@ -336,7 +329,6 @@ const ProcessMoneyRequestHoldStackNavigator = createModalStackNavigator({ export { AddPersonalBankAccountModalStackNavigator, DetailsModalStackNavigator, - OnboardEngagementModalStackNavigator, EditRequestStackNavigator, EnablePaymentsStackNavigator, FlagCommentStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index c421bdc82028..93d2f8fba989 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -85,10 +85,6 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.NEW_TASK} component={ModalStackNavigators.NewTaskModalStackNavigator} /> - Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS)}); - Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => - Navigation.navigate( - // Uncomment once Stage 1 Onboarding Flow is ready - // - // ROUTES.ONBOARDING_PERSONAL_DETAILS - // - ROUTES.ONBOARD, - ), - }); + Welcome.isOnboardingFlowCompleted({onNotCompleted: () => Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS)}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoadingApp]); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index dafce762116e..1982afaa149f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -451,13 +451,6 @@ const config: LinkingOptions['config'] = { [SCREENS.NEW_TASK.DESCRIPTION]: ROUTES.NEW_TASK_DESCRIPTION, }, }, - [SCREENS.RIGHT_MODAL.ONBOARD_ENGAGEMENT]: { - screens: { - [SCREENS.ONBOARD_ENGAGEMENT.ROOT]: ROUTES.ONBOARD, - [SCREENS.ONBOARD_ENGAGEMENT.MANAGE_TEAMS_EXPENSES]: ROUTES.ONBOARD_MANAGE_EXPENSES, - [SCREENS.ONBOARD_ENGAGEMENT.EXPENSIFY_CLASSIC]: ROUTES.ONBOARD_EXPENSIFY_CLASSIC, - }, - }, [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: { screens: { [SCREENS.SAVE_THE_WORLD.ROOT]: ROUTES.TEACHERS_UNITE, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 41e4602ea2d9..7dc862ed437d 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -412,12 +412,6 @@ type NewTaskNavigatorParamList = { [SCREENS.NEW_TASK.DESCRIPTION]: undefined; }; -type OnboardEngagementNavigatorParamList = { - [SCREENS.ONBOARD_ENGAGEMENT.ROOT]: undefined; - [SCREENS.ONBOARD_ENGAGEMENT.MANAGE_TEAMS_EXPENSES]: undefined; - [SCREENS.ONBOARD_ENGAGEMENT.EXPENSIFY_CLASSIC]: undefined; -}; - type TeachersUniteNavigatorParamList = { [SCREENS.SAVE_THE_WORLD.ROOT]: undefined; [SCREENS.I_KNOW_A_TEACHER]: undefined; @@ -525,7 +519,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.ROOM_INVITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.ONBOARD_ENGAGEMENT]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.TASK_DETAILS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ENABLE_PAYMENTS]: NavigatorScreenParams; @@ -737,7 +730,6 @@ export type { ReimbursementAccountNavigatorParamList, State, WorkspaceSwitcherNavigatorParamList, - OnboardEngagementNavigatorParamList, SwitchPolicyIDParams, FullScreenNavigatorParamList, WorkspacesCentralPaneNavigatorParamList, diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index a93da266e622..95b879fc7485 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -63,11 +63,7 @@ function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOn return; } - // Uncomment once Stage 1 Onboarding Flow is ready - // - // const onboardingFlowCompleted = hasProvidedPersonalDetails && hasSelectedPurpose; - // - const onboardingFlowCompleted = hasSelectedPurpose; + const onboardingFlowCompleted = hasProvidedPersonalDetails && hasSelectedPurpose; if (onboardingFlowCompleted) { onCompleted?.(); diff --git a/src/pages/OnboardEngagement/ExpensifyClassicPage.tsx b/src/pages/OnboardEngagement/ExpensifyClassicPage.tsx deleted file mode 100644 index 7b242dd4aefa..000000000000 --- a/src/pages/OnboardEngagement/ExpensifyClassicPage.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import Button from '@components/Button'; -import HeaderPageLayout from '@components/HeaderPageLayout'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import Navigation from '@libs/Navigation/Navigation'; -import variables from '@styles/variables'; -import * as Link from '@userActions/Link'; -import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; - -function ExpensifyClassicModal() { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const {isExtraSmallScreenHeight} = useWindowDimensions(); - const theme = useTheme(); - - const navigateBack = () => { - Navigation.goBack(ROUTES.ONBOARD_MANAGE_EXPENSES); - }; - - const navigateToOldDot = () => { - Link.openOldDotLink(`${CONST.OLDDOT_URLS.INBOX}${CONST.OLDDOT_URLS.DISMMISSED_REASON}`); - }; - - return ( - - } - footer={ - - + ); } diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx deleted file mode 100644 index 64391909b197..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.android.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import {Animated, View} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type FloatingMessageCounterContainerProps from './types'; - -function FloatingMessageCounterContainer({containerStyles, children}: FloatingMessageCounterContainerProps) { - const styles = useThemeStyles(); - - return ( - - {children} - - ); -} - -FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer'; - -export default FloatingMessageCounterContainer; diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx deleted file mode 100644 index 8757d66160c4..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import {Animated} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type FloatingMessageCounterContainerProps from './types'; - -function FloatingMessageCounterContainer({accessibilityHint, containerStyles, children}: FloatingMessageCounterContainerProps) { - const styles = useThemeStyles(); - - return ( - - {children} - - ); -} - -FloatingMessageCounterContainer.displayName = 'FloatingMessageCounterContainer'; - -export default FloatingMessageCounterContainer; diff --git a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts b/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts deleted file mode 100644 index cfe791eed79c..000000000000 --- a/src/pages/home/report/FloatingMessageCounter/FloatingMessageCounterContainer/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {StyleProp, ViewStyle} from 'react-native'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; - -type FloatingMessageCounterContainerProps = ChildrenProps & { - /** Styles to be assigned to Container */ - containerStyles?: StyleProp; - - /** Specifies the accessibility hint for the component */ - accessibilityHint?: string; -}; - -export default FloatingMessageCounterContainerProps; From 35ea3650ee17950c264c2af6275fec819511e24b Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:28:44 -0700 Subject: [PATCH 0472/1357] Remove unused styles --- src/styles/index.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 537038d9f2e1..9bba82ac6b95 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3341,21 +3341,6 @@ const styles = (theme: ThemeColors) => ...visibility.hidden, }, - floatingMessageCounterWrapperAndroid: { - left: 0, - width: '100%', - alignItems: 'center', - position: 'absolute', - top: 0, - zIndex: 100, - ...visibility.hidden, - }, - - floatingMessageCounterSubWrapperAndroid: { - left: '50%', - width: 'auto', - }, - floatingMessageCounter: { left: '-50%', ...visibility.visible, From fda608b3b896d4d2ea2ede7101b5cd42ad85c93f Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:31:41 -0700 Subject: [PATCH 0473/1357] Combine styles together --- src/pages/home/report/FloatingMessageCounter.tsx | 2 +- src/styles/index.ts | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 997198192bb6..34eb01b62817 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -52,7 +52,7 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index 9bba82ac6b95..df94258b5c77 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3333,24 +3333,20 @@ const styles = (theme: ThemeColors) => height: variables.communicationsLinkHeight, }, - floatingMessageCounterWrapper: { + floatingMessageCounterWrapper: (translateY: AnimatableNumericValue) => ({ position: 'absolute', left: '50%', top: 0, zIndex: 100, + transform: [{translateY}], ...visibility.hidden, - }, + }), floatingMessageCounter: { left: '-50%', ...visibility.visible, }, - floatingMessageCounterTransformation: (translateY: AnimatableNumericValue) => - ({ - transform: [{translateY}], - } satisfies ViewStyle), - confirmationAnimation: { height: 180, width: 180, From cf6d23e8bd6eefe3af55e1b6c529e336d69e1ada Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Apr 2024 17:48:03 -0700 Subject: [PATCH 0474/1357] Migrate animation to reanimated --- .../home/report/FloatingMessageCounter.tsx | 27 ++++++++++--------- src/styles/index.ts | 5 ++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 34eb01b62817..8e73d36a878a 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useEffect, useMemo} from 'react'; -import {Animated, View} from 'react-native'; +import {View} from 'react-native'; +import Animated, {useAnimatedStyle, useSharedValue, withSpring} from 'react-native-reanimated'; import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -7,7 +8,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useNativeDriver from '@libs/useNativeDriver'; import CONST from '@src/CONST'; type FloatingMessageCounterProps = { @@ -25,20 +25,18 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const translateY = useMemo(() => new Animated.Value(MARKER_INACTIVE_TRANSLATE_Y), []); + const translateY = useSharedValue(MARKER_INACTIVE_TRANSLATE_Y); const show = useCallback(() => { - Animated.spring(translateY, { - toValue: MARKER_ACTIVE_TRANSLATE_Y, - useNativeDriver, - }).start(); + 'worklet'; + + translateY.value = withSpring(MARKER_ACTIVE_TRANSLATE_Y); }, [translateY]); const hide = useCallback(() => { - Animated.spring(translateY, { - toValue: MARKER_INACTIVE_TRANSLATE_Y, - useNativeDriver, - }).start(); + 'worklet'; + + translateY.value = withSpring(MARKER_INACTIVE_TRANSLATE_Y); }, [translateY]); useEffect(() => { @@ -49,10 +47,15 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating } }, [isActive, show, hide]); + const wrapperStyle = useAnimatedStyle(() => ({ + ...styles.floatingMessageCounterWrapper, + transform: [{translateY: translateY.value}], + })); + return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index df94258b5c77..0badb7412b1a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3333,14 +3333,13 @@ const styles = (theme: ThemeColors) => height: variables.communicationsLinkHeight, }, - floatingMessageCounterWrapper: (translateY: AnimatableNumericValue) => ({ + floatingMessageCounterWrapper: { position: 'absolute', left: '50%', top: 0, zIndex: 100, - transform: [{translateY}], ...visibility.hidden, - }), + }, floatingMessageCounter: { left: '-50%', From facd36b9aad1aca5672525e93e77fd5dd7b8215c Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 17 Apr 2024 10:10:29 +0530 Subject: [PATCH 0475/1357] Fixed a minor bug in distance preview --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index f460b9d8e88c..e5b013bb3be0 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -941,7 +941,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ )} - {(!isMovingTransactionFromTrackExpense || !hasRoute) && + {!isMovingTransactionFromTrackExpense && + !hasRoute && // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (receiptImage || receiptThumbnail ? receiptThumbnailContent From 068f3abceefcb559685c3175e29b6fec5814f8d3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 17 Apr 2024 12:12:27 +0700 Subject: [PATCH 0476/1357] fix bump enpensify-common --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 478ae3c12b3c..46d9e3c8aef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -20212,8 +20212,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", - "integrity": "sha512-/NAZoAXqeqFWHvC61dueqq9VjRugF69urUtDdDhsfvu1sQE2PCnBoM7a+ACoAEWRYrnP82cyHHhdSA8e7fPuAg==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", + "integrity": "sha512-zz0/y0apISP1orxXEQOgn+Uod45O4wVypwwtaqcDPV4dH1tC3i4L98NoLSZvLn7Y17EcceSkfN6QCEsscgFTDQ==", "license": "MIT", "dependencies": { "classnames": "2.5.0", diff --git a/package.json b/package.json index e5092e132eae..83b140cd23f5 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", From 80efdea3e2f6766123ef518ac1372c45c2dc6342 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 17 Apr 2024 12:38:20 +0700 Subject: [PATCH 0477/1357] Display violation when tax rate is invalid --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 ++ src/libs/Permissions.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 73fc7e9bae6e..6d4a0bd7c308 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -451,6 +451,8 @@ function MoneyRequestView({ ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID), ) } + brickRoadIndicator={getErrorForField('tax') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={getErrorForField('tax')} /> )} diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 105736faeba0..80916a1c7bb2 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,6 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { + return true; return !!betas?.includes(CONST.BETAS.ALL); } From 7f84a5054235cec60aba60a40fff8245add22d2f Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:55:31 +0200 Subject: [PATCH 0478/1357] add form validation on change after first submit press --- .../BaseOnboardingPersonalDetails.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 96f8974890af..da6b239289ba 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -38,6 +38,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {isSmallScreenWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useOnboardingLayout(); const {inputCallbackRef} = useAutoFocusInput(); + const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); useDisableModalDismissOnEscape(); @@ -48,6 +49,10 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat }, []); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { + if(!shouldValidateOnChange) { + setShouldValidateOnChange(true); + } + const errors = {}; // First we validate the first name field @@ -102,7 +107,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat enabledWhenOffline submitFlexEnabled shouldValidateOnBlur={false} - shouldValidateOnChange={false} + shouldValidateOnChange={shouldValidateOnChange} shouldTrimValues={false} > From e32b6f4a5a5e2693ad27177e344ff31d21b07dc7 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:57:11 +0200 Subject: [PATCH 0479/1357] fix prettier --- .../OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index da6b239289ba..4638ee4b6ca5 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -49,7 +49,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat }, []); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { - if(!shouldValidateOnChange) { + if (!shouldValidateOnChange) { setShouldValidateOnChange(true); } From 4d872eb62691bc0f30b5ec0f9605e9a2926691b1 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 10:10:13 +0200 Subject: [PATCH 0480/1357] Minor improvements --- src/libs/PolicyUtils.ts | 2 +- src/libs/actions/IOU.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 35abc7258d45..48e0032da8cd 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -11,8 +11,8 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; import Navigation, {navigationRef} from './Navigation/Navigation'; import type {RootStackParamList, State} from './Navigation/types'; -import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; import * as NetworkStore from './Network/NetworkStore'; +import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; type MemberEmailsToAccountIDs = Record; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ab39072a56b8..9916afbf3a96 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -856,7 +856,7 @@ function buildOnyxDataForInvoice( optimisticPolicyRecentlyUsedTags: OnyxTypes.RecentlyUsedTags, isNewChatReport: boolean, transactionThreadReport: OptimisticChatReport, - transactionThreadCreatedReportAction: OptimisticCreatedReportAction, + transactionThreadCreatedReportAction: OptimisticCreatedReportAction | EmptyObject, inviteReportAction?: OptimisticInviteReportAction, policy?: OnyxEntry, policyTagList?: OnyxEntry, From f6ef05fdd6160654ee814967ae9aecc5471efbc6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 11:26:26 +0200 Subject: [PATCH 0481/1357] fix: minor fix --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index bbcb1dea5365..4d536a13a619 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -253,7 +253,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const prevRate = usePrevious(rate); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); - const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? CONST.CURRENCY.USD; + const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; const distance = transaction?.routes?.route0?.distance ?? 0; const taxRates = policy?.taxRates ?? null; From e240e13e1bee2fc53881432233c884b7982de01b Mon Sep 17 00:00:00 2001 From: Eduardo Date: Wed, 17 Apr 2024 11:40:18 +0200 Subject: [PATCH 0482/1357] Removed rule, updated style.md to explain when to use a named function callback --- .eslintrc.js | 1 - contributingGuides/STYLE.md | 43 ++++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 951e37d49e45..c2198da60c52 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -123,7 +123,6 @@ module.exports = { }, }, ], - 'rulesdir/avoid-anonymous-functions': 'warn', }, }, // This helps disable the `prefer-alias` rule to be enabled for specific directories diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index 60d71979f0ea..af6ed4b6d761 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -72,31 +72,44 @@ Using named functions is the preferred way to write a callback method. ```javascript // Bad -people.map(function (item) {...}); -people.map((item) => {...}); -useEffect/useMemo/useCallback(() => {}, []); -randomList.push({ - onSelected: Utils.checkIfAllowed(() => Utils.canTeamUp(people)), -}); +people.map(function (item) {/* Long and complex logic */}); +people.map((item) => {/* Long and complex logic with many inner loops*/}); +useEffect/useMemo/useCallback(() => {/* Long and complex logic */}, []); // Good -function mappingPeople(person) {}; +function mappingPeople(person) {/* Long and complex logic */}; people.map(mappingPeople); -useEffect/useMemo/useCallback(function handlingConnection() {}, []); -randomList.push({ - onSelected: Utils.checkIfAllowed(function checkTask() { return Utils.canTeamUp(people); }), -}); +useEffect/useMemo/useCallback(function handlingConnection() {/* Long and complex logic */}, []); ``` -You can still use arrow function for declarations. +You can still use arrow function for declarations or simple logics to keep them readable. ```javascript +// Bad +randomList.push({ + onSelected: Utils.checkIfAllowed(function checkTask() { return Utils.canTeamUp(people); }), +}); +routeList.filter(function checkIsActive(route) { + return route.isActive; +}); + // Good +randomList.push({ + onSelected: Utils.checkIfAllowed(() => Utils.canTeamUp(people)), +}); +routeList.filter((route) => route.isActive); const myFunction = () => {...}; const person = { getName: () => {} }; Utils.connect({ callback: (val) => {}, }); +useEffect(() => { + if (isFocused) { + return; + } + setError(null, {}); +}, [isFocused]); + ``` Empty functions (noop) should be declare as arrow functions with no whitespace inside. Avoid _.noop() @@ -144,9 +157,9 @@ myArray.forEach(item => doSomething(item)); _.each(myArray, item => doSomething(item)); // Bad -const myArray = Object.keys(someObject).map(function handleSomething(key) {return doSomething(someObject[key]);}); +const myArray = Object.keys(someObject).map((key) => doSomething(someObject[key])); // Good -const myArray = _.map(someObject, function handleSomething(value, key) {return doSomething(value);}); +const myArray = _.map(someObject, (value, key) => doSomething(value)); // Bad myCollection.includes('item'); @@ -607,7 +620,7 @@ There are pros and cons of each, but ultimately we have standardized on using th const focusTimeoutRef = useRef(null); useFocusEffect(useCallback(() => { - focusTimeoutRef.current = setTimeout(function runFocusTimeout() {textInputRef.current.focus();}, CONST.ANIMATED_TRANSITION); + focusTimeoutRef.current = setTimeout(() => textInputRef.current.focus(), CONST.ANIMATED_TRANSITION); return () => { if (!focusTimeoutRef.current) { return; From a73490b51c6bccf48b43df791bfb8c4188d3d9c6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 11:41:17 +0200 Subject: [PATCH 0483/1357] fix: currency and rate for p2p --- .../MoneyTemporaryForRefactorRequestConfirmationList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index 4d536a13a619..6a5a1ad8104f 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -244,8 +244,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ } }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]); + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policy?.outputCurrency ?? CONST.CURRENCY.USD) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); const {unit, rate} = mileageRate ?? {}; @@ -253,7 +255,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const prevRate = usePrevious(rate); const shouldCalculateDistanceAmount = isDistanceRequest && (iouAmount === 0 || prevRate !== rate); - const currency = (mileageRate as MileageRate)?.currency ?? policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + const currency = (mileageRate as MileageRate).currency ?? policyCurrency; const distance = transaction?.routes?.route0?.distance ?? 0; const taxRates = policy?.taxRates ?? null; From 23dd180b89f7b656b5c2138b49bac1c0a1a4c3ac Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:48:47 +0200 Subject: [PATCH 0484/1357] improve getInvoicesChatName --- src/libs/ReportUtils.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 28edbd79a9ba..4f50562de698 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3011,26 +3011,20 @@ function getInvoicesChatName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - let isReceiver = false; - - if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { - isReceiver = true; - } + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - if (!isIndividual && PolicyUtils.isPolicyEmployee(policyID, allPolicies)) { - isReceiver = true; - } - - if (isReceiver) { - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); + if (isCurrentUserReceiver) { + return getPolicyName(report); } if (isIndividual) { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); } - return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]); + // TODO: Check this flow in a scope of the Invoice V0.3 + return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); } /** From 12bd9c484f46510441726eb5626cb974153b6f5b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:52:24 +0200 Subject: [PATCH 0485/1357] add comment --- src/libs/ReportUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4f50562de698..8a0b1d43adf6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2977,6 +2977,11 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } +/** + * Get the invoice payer name based on its type: + * - Individual - a receiver display name. + * - Policy - a receiver policy name. + */ function getInvoicePayerName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; From 7b711429ea0a4ff3a965b531a7097aacd3e945bc Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:53:03 +0200 Subject: [PATCH 0486/1357] Revert "integrate deleted invoice message" This reverts commit 4e81a6c86a7a00ffbaa7692f17cf82d29f0cfb24. --- src/components/ReportActionItem/MoneyRequestAction.tsx | 2 -- src/languages/en.ts | 1 - src/languages/es.ts | 1 - src/pages/home/report/ReportActionItem.tsx | 2 -- 4 files changed, 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 9bef637bf292..7d9ba2697c7a 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -116,8 +116,6 @@ function MoneyRequestAction({ message = 'parentReportAction.reversedTransaction'; } else if (isTrackExpenseAction) { message = 'parentReportAction.deletedExpense'; - } else if (action.childType === CONST.REPORT.TYPE.INVOICE) { - message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } diff --git a/src/languages/en.ts b/src/languages/en.ts index b6de47a3aaf0..05313113c521 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2541,7 +2541,6 @@ export default { reversedTransaction: '[Reversed transaction]', deletedTask: '[Deleted task]', hiddenMessage: '[Hidden message]', - deletedInvoice: '[Deleted invoice]', }, threads: { thread: 'Thread', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6b963a874599..f99eaa4eef10 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3033,7 +3033,6 @@ export default { reversedTransaction: '[Transacción anulada]', deletedTask: '[Tarea eliminada]', hiddenMessage: '[Mensaje oculto]', - deletedInvoice: '[Factura eliminada]', }, threads: { thread: 'Hilo', diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 06e222d86928..6a6eca9fb734 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -738,8 +738,6 @@ function ReportActionItem({ message = 'parentReportAction.reversedTransaction'; } else if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { message = 'parentReportAction.deletedExpense'; - } else if (parentReportAction?.childType === CONST.REPORT.TYPE.INVOICE) { - message = 'parentReportAction.deletedInvoice'; } else { message = 'parentReportAction.deletedRequest'; } From 78f5f642773d697474736dbe2cbcf4eea6384d8b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 11:54:01 +0200 Subject: [PATCH 0487/1357] Revert "integrate deleted invoice message in getTransactionReportName" This reverts commit 19b54e34b25b9d9ddcb7c27ae4b237cdcac744ff. --- src/libs/ReportUtils.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a0b1d43adf6..4f8a16246d61 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -191,9 +191,6 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' - | 'childType' - | 'childVisibleActionCount' - | 'childCommenterCount' >; type ReportRouteParams = { @@ -2691,10 +2688,6 @@ function getTransactionReportName(reportAction: OnyxEntry Date: Wed, 17 Apr 2024 11:54:30 +0200 Subject: [PATCH 0488/1357] clear redundant participant props --- src/types/onyx/Report.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 2bad31bc372c..36f124a4b826 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -26,9 +26,6 @@ type PendingChatMember = { type Participant = { hidden?: boolean; role?: 'admin' | 'member'; - // TODO: Confirm - type?: 'policy' | 'individual'; - policyID?: string; }; type Participants = Record; From 75f8b2386c9056ec06bd781bb00d61b3afa4ffab Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 11:58:26 +0200 Subject: [PATCH 0489/1357] Update optimistic invoice room creation to include current user as a member --- src/libs/actions/IOU.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9916afbf3a96..130a43a79319 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1619,6 +1619,7 @@ function getDeleteTrackExpenseInformation( /** Gathers all the data needed to create an invoice. */ function getSendInvoiceInformation( transaction: OnyxEntry, + currentUserAccountID: number, invoiceChatReport?: OnyxEntry, receipt?: Receipt, policy?: OnyxEntry, @@ -1641,7 +1642,7 @@ function getSendInvoiceInformation( if (!chatReport) { isNewChatReport = true; - chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); + chatReport = ReportUtils.buildOptimisticChatReport([receiverAccountID, currentUserAccountID], CONST.REPORT.DEFAULT_REPORT_NAME, CONST.REPORT.CHAT_TYPE.INVOICE, senderWorkspaceID); } // STEP 3: Create a new optimistic invoice report. @@ -3341,7 +3342,7 @@ function sendInvoice( optimisticTransactionID, optimisticTransactionThreadReportID, onyxData, - } = getSendInvoiceInformation(transaction, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); + } = getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories); let parameters: SendInvoiceParams = { senderWorkspaceID, From 030f7798f06d56bf4c19b9586d8220683102625e Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 17 Apr 2024 13:10:26 +0300 Subject: [PATCH 0490/1357] fix employee list --- src/libs/PolicyUtils.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index cb3e223bea8d..85a20ceaf512 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -6,7 +6,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, PolicyCategories, PolicyEmployeeList, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy'; -import type PolicyMember from '@src/types/onyx/PolicyMember'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; @@ -320,7 +319,17 @@ function getPolicyIDFromNavigationState() { } function getAdminEmailList(policy: Policy | null) { - return policy?.employeeList?.filter((employee: PolicyMember) => employee?.role === CONST.POLICY.ROLE.ADMIN).map((admin) => admin.email); + const adminEmailList: Array<{email: string}> = []; + if (!policy?.employeeList) { + return adminEmailList; + } + Object.keys(policy.employeeList).forEach((email: string) => { + if (policy?.employeeList?.[email].role !== CONST.POLICY.ROLE.ADMIN) { + return; + } + adminEmailList.push({email}); + }); + return adminEmailList; } export { From 5625582f11ab9b8a4c136651fce4319b09ad6689 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:16:41 +0200 Subject: [PATCH 0491/1357] sync changes --- src/CONST.ts | 3 ++- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/ReportUtils.ts | 32 +++++++++++++++++--------------- src/types/onyx/Report.ts | 23 +++++++++++++---------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0dbf26851ead..95056970457b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -52,7 +52,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', - INVOICE: 'invoiceRoom', + INVOICE: 'invoice', } as const; // Explicit type annotation is required @@ -1423,6 +1423,7 @@ const CONST = { SPLIT: 'split', REQUEST: 'request', TRACK_EXPENSE: 'track-expense', + INVOICE: 'invoice', }, REQUEST_TYPE: { DISTANCE: 'distance', diff --git a/src/languages/en.ts b/src/languages/en.ts index 05313113c521..f9c845b42a5a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -505,6 +505,7 @@ export default { beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` to chat about anything ${workspaceName} related.`, beginningOfChatHistoryUserRoomPartOne: 'Collaboration starts here! 🎉\nUse this space to chat about anything ', beginningOfChatHistoryUserRoomPartTwo: ' related.', + beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 Use this room to view, discuss, and pay invoices.', beginningOfChatHistory: 'This is the beginning of your chat with ', beginningOfChatHistoryPolicyExpenseChatPartOne: 'Collaboration between ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' and ', @@ -522,7 +523,6 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'track an expense', }, - beginningOfChatHistoryInvoiceRoom: 'Collaboration starts here! 🎉 \nUse this room to view, discuss, and pay invoices.', }, reportAction: { asCopilot: 'as copilot for', diff --git a/src/languages/es.ts b/src/languages/es.ts index f99eaa4eef10..568e14e6b241 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -501,6 +501,7 @@ export default { beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` para chatear sobre cualquier cosa relacionada con ${workspaceName}.`, beginningOfChatHistoryUserRoomPartOne: '¡Este es el lugar para colaborar! 🎉\nUsa este espacio para chatear sobre cualquier cosa relacionada con ', beginningOfChatHistoryUserRoomPartTwo: '.', + beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉 Utilice esta sala para ver, discutir y pagar facturas.', beginningOfChatHistory: 'Aquí comienzan tus conversaciones con ', beginningOfChatHistoryPolicyExpenseChatPartOne: '¡La colaboración entre ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ', @@ -518,7 +519,6 @@ export default { // eslint-disable-next-line @typescript-eslint/naming-convention 'track-expense': 'rastrear un gasto', }, - beginningOfChatHistoryInvoiceRoom: '¡Este es el lugar para colaborar! 🎉\nUsa esta sala para ver, discutir y pagar facturas.', }, reportAction: { asCopilot: 'como copiloto de', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4f8a16246d61..76693877b462 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -141,6 +141,7 @@ type OptimisticAddCommentReportAction = Pick< | 'childStatusNum' | 'childStateNum' | 'errors' + | 'whisperedToAccountIDs' > & {isOptimisticAction: boolean}; type OptimisticReportAction = { @@ -283,6 +284,7 @@ type OptimisticChatReport = Pick< | 'description' | 'writeCapability' | 'avatarUrl' + | 'invoiceReceiver' > & { isOptimisticReport: true; }; @@ -675,6 +677,13 @@ function isChatReport(report: OnyxEntry | EmptyObject): boolean { return report?.type === CONST.REPORT.TYPE.CHAT; } +/** + * Checks if a report is an invoice report. + */ +function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { + return report?.type === CONST.REPORT.TYPE.INVOICE; +} + /** * Checks if a report is an Expense report. */ @@ -862,6 +871,13 @@ function isPolicyExpenseChat(report: OnyxEntry | Participant | EmptyObje return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT || (report?.isPolicyExpenseChat ?? false); } +/** + * Whether the provided report is an invoice room chat. + */ +function isInvoiceRoom(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; +} + /** * Whether the provided report belongs to a Control policy and is an expense chat */ @@ -906,20 +922,6 @@ function isPaidGroupPolicyExpenseReport(report: OnyxEntry): boolean { return isExpenseReport(report) && isPaidGroupPolicy(report); } -/** - * Check if Report is an invoice room - */ -function isInvoiceRoom(report: OnyxEntry): boolean { - return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; -} - -/** - * Check if Report is an invoice report - */ -function isInvoiceReport(report: OnyxEntry | EmptyObject): boolean { - return report?.type === CONST.REPORT.TYPE.INVOICE; -} - /** * Checks if the supplied report is an invoice report in Open state and status. */ @@ -2218,7 +2220,7 @@ function hasNonReimbursableTransactions(iouReportID: string | undefined): boolea function getMoneyRequestSpendBreakdown(report: OnyxEntry, allReportsDict: OnyxCollection = null): SpendBreakdown { const allAvailableReports = allReportsDict ?? allReports; let moneyRequestReport; - if (isMoneyRequestReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { moneyRequestReport = report; } if (allAvailableReports && report?.iouReportID) { diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 36f124a4b826..344b7df5b2eb 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -28,6 +28,16 @@ type Participant = { role?: 'admin' | 'member'; }; +type InvoiceReceiver = + | { + type: 'individual'; + accountID: number; + } + | { + type: 'policy'; + policyID: string; + }; + type Participants = Record; type Report = OnyxCommon.OnyxValueWithOfflineFeedback< @@ -128,6 +138,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Report cached total */ cachedTotal?: string; + /** Invoice room receiver data */ + invoiceReceiver?: InvoiceReceiver; + lastMessageTranslationKey?: string; parentReportID?: string; parentReportActionID?: string; @@ -186,16 +199,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; - - invoiceReceiver?: - | { - type: typeof CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; - accountID: number; - } - | { - type: typeof CONST.INVOICE_RECEIVER_TYPE.POLICY; - policyID: string; - }; }, PolicyReportField['fieldID'] >; From 1b92e52f10d874af91ab346590e315aa640be244 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:18:30 +0200 Subject: [PATCH 0492/1357] update invoice receiver const --- src/CONST.ts | 9 ++++----- src/libs/ReportUtils.ts | 10 +++++----- src/libs/actions/IOU.ts | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 95056970457b..6467769263dd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -839,6 +839,10 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -4353,11 +4357,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - - INVOICE_RECEIVER_TYPE: { - INDIVIDUAL: 'individual', - POLICY: 'policy', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 76693877b462..b2a1fd53afeb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1971,7 +1971,7 @@ function getIcons( const invoiceRoomReport = getReport(report.chatReportID); const icons = [getWorkspaceIcon(invoiceRoomReport, policy)]; - if (invoiceRoomReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (invoiceRoomReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { icons.push(...getIconsForParticipants([invoiceRoomReport?.invoiceReceiver.accountID], personalDetails)); return icons; @@ -2979,7 +2979,7 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco */ function getInvoicePayerName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; if (isIndividual) { return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID]); @@ -3009,7 +3009,7 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent */ function getInvoicesChatName(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; const isCurrentUserReceiver = @@ -3032,7 +3032,7 @@ function getInvoicesChatName(report: OnyxEntry): string { */ function getInvoicesChatSubtitle(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; let isReceiver = false; @@ -5236,7 +5236,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyEmployee: boolean): boo } const isReceiverPolicyAdmin = - report?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.POLICY ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; + report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS ? getPolicy(report?.invoiceReceiver?.policyID)?.role === CONST.POLICY.ROLE.ADMIN : false; if (isReceiverPolicyAdmin) { return false; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 947a800f5ee5..ae93ec7413ac 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5328,7 +5328,7 @@ function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chat } if (ReportUtils.isInvoiceReport(iouReport)) { - if (chatReport?.invoiceReceiver?.type === CONST.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { + if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return chatReport?.invoiceReceiver?.accountID === userAccountID; } From fb69172a697a9892cc07f7a6a80581f3f3ee99e6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:22:27 +0200 Subject: [PATCH 0493/1357] improve getInvoicesChatSubtitle --- src/libs/ReportUtils.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b2a1fd53afeb..efd51ef4af99 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3034,18 +3034,11 @@ function getInvoicesChatSubtitle(report: OnyxEntry): string { const invoiceReceiver = report?.invoiceReceiver; const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const policyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - let isReceiver = false; - - if (isIndividual && invoiceReceiverAccountID === currentUserAccountID) { - isReceiver = true; - } - - if (!isIndividual && PolicyUtils.isPolicyEmployee(policyID, allPolicies)) { - isReceiver = true; - } + const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; + const isCurrentUserReceiver = + (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - if (isReceiver) { + if (isCurrentUserReceiver) { let receiver = ''; if (isIndividual) { @@ -3057,7 +3050,8 @@ function getInvoicesChatSubtitle(report: OnyxEntry): string { return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); } - return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`])}); + // TODO: Check this flow in a scope of the Invoice V0.3 + return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`])}); } /** From 2610f14920e41ec6a9b067bab9c9c4dab4c7fa77 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:25:59 +0200 Subject: [PATCH 0494/1357] revert extra changes --- src/libs/ReportUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index efd51ef4af99..f41b310c2f2e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -141,7 +141,6 @@ type OptimisticAddCommentReportAction = Pick< | 'childStatusNum' | 'childStateNum' | 'errors' - | 'whisperedToAccountIDs' > & {isOptimisticAction: boolean}; type OptimisticReportAction = { @@ -192,6 +191,8 @@ type OptimisticIOUReportAction = Pick< | 'receipt' | 'whisperedToAccountIDs' | 'childReportID' + | 'childVisibleActionCount' + | 'childCommenterCount' >; type ReportRouteParams = { From b8d94a7681ffe4d84aa94aa24ca0893d910b449f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:32:51 +0200 Subject: [PATCH 0495/1357] simplify invoice room subtitle --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f41b310c2f2e..417cfdc09bf9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3149,7 +3149,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu */ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isInvoiceRoom(report)) { - return getInvoicesChatSubtitle(report); + return Localize.translateLocal('workspace.common.invoices'); } if (isChatThread(report)) { return ''; From 04ee7b1fb55945358135fcb907271a11a34be45e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 12:33:55 +0200 Subject: [PATCH 0496/1357] Revert "integrate report subtitle for invoice room" This reverts commit a524346f9402a98b9b6900a167c9ca15a1569078. --- src/languages/en.ts | 4 ---- src/languages/es.ts | 4 ---- src/languages/types.ts | 6 ------ src/libs/ReportUtils.ts | 31 +++---------------------------- 4 files changed, 3 insertions(+), 42 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f9c845b42a5a..d2f76ee4ed44 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -30,8 +30,6 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - InvoicesFromParams, - InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2148,8 +2146,6 @@ export default { unlockVBACopy: "You're all set to accept payments by ACH or credit card!", viewUnpaidInvoices: 'View unpaid invoices', sendInvoice: 'Send invoice', - invoicesFrom: ({sender}: InvoicesFromParams) => `Invoices from ${sender}`, - invoicesTo: ({receiver}: InvoicesToParams) => `Invoices to ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Unlock Concierge travel booking', diff --git a/src/languages/es.ts b/src/languages/es.ts index 568e14e6b241..141e3ad3db91 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -29,8 +29,6 @@ import type { GoBackMessageParams, GoToRoomParams, InstantSummaryParams, - InvoicesFromParams, - InvoicesToParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -2176,8 +2174,6 @@ export default { unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', viewUnpaidInvoices: 'Ver facturas emitidas pendientes', sendInvoice: 'Enviar factura', - invoicesFrom: ({sender}: InvoicesFromParams) => `Facturas de ${sender}`, - invoicesTo: ({receiver}: InvoicesToParams) => `Facturas a ${receiver}`, }, travel: { unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', diff --git a/src/languages/types.ts b/src/languages/types.ts index 8c4d287b7dfc..59e1bfe40af2 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -251,10 +251,6 @@ type ViolationsTaxOutOfPolicyParams = {taxName?: string}; type TaskCreatedActionParams = {title: string}; -type InvoicesFromParams = {sender: string}; - -type InvoicesToParams = {receiver: string}; - /* Translation Object types */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type TranslationBaseValue = string | string[] | ((...args: any[]) => string); @@ -407,6 +403,4 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, - InvoicesFromParams, - InvoicesToParams, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 417cfdc09bf9..0ba38d8f91de 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3028,33 +3028,6 @@ function getInvoicesChatName(report: OnyxEntry): string { return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]); } -/** - * Get the subtitle for an invoice room. - */ -function getInvoicesChatSubtitle(report: OnyxEntry): string { - const invoiceReceiver = report?.invoiceReceiver; - const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; - const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : -1; - const invoiceReceiverPolicyID = isIndividual ? '' : invoiceReceiver?.policyID ?? ''; - const isCurrentUserReceiver = - (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && PolicyUtils.isPolicyEmployee(invoiceReceiverPolicyID, allPolicies)); - - if (isCurrentUserReceiver) { - let receiver = ''; - - if (isIndividual) { - receiver = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); - } else { - receiver = getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); - } - - return Localize.translateLocal('workspace.invoices.invoicesTo', {receiver}); - } - - // TODO: Check this flow in a scope of the Invoice V0.3 - return Localize.translateLocal('workspace.invoices.invoicesFrom', {sender: getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`])}); -} - /** * Get the title for a report. */ @@ -3167,6 +3140,9 @@ function getChatRoomSubtitle(report: OnyxEntry): string | undefined { if (isArchivedRoom(report)) { return report?.oldPolicyName ?? ''; } + if (isInvoiceRoom(report)) { + return Localize.translateLocal('workspace.common.invoices'); + } return getPolicyName(report); } @@ -6208,7 +6184,6 @@ export { getDisplayNamesWithTooltips, getInvoicePayerName, getInvoicesChatName, - getInvoicesChatSubtitle, getReportName, getReport, getReportNotificationPreference, From 9aa233c33eb954528fffd433272ed4c21f03438a Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 17 Apr 2024 14:31:54 +0300 Subject: [PATCH 0497/1357] Resolve C+ review comments --- ...oksCompanyCardExpenseAccountSelectPage.tsx | 64 ++++++++++------ ...oksCompanyCardExpenseConfigurationPage.tsx | 7 +- .../QuickbooksExportConfigurationPage.tsx | 72 ++++++++---------- .../export/QuickbooksExportDateSelectPage.tsx | 20 ++--- ...ickbooksExportInvoiceAccountSelectPage.tsx | 18 +++-- ...oksOutOfPocketExpenseAccountSelectPage.tsx | 18 +++-- ...oksOutOfPocketExpenseConfigurationPage.tsx | 8 +- ...ooksOutOfPocketExpenseEntitySelectPage.tsx | 75 +++++++++++-------- ...ooksPreferredExporterConfigurationPage.tsx | 31 ++++---- src/types/onyx/PolicyEmployee.ts | 4 +- 10 files changed, 173 insertions(+), 144 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx index 042b2a8b283d..71dc3118ecc7 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx @@ -1,9 +1,11 @@ import React, {useCallback, useMemo} from 'react'; +import type {SectionListData} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem, Section} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -14,6 +16,12 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +type CardListItem = ListItem & { + value: string; +}; +type CardsSection = SectionListData>; +type Card = {name: string}; + function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -22,38 +30,46 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyProps const {exportCompanyCard, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {}; const isLocationEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); - const defaultCards = [ - { - name: translate(`workspace.qbo.creditCard`), - }, - { - name: translate(`workspace.qbo.debitCard`), - }, - { - name: translate(`workspace.qbo.vendorBill`), - }, - ]; - const cardsBasedOnLocation = isLocationEnabled ? defaultCards.slice(0, -1) : defaultCards; - const result = creditCards?.length ? creditCards : cardsBasedOnLocation; - const policyID = policy?.id ?? ''; - const data = useMemo( + const defaultCards = useMemo( + () => [ + { + name: translate(`workspace.qbo.creditCard`), + }, + { + name: translate(`workspace.qbo.debitCard`), + }, + { + name: translate(`workspace.qbo.vendorBill`), + }, + ], + [translate], + ); + const cards = useMemo(() => { + if (creditCards?.length) { + return creditCards; + } + return isLocationEnabled ? defaultCards.slice(0, -1) : defaultCards; + }, [creditCards, isLocationEnabled, defaultCards]); + + const data = useMemo( () => - result?.map((card) => ({ + cards.map((card) => ({ value: card.name, text: card.name, keyForList: card.name, isSelected: card.name === exportCompanyCard, })), - [exportCompanyCard, result], + [cards, exportCompanyCard], ); + const sections = useMemo(() => [{data}], [data]); + const policyID = policy?.id ?? ''; + const onSelectRow = useCallback( - (row: {value: string}) => { - if (exportCompanyCard && row.value === exportCompanyCard) { - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.getRoute(policyID)); - return; + (row: CardListItem) => { + if (row.value !== exportCompanyCard) { + Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_COMPANY_CARD, row.value); } - Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_COMPANY_CARD, row.value); Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.getRoute(policyID)); }, [exportCompanyCard, policyID], @@ -68,12 +84,12 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyProps mode.isSelected)?.keyForList} + footerContent={isLocationEnabled && {translate('workspace.qbo.companyCardsLocationEnabledDescription')}} /> - {isLocationEnabled && {translate('workspace.qbo.companyCardsLocationEnabledDescription')}} ); diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx index 457e12c3c666..3a1f40a0de32 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx @@ -10,18 +10,17 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; function QuickbooksCompanyCardExpenseConfigurationPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? ''; - const {exportCompanyCard} = policy?.connections?.quickbooksOnline?.config ?? {}; - + const {exportCompanyCard, errors} = policy?.connections?.quickbooksOnline?.config ?? {}; return ( @@ -32,7 +31,7 @@ function QuickbooksCompanyCardExpenseConfigurationPage({policy}: WithPolicyProps title={exportCompanyCard} description={translate('workspace.qbo.exportAs')} onPress={() => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID))} - brickRoadIndicator={undefined} + brickRoadIndicator={errors?.exportCompanyCard ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} shouldShowRightIcon /> diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx index 9964c792f6ad..1a51d36c9ec2 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx @@ -1,11 +1,12 @@ import React from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import type {MenuItemProps} from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; +import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; @@ -20,81 +21,74 @@ function QuickbooksExportConfigurationPage({policy}: WithPolicyProps) { const styles = useThemeStyles(); const policyID = policy?.id ?? ''; const policyOwner = policy?.owner ?? ''; - const {exporter, exportDate, exportEntity, exportInvoice, exportCompanyCard} = policy?.connections?.quickbooksOnline?.config ?? {}; - const sections = [ + const {exporter, exportDate, exportEntity, exportInvoice, exportCompanyCard, errors} = policy?.connections?.quickbooksOnline?.config ?? {}; + const menuItems: MenuItemProps[] = [ { description: translate('workspace.qbo.preferredExporter'), - action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER.getRoute(policyID)), - hasError: Boolean(policy?.errors?.exporter), + onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER.getRoute(policyID)), + brickRoadIndicator: errors?.exporter ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: exporter ?? policyOwner, }, { description: translate('workspace.qbo.date'), - action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT.getRoute(policyID)), - hasError: Boolean(policy?.errors?.exportDate), + onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT.getRoute(policyID)), + brickRoadIndicator: errors?.exportDate ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: exportDate ? translate(`workspace.qbo.${exportDate}.label`) : undefined, }, { description: translate('workspace.qbo.exportExpenses'), - action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)), - hasError: Boolean(policy?.errors?.syncCustomers), + onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)), + brickRoadIndicator: errors?.exportExpenses ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: exportEntity ? translate(`workspace.qbo.${exportEntity}`) : undefined, }, { description: translate('workspace.qbo.exportInvoices'), - action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT.getRoute(policyID)), - hasError: Boolean(policy?.errors?.exportInvoice), + onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT.getRoute(policyID)), + brickRoadIndicator: errors?.exportInvoice ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: exportInvoice, }, { description: translate('workspace.qbo.exportCompany'), - action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.getRoute(policyID)), - hasError: Boolean(policy?.errors?.exportCompanyCard), + onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.getRoute(policyID)), + brickRoadIndicator: errors?.exportCompanyCard ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: exportCompanyCard, }, { description: translate('workspace.qbo.exportExpensifyCard'), - action: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT.getRoute(policyID)), - hasError: Boolean(policy?.errors?.syncTaxes), - title: 'Credit card', - interactive: false, + title: translate('workspace.qbo.creditCard'), + shouldShowRightIcon: false, }, ]; return ( {translate('workspace.qbo.exportDescription')} - {sections.map((section) => ( - + {menuItems.map((menuItem) => ( + ))} - { - Link.openExternalLink(CONST.DEEP_DIVE_EXPENSIFY_CARD); - }} - style={[styles.ph5, styles.pb5]} - accessibilityLabel={translate('workspace.qbo.deepDiveExpensifyCard')} - role={CONST.ROLE.LINK} - > - - {`${translate('workspace.qbo.deepDiveExpensifyCard')} `} - {translate('workspace.qbo.deepDiveExpensifyCardIntegration')} - - + + {`${translate('workspace.qbo.deepDiveExpensifyCard')} `} + Link.openExternalLink(CONST.DEEP_DIVE_EXPENSIFY_CARD)} + style={[styles.optionAlternateText, styles.textLabelSupporting, styles.link]} + > + {translate('workspace.qbo.deepDiveExpensifyCardIntegration')} + + ); diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksExportDateSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksExportDateSelectPage.tsx index 4278ecbd72b3..38e326ff6694 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksExportDateSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksExportDateSelectPage.tsx @@ -1,9 +1,11 @@ import React, {useCallback} from 'react'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -14,26 +16,27 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +type CardListItem = ListItem & { + value: ValueOf; +}; function QuickbooksExportDateSelectPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? ''; const {exportDate} = policy?.connections?.quickbooksOnline?.config ?? {}; - const data = Object.values(CONST.QUICKBOOKS_EXPORT_DATE).map((dateType) => ({ + const data: CardListItem[] = Object.values(CONST.QUICKBOOKS_EXPORT_DATE).map((dateType) => ({ value: dateType, text: translate(`workspace.qbo.${dateType}.label`), alternateText: translate(`workspace.qbo.${dateType}.description`), keyForList: dateType, - isSelected: exportDate ? exportDate === dateType : false, + isSelected: exportDate === dateType, })); const onSelectRow = useCallback( - (row: {value: string}) => { - if (exportDate && row.value === exportDate) { - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT.getRoute(policyID)); - return; + (row: CardListItem) => { + if (row.value !== exportDate) { + Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_DATE, row.value); } - Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_DATE, row.value); Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT.getRoute(policyID)); }, [exportDate, policyID], @@ -42,13 +45,12 @@ function QuickbooksExportDateSelectPage({policy}: WithPolicyProps) { return ( - {translate('workspace.qbo.exportDateDescription')} {translate('workspace.qbo.exportDateDescription')}} sections={[{data}]} ListItem={RadioListItem} onSelectRow={onSelectRow} diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksExportInvoiceAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksExportInvoiceAccountSelectPage.tsx index b9c6c0466dc2..51ec82f65d0a 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksExportInvoiceAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksExportInvoiceAccountSelectPage.tsx @@ -4,6 +4,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -14,6 +15,10 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +type CardListItem = ListItem & { + value: string; +}; + function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -31,7 +36,7 @@ function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyProps) { const result = accountsReceivable?.length ? accountsReceivable : draft; const policyID = policy?.id ?? ''; - const data = useMemo( + const data: CardListItem[] = useMemo( () => result?.map((account) => ({ value: account.name, @@ -43,12 +48,10 @@ function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyProps) { ); const onSelectRow = useCallback( - (row: {value: string}) => { - if (exportInvoice && row.value === exportInvoice) { - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT.getRoute(policyID)); - return; + (row: CardListItem) => { + if (row.value !== exportInvoice) { + Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_INVOICE, row.value); } - Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_INVOICE, row.value); Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT.getRoute(policyID)); }, [exportInvoice, policyID], @@ -57,13 +60,12 @@ function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyProps) { return ( - {translate('workspace.qbo.exportInvoicesDescription')} {translate('workspace.qbo.exportInvoicesDescription')}} sections={[{data}]} ListItem={RadioListItem} onSelectRow={onSelectRow} diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx index 095d4494a583..4af0e8ad4261 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage.tsx @@ -4,6 +4,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -24,6 +25,10 @@ const draft = [ }, ]; +type CardListItem = ListItem & { + value: string; +}; + function QuickbooksOutOfPocketExpenseAccountSelectPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -31,7 +36,7 @@ function QuickbooksOutOfPocketExpenseAccountSelectPage({policy}: WithPolicyProps const {exportEntity, exportAccount} = policy?.connections?.quickbooksOnline?.config ?? {}; - const data = useMemo(() => { + const data: CardListItem[] = useMemo(() => { let result; switch (exportEntity) { case CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK: @@ -58,12 +63,10 @@ function QuickbooksOutOfPocketExpenseAccountSelectPage({policy}: WithPolicyProps const policyID = policy?.id ?? ''; const onSelectRow = useCallback( - (row: {value: string}) => { - if (exportAccount && row.value === exportAccount) { - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)); - return; + (row: CardListItem) => { + if (row.value !== exportAccount) { + Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_ACCOUNT, row.value); } - Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_ACCOUNT, row.value); Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)); }, [exportAccount, policyID], @@ -72,13 +75,12 @@ function QuickbooksOutOfPocketExpenseAccountSelectPage({policy}: WithPolicyProps return ( - {translate('workspace.qbo.accountsPayableDescription')} {translate('workspace.qbo.accountsPayableDescription')}} sections={[{data}]} ListItem={RadioListItem} onSelectRow={onSelectRow} diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx index 38c0017e04b1..9151afcca9d3 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx @@ -17,13 +17,12 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyProps const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? ''; - const {syncLocations, exportAccount, exportEntity} = policy?.connections?.quickbooksOnline?.config ?? {}; + const {syncLocations, exportAccount, exportEntity, errors} = policy?.connections?.quickbooksOnline?.config ?? {}; const isLocationEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); return ( @@ -34,7 +33,7 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyProps title={exportEntity ? translate(`workspace.qbo.${exportEntity}`) : undefined} description={translate('workspace.qbo.exportAs')} onPress={() => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT.getRoute(policyID))} - brickRoadIndicator={undefined} + brickRoadIndicator={errors?.exportEntity ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} shouldShowRightIcon /> @@ -46,8 +45,9 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyProps Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT.getRoute(policyID))} - brickRoadIndicator={undefined} + brickRoadIndicator={errors?.exportAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} shouldShowRightIcon /> diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx index d099f53b7332..d6ef35a3a1d0 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx @@ -1,9 +1,12 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; +import type {SectionListData} from 'react-native'; +import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem, Section} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -14,6 +17,12 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +type CardListItem = ListItem & { + value: ValueOf; + isShown: boolean; +}; +type CardsSection = SectionListData>; + function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -21,37 +30,40 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyProps) const isTaxesEnabled = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); const policyID = policy?.id ?? ''; - const data = [ - { - value: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, - text: translate(`workspace.qbo.check`), - keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, - isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, - isShown: true, - }, - { - value: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, - text: translate(`workspace.qbo.journalEntry`), - keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, - isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, - isShown: !isTaxesEnabled, - }, - { - value: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, - text: translate(`workspace.qbo.vendorBill`), - keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, - isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, - isShown: true, - }, - ]; + const data: CardListItem[] = useMemo( + () => [ + { + value: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, + text: translate(`workspace.qbo.check`), + keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, + isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, + isShown: true, + }, + { + value: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, + text: translate(`workspace.qbo.journalEntry`), + keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, + isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, + isShown: !isTaxesEnabled, + }, + { + value: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, + text: translate(`workspace.qbo.vendorBill`), + keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, + isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, + isShown: true, + }, + ], + [exportEntity, isTaxesEnabled, translate], + ); + + const sections: CardsSection[] = useMemo(() => [{data: data.filter((item) => item.isShown)}], [data]); const onSelectRow = useCallback( - (row: {value: string}) => { - if (exportEntity && row.value === exportEntity) { - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)); - return; + (row: CardListItem) => { + if (row.value !== exportEntity) { + Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_ENTITY, row.value); } - Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_ENTITY, row.value); Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)); }, [exportEntity, policyID], @@ -60,14 +72,13 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyProps) return ( - {translate('workspace.qbo.optionBelow')} item.isShown)}]} + headerContent={{translate('workspace.qbo.optionBelow')}} + sections={sections} ListItem={RadioListItem} onSelectRow={onSelectRow} initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList} diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksPreferredExporterConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksPreferredExporterConfigurationPage.tsx index 9b81b17961ed..d86ed1e8d783 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksPreferredExporterConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksPreferredExporterConfigurationPage.tsx @@ -4,6 +4,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -23,6 +24,11 @@ const draft = [ {name: 'Alberto Gonzalez-Cela', currency: 'USD', id: '104', email: 'alberto@expensify.com'}, {name: 'Aldo test QBO2 QBO2 Last name', currency: 'USD', id: '140', email: 'admin@qbo.com'}, ]; + +type CardListItem = ListItem & { + value: string; +}; + function QuickBooksExportPreferredExporterPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -31,7 +37,7 @@ function QuickBooksExportPreferredExporterPage({policy}: WithPolicyProps) { const result = exporters?.length ? exporters : draft; const policyID = policy?.id ?? ''; - const sections = useMemo( + const data: CardListItem[] = useMemo( () => result?.map((vendor) => ({ value: vendor.email, @@ -43,15 +49,11 @@ function QuickBooksExportPreferredExporterPage({policy}: WithPolicyProps) { ); const onSelectRow = useCallback( - (row: {value?: string}) => { - if (exporter && row.value === exporter) { - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER.getRoute(policyID)); - return; - } - if (row?.value) { + (row: CardListItem) => { + if (row.value !== exporter) { Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.PREFERRED_EXPORTER, row.value); - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER.getRoute(policyID)); } + Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER.getRoute(policyID)); }, [policyID, exporter], ); @@ -59,19 +61,22 @@ function QuickBooksExportPreferredExporterPage({policy}: WithPolicyProps) { return ( - {translate('workspace.qbo.exportPreferredExporterNote')} - {translate('workspace.qbo.exportPreferredExporterSubNote')} + {translate('workspace.qbo.exportPreferredExporterNote')} + {translate('workspace.qbo.exportPreferredExporterSubNote')} + + } shouldStopPropagation - sections={[{data: sections}]} + sections={[{data}]} ListItem={RadioListItem} onSelectRow={onSelectRow} - initiallyFocusedOptionKey={sections.find((mode) => mode.isSelected)?.keyForList} + initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList} /> diff --git a/src/types/onyx/PolicyEmployee.ts b/src/types/onyx/PolicyEmployee.ts index 175bfdfbb149..4a5f374de44a 100644 --- a/src/types/onyx/PolicyEmployee.ts +++ b/src/types/onyx/PolicyEmployee.ts @@ -1,10 +1,8 @@ -import type {ValueOf} from 'type-fest'; -import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; type PolicyEmployee = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Role of the user in the policy */ - role?: ValueOf; + role?: string; /** Email of the user */ email?: string; From 3a905c7f3629610b08162c23b65cdd4ee5bcac61 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 17 Apr 2024 20:03:58 +0800 Subject: [PATCH 0498/1357] remove underscore and unused file --- src/ROUTES.ts | 5 - src/SCREENS.ts | 1 - src/components/Modal/modalPropTypes.js | 89 --------- .../OptionsSelector/BaseOptionsSelector.js | 35 ++-- src/components/Popover/popoverPropTypes.js | 48 ----- src/components/menuItemPropTypes.js | 179 ----------------- .../ModalStackNavigators/index.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 5 +- src/pages/EditRequestPage.js | 189 ------------------ ...yForRefactorRequestParticipantsSelector.js | 34 ++-- .../step/IOURequestStepRoutePropTypes.js | 29 --- src/pages/reportPropTypes.js | 79 -------- 13 files changed, 37 insertions(+), 658 deletions(-) delete mode 100644 src/components/Modal/modalPropTypes.js delete mode 100644 src/components/Popover/popoverPropTypes.js delete mode 100644 src/components/menuItemPropTypes.js delete mode 100644 src/pages/EditRequestPage.js delete mode 100644 src/pages/iou/request/step/IOURequestStepRoutePropTypes.js delete mode 100644 src/pages/reportPropTypes.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ec2bf11957e1..11988af00d5b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -202,11 +202,6 @@ const ROUTES = { route: 'r/:reportID/avatar', getRoute: (reportID: string) => `r/${reportID}/avatar` as const, }, - EDIT_REQUEST: { - route: 'r/:threadReportID/edit/:field/:tagIndex?', - getRoute: (threadReportID: string, field: ValueOf, tagIndex?: number) => - `r/${threadReportID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, - }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 96372d5bbabb..6fbaf047e544 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -265,7 +265,6 @@ const SCREENS = { }, EDIT_REQUEST: { - ROOT: 'EditRequest_Root', CURRENCY: 'EditRequest_Currency', REPORT_FIELD: 'EditRequest_ReportField', }, diff --git a/src/components/Modal/modalPropTypes.js b/src/components/Modal/modalPropTypes.js deleted file mode 100644 index 84e610b694e4..000000000000 --- a/src/components/Modal/modalPropTypes.js +++ /dev/null @@ -1,89 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import stylePropTypes from '@styles/stylePropTypes'; -import CONST from '@src/CONST'; - -const propTypes = { - /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */ - fullscreen: PropTypes.bool, - - /** Should we close modal on outside click */ - shouldCloseOnOutsideClick: PropTypes.bool, - - /** Should we announce the Modal visibility changes? */ - shouldSetModalVisibility: PropTypes.bool, - - /** Callback method fired when the user requests to close the modal */ - onClose: PropTypes.func.isRequired, - - /** State that determines whether to display the modal or not */ - isVisible: PropTypes.bool.isRequired, - - /** Modal contents */ - children: PropTypes.node.isRequired, - - /** Callback method fired when the user requests to submit the modal content. */ - onSubmit: PropTypes.func, - - /** Callback method fired when the modal is hidden */ - onModalHide: PropTypes.func, - - /** Callback method fired when the modal is shown */ - onModalShow: PropTypes.func, - - /** Style of modal to display */ - type: PropTypes.oneOf(_.values(CONST.MODAL.MODAL_TYPE)), - - /** A react-native-animatable animation definition for the modal display animation. */ - animationIn: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - /** A react-native-animatable animation definition for the modal hide animation. */ - animationOut: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - /** The anchor position of a popover modal. Has no effect on other modal types. */ - popoverAnchorPosition: PropTypes.shape({ - top: PropTypes.number, - right: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - }), - - /** Modal container styles */ - innerContainerStyle: stylePropTypes, - - /** Whether the modal should go under the system statusbar */ - statusBarTranslucent: PropTypes.bool, - - /** Whether the modal should avoid the keyboard */ - avoidKeyboard: PropTypes.bool, - - /** - * Whether the modal should hide its content while animating. On iOS, set to true - * if `useNativeDriver` is also true, to avoid flashes in the UI. - * - * See: https://github.com/react-native-modal/react-native-modal/pull/116 - * */ - hideModalContentWhileAnimating: PropTypes.bool, - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - fullscreen: true, - shouldCloseOnOutsideClick: false, - shouldSetModalVisibility: true, - onSubmit: null, - type: '', - onModalHide: () => {}, - onModalShow: () => {}, - animationIn: null, - animationOut: null, - popoverAnchorPosition: {}, - innerContainerStyle: {}, - statusBarTranslucent: true, - avoidKeyboard: false, - hideModalContentWhileAnimating: false, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index cc73a6fc8fd7..13ac5160b30b 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,8 +1,9 @@ +import lodashDebounce from 'lodash/debounce'; import lodashGet from 'lodash/get'; +import lodashIsEqual from 'lodash/isEqual'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; @@ -77,9 +78,9 @@ class BaseOptionsSelector extends Component { this.calculateAllVisibleOptionsCount = this.calculateAllVisibleOptionsCount.bind(this); this.handleFocusIn = this.handleFocusIn.bind(this); this.handleFocusOut = this.handleFocusOut.bind(this); - this.debouncedUpdateSearchValue = _.debounce(this.updateSearchValue, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME); + this.debouncedUpdateSearchValue = lodashDebounce(this.updateSearchValue, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME); this.relatedTarget = null; - this.accessibilityRoles = _.values(CONST.ROLE); + this.accessibilityRoles = Object.values(CONST.ROLE); this.isWebOrDesktop = [CONST.PLATFORM.DESKTOP, CONST.PLATFORM.WEB].includes(getPlatform()); const allOptions = this.flattenSections(); @@ -155,7 +156,7 @@ class BaseOptionsSelector extends Component { this.focusedOption = this.state.allOptions[this.state.focusedIndex]; } - if (_.isEqual(this.props.sections, prevProps.sections)) { + if (lodashIsEqual(this.props.sections, prevProps.sections)) { return; } @@ -171,14 +172,14 @@ class BaseOptionsSelector extends Component { } const newFocusedIndex = this.props.selectedOptions.length; const isNewFocusedIndex = newFocusedIndex !== this.state.focusedIndex; - const prevFocusedOption = _.find(newOptions, (option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList); - const prevFocusedOptionIndex = prevFocusedOption ? _.findIndex(newOptions, (option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList) : undefined; + const prevFocusedOption = newOptions.find((option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList); + const prevFocusedOptionIndex = prevFocusedOption ? newOptions.findIndex((option) => this.focusedOption && option.keyForList === this.focusedOption.keyForList) : undefined; // eslint-disable-next-line react/no-did-update-set-state this.setState( { sections: newSections, allOptions: newOptions, - focusedIndex: prevFocusedOptionIndex || (_.isNumber(this.props.focusedIndex) ? this.props.focusedIndex : newFocusedIndex), + focusedIndex: prevFocusedOptionIndex || (typeof this.props.focusedIndex === 'number' ? this.props.focusedIndex : newFocusedIndex), }, () => { // If we just toggled an option on a multi-selection page or cleared the search input, scroll to top @@ -230,11 +231,11 @@ class BaseOptionsSelector extends Component { } else { defaultIndex = this.props.selectedOptions.length; } - if (_.isUndefined(this.props.initiallyFocusedOptionKey)) { + if (typeof this.props.initiallyFocusedOptionKey === 'undefined') { return defaultIndex; } - const indexOfInitiallyFocusedOption = _.findIndex(allOptions, (option) => option.keyForList === this.props.initiallyFocusedOptionKey); + const indexOfInitiallyFocusedOption = allOptions.findIndex((option) => option.keyForList === this.props.initiallyFocusedOptionKey); return indexOfInitiallyFocusedOption; } @@ -245,8 +246,8 @@ class BaseOptionsSelector extends Component { * @returns {Objects[]} */ sliceSections() { - return _.map(this.props.sections, (section) => { - if (_.isEmpty(section.data)) { + return this.props.sections.map((section) => { + if (section.data.length === 0) { return section; } @@ -266,7 +267,7 @@ class BaseOptionsSelector extends Component { calculateAllVisibleOptionsCount() { let count = 0; - _.forEach(this.state.sections, (section) => { + this.state.sections.forEach((section) => { count += lodashGet(section, 'data.length', 0); }); @@ -347,7 +348,7 @@ class BaseOptionsSelector extends Component { selectFocusedOption(e) { const focusedItemKey = lodashGet(e, ['target', 'attributes', 'id', 'value']); - const focusedOption = focusedItemKey ? _.find(this.state.allOptions, (option) => option.keyForList === focusedItemKey) : this.state.allOptions[this.state.focusedIndex]; + const focusedOption = focusedItemKey ? this.state.allOptions.find((option) => option.keyForList === focusedItemKey) : this.state.allOptions[this.state.focusedIndex]; if (!focusedOption || !this.props.isFocused) { return; @@ -393,8 +394,8 @@ class BaseOptionsSelector extends Component { const allOptions = []; this.disabledOptionsIndexes = []; let index = 0; - _.each(this.props.sections, (section, sectionIndex) => { - _.each(section.data, (option, optionIndex) => { + this.props.sections.forEach((section, sectionIndex) => { + section.data.forEach((option, optionIndex) => { allOptions.push({ ...option, sectionIndex, @@ -496,8 +497,8 @@ class BaseOptionsSelector extends Component { render() { const shouldShowShowMoreButton = this.state.allOptions.length > CONST.MAX_OPTIONS_SELECTOR_PAGE_LENGTH * this.state.paginationPage; const shouldShowFooter = - !this.props.isReadOnly && (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && _.isEmpty(this.props.selectedOptions)); - const defaultConfirmButtonText = _.isUndefined(this.props.confirmButtonText) ? this.props.translate('common.confirm') : this.props.confirmButtonText; + !this.props.isReadOnly && (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && this.props.selectedOptions.length === 0); + const defaultConfirmButtonText = typeof this.props.confirmButtonText === 'undefined' ? this.props.translate('common.confirm') : this.props.confirmButtonText; const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; const listContainerStyles = this.props.listContainerStyles || [this.props.themeStyles.flex1]; diff --git a/src/components/Popover/popoverPropTypes.js b/src/components/Popover/popoverPropTypes.js deleted file mode 100644 index c758c4e6d311..000000000000 --- a/src/components/Popover/popoverPropTypes.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import {defaultProps as defaultModalProps, propTypes as modalPropTypes} from '@components/Modal/modalPropTypes'; -import refPropTypes from '@components/refPropTypes'; -import CONST from '@src/CONST'; - -const propTypes = { - ..._.omit(modalPropTypes, ['type', 'popoverAnchorPosition']), - - /** The anchor position of the popover */ - anchorPosition: PropTypes.shape({ - top: PropTypes.number, - right: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - }), - - /** The anchor ref of the popover */ - anchorRef: refPropTypes, - - /** A react-native-animatable animation timing for the modal display animation. */ - animationInTiming: PropTypes.number, - - /** Whether disable the animations */ - disableAnimation: PropTypes.bool, - - /** The ref of the popover */ - withoutOverlayRef: refPropTypes, - - /** Whether we want to show the popover on the right side of the screen */ - fromSidebarMediumScreen: PropTypes.bool, -}; - -const defaultProps = { - ..._.omit(defaultModalProps, ['type', 'popoverAnchorPosition']), - - animationIn: 'fadeIn', - animationOut: 'fadeOut', - animationInTiming: CONST.ANIMATED_TRANSITION, - - // Anchor position is optional only because it is not relevant on mobile - anchorPosition: {}, - anchorRef: () => {}, - disableAnimation: true, - withoutOverlayRef: () => {}, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js deleted file mode 100644 index 80ae1edd5176..000000000000 --- a/src/components/menuItemPropTypes.js +++ /dev/null @@ -1,179 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import stylePropTypes from '@styles/stylePropTypes'; -import CONST from '@src/CONST'; -import avatarPropTypes from './avatarPropTypes'; -import sourcePropTypes from './Image/sourcePropTypes'; -import refPropTypes from './refPropTypes'; - -const propTypes = { - /** Text to be shown as badge near the right end. */ - badgeText: PropTypes.string, - - /** Any additional styles to apply */ - // eslint-disable-next-line react/forbid-prop-types - wrapperStyle: stylePropTypes, - - /** Used to apply offline styles to child text components */ - style: stylePropTypes, - - /** Used to apply styles specifically to the title */ - titleStyle: stylePropTypes, - - /** Function to fire when component is pressed */ - onPress: PropTypes.func, - - /** Icon to display on the left side of component */ - icon: PropTypes.oneOfType([PropTypes.string, sourcePropTypes, PropTypes.arrayOf(avatarPropTypes)]), - - /** Secondary icon to display on the left side of component, right of the icon */ - secondaryIcon: sourcePropTypes, - - /** Icon Width */ - iconWidth: PropTypes.number, - - /** Icon Height */ - iconHeight: PropTypes.number, - - /** Text to display for the item */ - title: PropTypes.string, - - /** Text that appears above the title */ - label: PropTypes.string, - - /** Boolean whether to display the title right icon */ - shouldShowTitleIcon: PropTypes.bool, - - /** Icon to display at right side of title */ - titleIcon: sourcePropTypes, - - /** Boolean whether to display the right icon */ - shouldShowRightIcon: PropTypes.bool, - - /** Should we make this selectable with a checkbox */ - shouldShowSelectedState: PropTypes.bool, - - /** Should the title show with normal font weight (not bold) */ - shouldShowBasicTitle: PropTypes.bool, - - /** Should the description be shown above the title (instead of the other way around) */ - shouldShowDescriptionOnTop: PropTypes.bool, - - /** Whether this item is selected */ - isSelected: PropTypes.bool, - - /** A boolean flag that gives the icon a green fill if true */ - success: PropTypes.bool, - - /** Overrides the icon for shouldShowRightIcon */ - iconRight: sourcePropTypes, - - /** A description text to show under the title */ - description: PropTypes.string, - - /** Any additional styles to pass to the icon container. */ - iconStyles: PropTypes.arrayOf(PropTypes.object), - - /** The fill color to pass into the icon. */ - iconFill: PropTypes.string, - - /** The fill color to pass into the secondary icon. */ - secondaryIconFill: PropTypes.string, - - /** Whether item is focused or active */ - focused: PropTypes.bool, - - /** Should we disable this menu item? */ - disabled: PropTypes.bool, - - /** A right-aligned subtitle for this menu option */ - subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - - /** Flag to choose between avatar image or an icon */ - iconType: PropTypes.oneOf([CONST.ICON_TYPE_AVATAR, CONST.ICON_TYPE_ICON, CONST.ICON_TYPE_WORKSPACE]), - - /** Whether the menu item should be interactive at all */ - interactive: PropTypes.bool, - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon: PropTypes.oneOfType([PropTypes.string, sourcePropTypes]), - - /** Avatars to show on the right of the menu item */ - floatRightAvatars: PropTypes.arrayOf(avatarPropTypes), - - /** The type of brick road indicator to show. */ - brickRoadIndicator: PropTypes.oneOf([CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, CONST.BRICK_ROAD_INDICATOR_STATUS.INFO, '']), - - /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: PropTypes.bool, - - /** Prop to represent the size of the float right avatar images to be shown */ - floatRightAvatarSize: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), - - /** Prop to represent the size of the avatar images to be shown */ - avatarSize: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)), - - /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction: PropTypes.func, - - /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ - shouldBlockSelection: PropTypes.bool, - - /** The ref to the menu item */ - forwardedRef: refPropTypes, - - /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: PropTypes.arrayOf(PropTypes.object), - - /** Text to display under the main item */ - furtherDetails: PropTypes.string, - - /** An icon to display under the main item */ - furtherDetailsIcon: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]), - - /** The action accept for anonymous user or not */ - isAnonymousAction: PropTypes.bool, - - /** Whether we should use small avatar subscript sizing the for menu item */ - isSmallAvatarSubscriptMenu: PropTypes.bool, - - /** The max number of lines the title text should occupy before ellipses are added */ - numberOfLines: PropTypes.number, - - /** Should we grey out the menu item when it is disabled? */ - shouldGreyOutWhenDisabled: PropTypes.bool, - - /** Error to display below the title */ - error: PropTypes.string, - - /** Should render the content in HTML format */ - shouldRenderAsHTML: PropTypes.bool, - - /** Label to be displayed on the right */ - rightLabel: PropTypes.string, - - /** Component to be displayed on the right */ - rightComponent: PropTypes.node, - - /** Should render component on the right */ - shouldShowRightComponent: PropTypes.bool, - - /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: PropTypes.arrayOf(PropTypes.object), - - /** Should check anonymous user in onPress function */ - shouldCheckActionAllowedOnPress: PropTypes.bool, - - shouldPutLeftPaddingWhenNoIcon: PropTypes.bool, - - /** The menu item link or function to get the link */ - link: PropTypes.oneOfType(PropTypes.func, PropTypes.string), - - /** Icon should be displayed in its own color */ - displayInDefaultIconColor: PropTypes.bool, - - /** Is this menu item in the settings pane */ - isPaneMenu: PropTypes.bool, -}; - -export default propTypes; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 7380bf102331..b1b9449fa92b 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -299,7 +299,6 @@ const FlagCommentStackNavigator = createModalStackNavigator({ - [SCREENS.EDIT_REQUEST.ROOT]: () => require('../../../../pages/EditRequestPage').default as React.ComponentType, [SCREENS.EDIT_REQUEST.REPORT_FIELD]: () => require('../../../../pages/EditReportFieldPage').default as React.ComponentType, }); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6165ccb16fa3..ec8b1fcdc2b5 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -618,7 +618,6 @@ const config: LinkingOptions['config'] = { }, [SCREENS.RIGHT_MODAL.EDIT_REQUEST]: { screens: { - [SCREENS.EDIT_REQUEST.ROOT]: ROUTES.EDIT_REQUEST.route, [SCREENS.EDIT_REQUEST.REPORT_FIELD]: ROUTES.EDIT_REPORT_FIELD_REQUEST.route, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8273278f971e..a8e95be68e6d 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -584,10 +584,7 @@ type FlagCommentNavigatorParamList = { }; type EditRequestNavigatorParamList = { - [SCREENS.EDIT_REQUEST.ROOT]: { - field: string; - threadReportID: string; - }; + [SCREENS.EDIT_REQUEST.REPORT_FIELD]: undefined; }; type SignInNavigatorParamList = { diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js deleted file mode 100644 index d3941dca044e..000000000000 --- a/src/pages/EditRequestPage.js +++ /dev/null @@ -1,189 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import categoryPropTypes from '@components/categoryPropTypes'; -import ScreenWrapper from '@components/ScreenWrapper'; -import tagPropTypes from '@components/tagPropTypes'; -import transactionPropTypes from '@components/transactionPropTypes'; -import compose from '@libs/compose'; -import * as IOUUtils from '@libs/IOUUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import EditRequestReceiptPage from './EditRequestReceiptPage'; -import EditRequestTagPage from './EditRequestTagPage'; -import reportActionPropTypes from './home/report/reportActionPropTypes'; -import reportPropTypes from './reportPropTypes'; -import {policyPropTypes} from './workspace/withPolicy'; - -const propTypes = { - /** Route from navigation */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** Which field we are editing */ - field: PropTypes.string, - - /** reportID for the "transaction thread" */ - threadReportID: PropTypes.string, - - /** Indicates which tag list index was selected */ - tagIndex: PropTypes.string, - }), - }).isRequired, - - /** Onyx props */ - /** The report object for the thread report */ - report: reportPropTypes, - - /** The policy of the report */ - policy: policyPropTypes.policy, - - /** Collection of categories attached to a policy */ - policyCategories: PropTypes.objectOf(categoryPropTypes), - - /** Collection of tags attached to a policy */ - policyTags: tagPropTypes, - - /** The actions from the parent report */ - parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - - /** Transaction that stores the request data */ - transaction: transactionPropTypes, -}; - -const defaultProps = { - report: {}, - policy: {}, - policyCategories: {}, - policyTags: {}, - parentReportActions: {}, - transaction: {}, -}; - -function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { - const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); - const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); - const {tag: transactionTag} = ReportUtils.getTransactionDetails(transaction); - - const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const tagListIndex = Number(lodashGet(route, ['params', 'tagIndex'], undefined)); - - const tag = TransactionUtils.getTag(transaction, tagListIndex); - const policyTagListName = PolicyUtils.getTagListName(policyTags, tagListIndex); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - - // A flag for verifying that the current report is a sub-report of a workspace chat - const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); - - // A flag for showing the tags page - const shouldShowTags = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, transactionTag]); - - // Decides whether to allow or disallow editing a money request - useEffect(() => { - // Do not dismiss the modal, when a current user can edit this property of the money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, fieldToEdit)) { - return; - } - - // Dismiss the modal when a current user cannot edit a money request. - Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(); - }); - }, [parentReportAction, fieldToEdit]); - - const saveTag = useCallback( - ({tag: newTag}) => { - let updatedTag = newTag; - if (newTag === tag) { - // In case the same tag has been selected, reset the tag. - updatedTag = ''; - } - IOU.updateMoneyRequestTag( - transaction.transactionID, - report.reportID, - IOUUtils.insertTagIntoTransactionTagsString(transactionTag, updatedTag, tagListIndex), - policy, - policyTags, - policyCategories, - ); - Navigation.dismissModal(); - }, - [tag, transaction.transactionID, report.reportID, transactionTag, tagListIndex, policy, policyTags, policyCategories], - ); - - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAG && shouldShowTags) { - return ( - - ); - } - - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { - return ( - - ); - } - - return ( - - - - ); -} - -EditRequestPage.displayName = 'EditRequestPage'; -EditRequestPage.propTypes = propTypes; -EditRequestPage.defaultProps = defaultProps; -export default compose( - withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, - policyCategories: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, - }, - policyTags: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, - }, - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, - canEvict: false, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({report, parentReportActions}) => { - const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); - const parentReportAction = lodashGet(parentReportActions, parentReportActionID); - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - }), -)(EditRequestPage); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 87883972f84f..a91bf03c6ed4 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -1,8 +1,10 @@ import lodashGet from 'lodash/get'; +import lodashIsEqual from 'lodash/isEqual'; +import lodashPick from 'lodash/pick'; +import lodashReject from 'lodash/reject'; import PropTypes from 'prop-types'; import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import BlockingView from '@components/BlockingViews/BlockingView'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -51,13 +53,13 @@ const propTypes = { ), /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)).isRequired, + iouType: PropTypes.oneOf(Object.values(CONST.IOU.TYPE)).isRequired, /** The request type, ie. manual, scan, distance */ - iouRequestType: PropTypes.oneOf(_.values(CONST.IOU.REQUEST_TYPE)).isRequired, + iouRequestType: PropTypes.oneOf(Object.values(CONST.IOU.REQUEST_TYPE)).isRequired, /** The action of the IOU, i.e. create, split, move */ - action: PropTypes.oneOf(_.values(CONST.IOU.ACTION)), + action: PropTypes.oneOf(Object.values(CONST.IOU.ACTION)), }; const defaultProps = { @@ -141,21 +143,21 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF newSections.push({ title: translate('common.recents'), data: chatOptions.recentReports, - shouldShow: !_.isEmpty(chatOptions.recentReports), + shouldShow: chatOptions.recentReports.length > 0, }); if (![CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action)) { newSections.push({ title: translate('common.contacts'), data: chatOptions.personalDetails, - shouldShow: !_.isEmpty(chatOptions.personalDetails), + shouldShow: chatOptions.personalDetails.length > 0, }); } if (chatOptions.userToInvite && !OptionsListUtils.isCurrentUser(chatOptions.userToInvite)) { newSections.push({ title: undefined, - data: _.map([chatOptions.userToInvite], (participant) => { + data: [chatOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); }), @@ -190,7 +192,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF (option) => { onParticipantsAdded([ { - ..._.pick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), + ...lodashPick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText'), selected: true, iouType, }, @@ -218,11 +220,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF return false; }; - const isOptionInList = _.some(participants, isOptionSelected); + const isOptionInList = participants.some(isOptionSelected); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(participants, isOptionSelected); + newSelectedOptions = lodashReject(participants, isOptionSelected); } else { newSelectedOptions = [ ...participants, @@ -247,11 +249,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF const headerMessage = useMemo( () => OptionsListUtils.getHeaderMessage( - _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0, + lodashGet(newChatOptions, 'personalDetails', []).length + lodashGet(newChatOptions, 'recentReports', []).length !== 0, Boolean(newChatOptions.userToInvite), debouncedSearchTerm.trim(), maxParticipantsReached, - _.some(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), + participants.some((participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())), ), [maxParticipantsReached, newChatOptions, participants, debouncedSearchTerm], ); @@ -259,7 +261,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF // Right now you can't split a request with a workspace and other additional participants // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants - const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); + const hasPolicyExpenseChatParticipant = participants.some((participant) => participant.isPolicyExpenseChat); const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant; // canUseP2PDistanceRequests is true if the iouType is track expense, but we don't want to allow splitting distance with track expense yet @@ -377,7 +379,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF ); - const isAllSectionsEmpty = _.every(sections, (section) => section.data.length === 0); + const isAllSectionsEmpty = sections.every((section) => section.data.length === 0); if ([CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action) && isAllSectionsEmpty && didScreenTransitionEnd && searchTerm.trim() === '') { return renderEmptyWorkspaceView(); } @@ -408,8 +410,8 @@ MoneyTemporaryForRefactorRequestParticipantsSelector.displayName = 'MoneyTempora export default memo( MoneyTemporaryForRefactorRequestParticipantsSelector, (prevProps, nextProps) => - _.isEqual(prevProps.participants, nextProps.participants) && + lodashIsEqual(prevProps.participants, nextProps.participants) && prevProps.iouRequestType === nextProps.iouRequestType && prevProps.iouType === nextProps.iouType && - _.isEqual(prevProps.betas, nextProps.betas), + lodashIsEqual(prevProps.betas, nextProps.betas), ); diff --git a/src/pages/iou/request/step/IOURequestStepRoutePropTypes.js b/src/pages/iou/request/step/IOURequestStepRoutePropTypes.js deleted file mode 100644 index 8b191fa0b58e..000000000000 --- a/src/pages/iou/request/step/IOURequestStepRoutePropTypes.js +++ /dev/null @@ -1,29 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import CONST from '@src/CONST'; - -export default PropTypes.shape({ - /** Route specific parameters used on this screen via route :iouType/new/category/:reportID? */ - params: PropTypes.shape({ - /** What action is being performed, ie. create, edit */ - action: PropTypes.oneOf(_.values(CONST.IOU.ACTION)), - - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)).isRequired, - - /** The ID of the transaction being configured */ - transactionID: PropTypes.string.isRequired, - - /** The report ID of the IOU */ - reportID: PropTypes.string.isRequired, - - /** Index of the waypoint being edited */ - pageIndex: PropTypes.string, - - /** A path to go to when the user presses the back button */ - backTo: PropTypes.string, - - /** Indicates which tag list index was selected */ - tagIndex: PropTypes.string, - }), -}); diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js deleted file mode 100644 index 7422bad8061f..000000000000 --- a/src/pages/reportPropTypes.js +++ /dev/null @@ -1,79 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import avatarPropTypes from '@components/avatarPropTypes'; -import CONST from '@src/CONST'; - -export default PropTypes.shape({ - /** The specific type of chat */ - chatType: PropTypes.oneOf(['', ..._.values(CONST.REPORT.CHAT_TYPE)]), - - /** List of icons for report participants */ - icons: PropTypes.arrayOf(avatarPropTypes), - - /** Whether the user is not an admin of policyExpenseChat chat */ - isOwnPolicyExpenseChat: PropTypes.bool, - - /** Indicates if the report is pinned to the LHN or not */ - isPinned: PropTypes.bool, - - /** Whether we're waiting on submitter to add a bank account */ - isWaitingOnBankAccount: PropTypes.bool, - - /** The accountID of the last message's actor */ - lastActorAccountID: PropTypes.number, - - /** The text of the last message on the report */ - lastMessageText: PropTypes.string, - - /** The time of the last message on the report */ - lastVisibleActionCreated: PropTypes.string, - - /** The time when user read the last message */ - lastReadTime: PropTypes.string, - - /** The current user's notification preference for this report */ - notificationPreference: PropTypes.oneOfType([ - // Some old reports have numbers for the notification preference - PropTypes.number, - PropTypes.string, - ]), - - /** The policy name to use for an archived report */ - oldPolicyName: PropTypes.string, - - /** The accountID of the report owner */ - ownerAccountID: PropTypes.number, - - /** List of accountIDs of participants of the report */ - participantAccountIDs: PropTypes.arrayOf(PropTypes.number), - - /** List of accountIDs of visible members of the report */ - visibleChatMemberAccountIDs: PropTypes.arrayOf(PropTypes.number), - - /** Linked policy's ID */ - policyID: PropTypes.string, - - /** Name of the report */ - reportName: PropTypes.string, - - /** ID of the report */ - reportID: PropTypes.string, - - /** The state that the report is currently in */ - stateNum: PropTypes.oneOf(_.values(CONST.REPORT.STATE_NUM)), - - /** The status of the current report */ - statusNum: PropTypes.oneOf(_.values(CONST.REPORT.STATUS_NUM)), - - /** Which user role is capable of posting messages on the report */ - writeCapability: PropTypes.oneOf(_.values(CONST.REPORT.WRITE_CAPABILITIES)), - - /** Field-specific pending states for offline UI status */ - pendingFields: PropTypes.objectOf(PropTypes.string), - - /** Custom fields attached to the report */ - reportFields: PropTypes.objectOf(PropTypes.string), - - /** ID of the transaction thread associated with the report, if any */ - transactionThreadReportID: PropTypes.string, -}); From 4005fd412d03b55f9e8228616dccc7409eb015f3 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 17 Apr 2024 13:16:54 +0100 Subject: [PATCH 0499/1357] Update src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx Co-authored-by: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> --- .../accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx index 8d850115d14a..0b3a9affd5df 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx @@ -82,7 +82,7 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyProps) { Date: Wed, 17 Apr 2024 13:18:44 +0100 Subject: [PATCH 0500/1357] Update src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx Co-authored-by: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> --- .../accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx index 0b3a9affd5df..5f7d2c4b267b 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx @@ -39,7 +39,7 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyProps) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const accountOptions = useMemo(() => DRAFT || [...(bankAccounts ?? []), ...(creditCards ?? [])], [bankAccounts, creditCards]); - const qboOnlineSelectorOptions = useMemo( + const qboOnlineSelectorOptions = useMemo( () => accountOptions?.map(({id, name}) => ({ value: id, From 03c86ae9d72ac7f307773558d34218e9c77832f9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 17 Apr 2024 13:18:52 +0100 Subject: [PATCH 0501/1357] Update src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx Co-authored-by: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> --- .../accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx index 5f7d2c4b267b..08bb5782cec2 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx @@ -44,8 +44,8 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyProps) { accountOptions?.map(({id, name}) => ({ value: id, text: name, - keyForList: name, - isSelected: selectedAccount === name, + keyForList: id, + isSelected: selectedAccount === id, })), [selectedAccount, accountOptions], ); From 6db22e200e3c0ca660000d34303c52fa6c04acf0 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 14:26:15 +0200 Subject: [PATCH 0502/1357] Add INVOICE_RECEIVER_TYPE to consts --- src/CONST.ts | 9 +++++++++ src/types/onyx/Report.ts | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa66b916ab7c..08ba93053685 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -839,6 +839,10 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + BUSINESS: 'policy', + }, }, NEXT_STEP: { FINISHED: 'Finished!', @@ -4353,6 +4357,11 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, + + INVOICE_RECEIVER_TYPE: { + INDIVIDUAL: 'individual', + POLICY: 'policy', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 344b7df5b2eb..51e3ce5fb37d 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -30,11 +30,11 @@ type Participant = { type InvoiceReceiver = | { - type: 'individual'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; accountID: number; } | { - type: 'policy'; + type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS; policyID: string; }; From 173432f67cb35058def61c27c6ab78d90b6087d2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 17 Apr 2024 14:37:20 +0200 Subject: [PATCH 0503/1357] Code improvements --- src/CONST.ts | 5 -- ...raryForRefactorRequestConfirmationList.tsx | 5 +- src/libs/actions/IOU.ts | 67 +++++++++---------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 08ba93053685..6467769263dd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4357,11 +4357,6 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, - - INVOICE_RECEIVER_TYPE: { - INDIVIDUAL: 'individual', - POLICY: 'policy', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx index e41c6cc96085..7b444be18da9 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx @@ -256,10 +256,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; }, [allPolicies, pickedParticipants]); - const canUpdateSenderWorkspace = useMemo( - () => PolicyUtils.getActiveAdminWorkspaces(allPolicies).length > 0 && !!transaction?.isFromGlobalCreate, - [allPolicies, transaction?.isFromGlobalCreate], - ); + const canUpdateSenderWorkspace = useMemo(() => PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate, [allPolicies, transaction?.isFromGlobalCreate]); // A flag for showing the tags field const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 130a43a79319..d6be557b8107 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -863,24 +863,7 @@ function buildOnyxDataForInvoice( policyCategories?: OnyxEntry, ): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); - const optimisticData: OnyxUpdate[] = []; - - if (chatReport) { - optimisticData.push({ - // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page - onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - lastMessageTranslationKey: '', - iouReportID: iouReport.reportID, - ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), - }, - }); - } - - optimisticData.push( + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -950,7 +933,22 @@ function buildOnyxDataForInvoice( key: `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, value: null, }, - ); + ]; + + if (chatReport) { + optimisticData.push({ + // Use SET for new reports because it doesn't exist yet, is faster and we need the data to be available when we navigate to the chat page + onyxMethod: isNewChatReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + lastMessageTranslationKey: '', + iouReportID: iouReport.reportID, + ...(isNewChatReport ? {pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}} : {}), + }, + }); + } if (optimisticPolicyRecentlyUsedCategories.length) { optimisticData.push({ @@ -968,21 +966,7 @@ function buildOnyxDataForInvoice( }); } - const successData: OnyxUpdate[] = []; - - if (isNewChatReport) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, - value: { - pendingFields: null, - errorFields: null, - isOptimisticReport: false, - }, - }); - } - - successData.push( + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, @@ -1007,7 +991,6 @@ function buildOnyxDataForInvoice( pendingFields: clearedPendingFields, }, }, - { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, @@ -1049,7 +1032,19 @@ function buildOnyxDataForInvoice( }, }, }, - ); + ]; + + if (isNewChatReport) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, + value: { + pendingFields: null, + errorFields: null, + isOptimisticReport: false, + }, + }); + } const errorKey = DateUtils.getMicroseconds(); From 78c43337640db7cee80a50fd62b9300b64736181 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 17 Apr 2024 13:52:30 +0100 Subject: [PATCH 0504/1357] update defaults --- .../qbo/advanced/QuickbooksAccountSelectPage.tsx | 2 +- .../advanced/QuickbooksInvoiceAccountSelectPage.tsx | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx index 08bb5782cec2..5449b8f6f905 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAccountSelectPage.tsx @@ -32,7 +32,7 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const selectedAccount = DRAFT[0].name; // selected + const selectedAccount = DRAFT[0].id; // selected const policyID = policy?.id ?? ''; const {bankAccounts, creditCards} = policy?.connections?.quickbooksOnline?.data ?? {}; diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx index b708737f5b56..af5b17fe04f5 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx @@ -32,7 +32,7 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const selectedAccount = DRAFT[1].name; // selected + const selectedAccount = DRAFT[1].id; // selected const policyID = policy?.id ?? ''; const {bankAccounts, otherCurrentAssetAccounts} = policy?.connections?.quickbooksOnline?.data ?? {}; @@ -41,11 +41,11 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyProps) { const qboOnlineSelectorOptions = useMemo( () => - accountOptions?.map(({name}) => ({ - value: name, + accountOptions?.map(({id,name}) => ({ + value: id, text: name, - keyForList: name, - isSelected: selectedAccount === name, + keyForList: id, + isSelected: selectedAccount === id, })), [selectedAccount, accountOptions], ); @@ -83,7 +83,7 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyProps) { Date: Wed, 17 Apr 2024 14:14:25 +0100 Subject: [PATCH 0505/1357] fix lint --- .../accounting/qbo/advanced/QuickbooksAdvancedPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx index fdca8cff1e4b..e83cab15a433 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx @@ -34,8 +34,8 @@ function QuickbooksAdvancedPage({policy}: WithPolicyProps) { { title: translate('workspace.qbo.advancedConfig.autoSync'), subtitle: translate('workspace.qbo.advancedConfig.autoSyncDescription'), - isActive: Boolean(autoSync), - onToggle: () => Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.AUTO_SYNC, !autoSync), + isActive: Boolean(autoSync?.enabled), + onToggle: () => Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.AUTO_SYNC, !autoSync?.enabled), pendingAction: pendingFields?.autoSync, }, { @@ -116,7 +116,7 @@ function QuickbooksAdvancedPage({policy}: WithPolicyProps) { Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR.getRoute(policyID)))} From 2e7d5c072cd802d2b92bb9d9519b4730d1b3be29 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 15:30:05 +0200 Subject: [PATCH 0506/1357] fix task video assigning --- src/libs/actions/Report.ts | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index e95132418068..9530fe40d1e7 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -116,6 +116,8 @@ type Video = { height: number; }; +type TaskMessage = Required>; + type TaskForParameters = | { type: 'task'; @@ -127,25 +129,16 @@ type TaskForParameters = createdTaskReportActionID: string; title: string; description: string; - video?: Video; } - | { + | ({ type: 'message'; - reportID: string; - reportActionID: string; - reportComment: string; - }; - -type TaskMessage = Required>; - -type GuidedSetupData = Array< - | AddCommentOrAttachementParams - | TaskForParameters + } & TaskMessage) | ({ type: 'video'; - } & Video & - AddCommentOrAttachementParams) ->; + } & TaskMessage & + Video); + +type GuidedSetupData = Array; let conciergeChatReportID: string | undefined; let currentUserAccountID = -1; @@ -3095,7 +3088,6 @@ function completeOnboarding( createdTaskReportActionID: taskCreatedAction.reportActionID, title: currentTask.reportName ?? '', description: currentTask.description ?? '', - video: task.video ?? undefined, }, { type: 'message', @@ -3118,9 +3110,9 @@ function completeOnboarding( }); } - if (taskVideoComment) { + if (taskVideoComment && task.video) { const taskVideoCommentAction: OptimisticAddCommentReportAction = taskVideoComment.reportAction; - const taskVideoCommentText = instructionComment.commentText; + const taskVideoCommentText = taskVideoComment.commentText; const taskVideoMessage: TaskMessage = { reportID: currentTask.reportID, reportActionID: taskVideoCommentAction.reportActionID, @@ -3128,7 +3120,8 @@ function completeOnboarding( }; tasksForParametersAcc.push({ - type: 'message', + type: 'video', + ...task.video, ...taskVideoMessage, }); } From c5ccafe95cc58fde48d4d102ff2be07183016fd4 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 17 Apr 2024 14:33:16 +0100 Subject: [PATCH 0507/1357] use otherCurrentAssetAccounts and bankAccounts --- .../qbo/advanced/QuickbooksAdvancedPage.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx index e83cab15a433..333566cdff41 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx @@ -26,9 +26,10 @@ function QuickbooksAdvancedPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const policyID = policy?.id ?? ''; - const {autoSync, syncPeople, autoCreateVendor, reimbursementAccountID, collectionAccountID, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {}; - const {bankAccounts, creditCards} = policy?.connections?.quickbooksOnline?.data ?? {}; + const {autoSync, syncPeople, autoCreateVendor, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {}; + const {bankAccounts, creditCards, otherCurrentAssetAccounts} = policy?.connections?.quickbooksOnline?.data ?? {}; const accountOptions = [...(bankAccounts ?? []), ...(creditCards ?? [])]; + const invoiceAccountOptions = [...(bankAccounts ?? []), ...(otherCurrentAssetAccounts ?? [])]; const qboToggleSettingItems: ToggleSettingOptionRowProps[] = [ { @@ -96,8 +97,15 @@ function QuickbooksAdvancedPage({policy}: WithPolicyProps) { wrapperStyle={styles.mv3} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing pendingAction={pendingFields?.reimbursementAccountID || pendingFields?.collectionAccountID} - isActive={Boolean(reimbursementAccountID && collectionAccountID)} - onToggle={() => Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.REIMBURSEMENT_ACCOUNT_ID, accountOptions[0].id)} + isActive={Boolean(accountOptions[0]?.id)} // TODO + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + onToggle={() => + Policy.updatePolicyConnectionConfig( + policyID, + CONST.QUICK_BOOKS_CONFIG.REIMBURSEMENT_ACCOUNT_ID || CONST.QUICK_BOOKS_CONFIG.COLLECTION_ACCOUNT_ID, + accountOptions[0]?.id, + ) + } /> Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.COLLECTION_ACCOUNT_ID, !collectionAccountID)} + isActive={Boolean(invoiceAccountOptions[0]?.id)} // TODO + onToggle={() => Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.COLLECTION_ACCOUNT_ID, invoiceAccountOptions[0]?.id)} /> Date: Wed, 17 Apr 2024 14:35:35 +0100 Subject: [PATCH 0508/1357] fix lint --- .../qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx index af5b17fe04f5..392288861eb5 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage.tsx @@ -41,7 +41,7 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyProps) { const qboOnlineSelectorOptions = useMemo( () => - accountOptions?.map(({id,name}) => ({ + accountOptions?.map(({id, name}) => ({ value: id, text: name, keyForList: id, From cb102de3d9c50658c3f52af431eb1e23f7dd4f14 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 15:40:12 +0200 Subject: [PATCH 0509/1357] minor improvement --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index e89b3048d8cb..82e4eab5c2df 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2973,7 +2973,7 @@ function getReportPrivateNote(reportID: string | undefined) { } function completeOnboarding( - engagementChoice: string, + engagementChoice: OnboardingPurposeType, data: ValueOf, { login, From 9e42ed8bc6905ff73a6c8c1299147386326f82f1 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 17 Apr 2024 16:42:24 +0300 Subject: [PATCH 0510/1357] add errors for out of pockets, design review, remove redundant screen --- src/ROUTES.ts | 4 -- src/SCREENS.ts | 1 - src/languages/en.ts | 3 ++ src/languages/es.ts | 3 ++ .../ModalStackNavigators/index.tsx | 2 - .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 - src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 3 -- ...oksCompanyCardExpenseAccountSelectPage.tsx | 7 +-- ...oksCompanyCardExpenseConfigurationPage.tsx | 45 ------------------- .../QuickbooksExportConfigurationPage.tsx | 8 ++-- ...oksOutOfPocketExpenseConfigurationPage.tsx | 13 ++++-- ...ooksOutOfPocketExpenseEntitySelectPage.tsx | 26 +++++++---- src/styles/index.ts | 6 +++ 14 files changed, 47 insertions(+), 76 deletions(-) delete mode 100644 src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 4b822d4c4f77..3e42e4db5251 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -491,10 +491,6 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE: { - route: 'settings/workspaces/:policyID/accounting/quickbooks-online/company-card-expense', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/company-card-expense` as const, - }, WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/company-card-expense-account-select', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/company-card-expense-account-select` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index ae49bcf47fde..6fdb1bcd2fd8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -264,7 +264,6 @@ const SCREENS = { QUICKBOOKS_ONLINE_EXPORT: 'Workspace_Accounting_Quickbooks_Online_Export', QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Date_Select', QUICKBOOKS_ONLINE_EXPORT_INVOICE_ACCOUNT_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Invoice_Account_Select', - QUICKBOOKS_ONLINE_EXPORT_COMPANY_CARD_EXPENSE: 'Workspace_Accounting_Quickbooks_Online_Export_Company_Card_Expense', QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Company_Card_Expense_Select', QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER: 'Workspace_Accounting_Quickbooks_Online_Export_Preferred_Exporter', QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES: 'Workspace_Accounting_Quickbooks_Online_Export_Out_Of_Pocket_Expenses', diff --git a/src/languages/en.ts b/src/languages/en.ts index bd75454fff47..1ba43084ad40 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1920,6 +1920,9 @@ export default { accountsPayableDescription: 'This is your chosen A/P account, against which vendor bills for each report are created.', journalEntry: 'Journal Entry', optionBelow: 'Choose an option below:', + vendorBillError: 'Vendor Bills are not available when locations are enabled. Please select a different export option.', + checkError: 'Check is not available when locations are enabled. Please select a different export option.', + journalEntryError: 'Journal entry is not available when taxes enabled. please select a different export option.', companyCardsLocationEnabledDescription: 'Note: QuickBooks Online does not support a field for Locations as Tags on Vendor Bills exports. As you import Locations from, this this export option is unavailable.', outOfPocketTaxEnabledDescription: diff --git a/src/languages/es.ts b/src/languages/es.ts index f630b70a36f0..d3dc7efeffd4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1947,6 +1947,9 @@ export default { 'Puede ser cualquier administrador del espacio de trabajo, pero debe ser un administrador de dominio si configura diferentes cuentas de exportación para tarjetas de empresa individuales en la configuración del dominio.', exportPreferredExporterSubNote: 'Una vez configurado, el exportador preferido verá los informes para exportar en su cuenta.', journalEntry: 'Asiento contable', + vendorBillError: 'Las facturas de proveedores no están disponibles cuando las ubicaciones están habilitadas. Seleccione una opción de exportación diferente.', + checkError: 'La verificación no está disponible cuando las ubicaciones están habilitadas. Seleccione una opción de exportación diferente.', + journalEntryError: 'El asiento de diario no está disponible cuando los impuestos están habilitados. seleccione una opción de exportación diferente.', exportOutOfPocketExpensesDescription: 'Establezca cómo se exportan los gastos de bolsillo a QuickBooks Online.', exportVendorBillDescription: 'Crearemos una única factura de proveedor detallada para cada informe de Expensify. Si el período de la factura está cerrado, lo publicaremos en el día 1 del siguiente período abierto. Puede agregar la factura del proveedor a la cuenta A/P de su elección (a continuación).', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 82e266984a11..301a55e70e20 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -266,8 +266,6 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage').default as React.ComponentType, - [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_COMPANY_CARD_EXPENSE]: () => - require('../../../../pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage').default as React.ComponentType, [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT]: () => require('../../../../pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage').default as React.ComponentType, [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER]: () => diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 53e03a4e751d..9f2a9808ed12 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -29,7 +29,6 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT, SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_INVOICE_ACCOUNT_SELECT, SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT, - SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_COMPANY_CARD_EXPENSE, SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT, SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER, SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 7d610ffd7310..ce111f69435d 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -279,7 +279,6 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT]: { path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT.route, }, - [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_COMPANY_CARD_EXPENSE]: {path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.route}, [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT]: { path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 42b3c288f37e..10b4f96d4594 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -718,9 +718,6 @@ type WorkspacesCentralPaneNavigatorParamList = { [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT]: { policyID: string; }; - [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_EXPORT_COMPANY_CARD_EXPENSE]: { - policyID: string; - }; [SCREENS.WORKSPACE.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT]: { policyID: string; }; diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx index 71dc3118ecc7..9b207868e83b 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx @@ -70,7 +70,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyProps if (row.value !== exportCompanyCard) { Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_COMPANY_CARD, row.value); } - Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.getRoute(policyID)); + Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT.getRoute(policyID)); }, [exportCompanyCard, policyID], ); @@ -81,14 +81,15 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyProps shouldEnableMaxHeight testID={QuickbooksCompanyCardExpenseAccountSelectPage.displayName} > - + {translate('workspace.qbo.exportCompanyCardsDescription')}} sections={sections} ListItem={RadioListItem} onSelectRow={onSelectRow} initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList} - footerContent={isLocationEnabled && {translate('workspace.qbo.companyCardsLocationEnabledDescription')}} + footerContent={isLocationEnabled && {translate('workspace.qbo.companyCardsLocationEnabledDescription')}} /> diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx deleted file mode 100644 index 3a1f40a0de32..000000000000 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseConfigurationPage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import ScreenWrapper from '@components/ScreenWrapper'; -import ScrollView from '@components/ScrollView'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import Navigation from '@navigation/Navigation'; -import withPolicy from '@pages/workspace/withPolicy'; -import type {WithPolicyProps} from '@pages/workspace/withPolicy'; -import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; - -function QuickbooksCompanyCardExpenseConfigurationPage({policy}: WithPolicyProps) { - const {translate} = useLocalize(); - const styles = useThemeStyles(); - const policyID = policy?.id ?? ''; - const {exportCompanyCard, errors} = policy?.connections?.quickbooksOnline?.config ?? {}; - return ( - - - - {translate('workspace.qbo.exportCompanyCardsDescription')} - - Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID))} - brickRoadIndicator={errors?.exportCompanyCard ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - shouldShowRightIcon - /> - - - - ); -} - -QuickbooksCompanyCardExpenseConfigurationPage.displayName = 'QuickbooksCompanyCardExpenseConfigurationPage'; - -export default withPolicy(QuickbooksCompanyCardExpenseConfigurationPage); diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx index 1a51d36c9ec2..cff82c6cad6a 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksExportConfigurationPage.tsx @@ -49,7 +49,7 @@ function QuickbooksExportConfigurationPage({policy}: WithPolicyProps) { }, { description: translate('workspace.qbo.exportCompany'), - onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE.getRoute(policyID)), + onPress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID)), brickRoadIndicator: errors?.exportCompanyCard ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, title: exportCompanyCard, }, @@ -80,11 +80,11 @@ function QuickbooksExportConfigurationPage({policy}: WithPolicyProps) { /> ))} - - {`${translate('workspace.qbo.deepDiveExpensifyCard')} `} + + {`${translate('workspace.qbo.deepDiveExpensifyCard')} `} Link.openExternalLink(CONST.DEEP_DIVE_EXPENSIFY_CARD)} - style={[styles.optionAlternateText, styles.textLabelSupporting, styles.link]} + style={[styles.mutedNormalTextLabel, styles.link]} > {translate('workspace.qbo.deepDiveExpensifyCardIntegration')} diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx index 9151afcca9d3..8741843066e3 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx @@ -17,8 +17,12 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyProps const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? ''; - const {syncLocations, exportAccount, exportEntity, errors} = policy?.connections?.quickbooksOnline?.config ?? {}; + const {syncLocations, exportAccount, exportEntity, errors, syncTaxes} = policy?.connections?.quickbooksOnline?.config ?? {}; const isLocationEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); + const isTaxesEnabled = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); + const showTaxError = isTaxesEnabled && exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY; + const showLocationError = isLocationEnabled && exportEntity !== CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY; + const brickEntityRoadIndicator = Boolean(errors?.exportEntity) || showTaxError || showLocationError; return ( Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT.getRoute(policyID))} - brickRoadIndicator={errors?.exportEntity ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + brickRoadIndicator={brickEntityRoadIndicator ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} shouldShowRightIcon /> {exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL && !isLocationEnabled && ( - {translate('workspace.qbo.exportVendorBillDescription')} + {translate('workspace.qbo.exportVendorBillDescription')} )} - {isLocationEnabled && {translate('workspace.qbo.outOfPocketLocationEnabledDescription')}} + {isLocationEnabled && {translate('workspace.qbo.outOfPocketLocationEnabledDescription')}} {!isLocationEnabled && ( >; function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const {exportEntity, syncTaxes} = policy?.connections?.quickbooksOnline?.config ?? {}; + const {exportEntity, syncTaxes, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {}; + const isLocationsEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); const isTaxesEnabled = Boolean(syncTaxes && syncTaxes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE); - + const isTaxError = isTaxesEnabled && exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY; + const isLocationError = isLocationsEnabled && exportEntity !== CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY; const policyID = policy?.id ?? ''; + + useEffect(() => { + if (!isTaxError && !isLocationError) { + return; + } + Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.EXPORT_ENTITY, ''); + }, [policyID, isTaxError, isLocationError]); + const data: CardListItem[] = useMemo( () => [ { @@ -37,24 +47,24 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyProps) text: translate(`workspace.qbo.check`), keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.CHECK, - isShown: true, + isShown: !isLocationsEnabled, }, { value: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, text: translate(`workspace.qbo.journalEntry`), keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.JOURNAL_ENTRY, - isShown: !isTaxesEnabled, + isShown: !isTaxesEnabled || isLocationsEnabled, }, { value: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, text: translate(`workspace.qbo.vendorBill`), keyForList: CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, isSelected: exportEntity === CONST.QUICKBOOKS_EXPORT_ENTITY.VENDOR_BILL, - isShown: true, + isShown: !isLocationsEnabled, }, ], - [exportEntity, isTaxesEnabled, translate], + [exportEntity, isTaxesEnabled, translate, isLocationsEnabled], ); const sections: CardsSection[] = useMemo(() => [{data: data.filter((item) => item.isShown)}], [data]); @@ -82,8 +92,8 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyProps) ListItem={RadioListItem} onSelectRow={onSelectRow} initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList} + footerContent={isTaxesEnabled && {translate('workspace.qbo.outOfPocketTaxEnabledDescription')}} /> - {translate('workspace.qbo.outOfPocketTaxEnabledDescription')} ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 537038d9f2e1..18b3f0b84bb9 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -378,6 +378,12 @@ const styles = (theme: ThemeColors) => lineHeight: variables.lineHeightLarge, }, + mutedNormalTextLabel: { + color: theme.textSupporting, + fontSize: variables.fontSizeLabel, + lineHeight: variables.lineHeightNormal, + }, + textMicro: { fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, fontSize: variables.fontSizeSmall, From c711542ba80bfc705bad89ade38209adc5a2b135 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 17 Apr 2024 21:58:33 +0800 Subject: [PATCH 0511/1357] correctly access the report data --- src/libs/TaskUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 19e1025a09c8..188b8f5b31ae 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -38,7 +38,7 @@ function getTaskReportActionMessage(action: OnyxEntry): Pick Date: Wed, 17 Apr 2024 16:00:02 +0200 Subject: [PATCH 0512/1357] add permissions to report --- src/CONST.ts | 6 ++++++ src/types/onyx/Report.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 2b85bcb2c326..425391f6ebea 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -837,6 +837,12 @@ const CONST = { OWNER_EMAIL_FAKE: '__FAKE__', OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', + PERMISSIONS: { + READ: 'read', + WRITE: 'write', + SHARE: 'share', + OWN: 'own', + } }, NEXT_STEP: { FINISHED: 'Finished!', diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..36d345b1084c 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -186,6 +186,8 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< transactionThreadReportID?: string; fieldList?: Record; + + permissions?: Array>; }, PolicyReportField['fieldID'] >; From de29862ae77c1cd6b5fd2fa03fc8e0443ed133ed Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:01:28 +0200 Subject: [PATCH 0513/1357] create isReadOnly --- src/libs/ReportUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 39964bfcc4d7..6b9ef9031cf0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5989,6 +5989,13 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } +/** + * + * Checks if report is in read-only mode. + */ +function isReadOnly(report: OnyxEntry): boolean { + return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; +} export { getReportParticipantsTitle, @@ -6228,6 +6235,7 @@ export { buildParticipantsFromAccountIDs, canReportBeMentionedWithinPolicy, getAllHeldTransactions, + isReadOnly, }; export type { From 7a13ae3e44fc50e05432badaf9b3db0025275ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 17 Apr 2024 16:01:32 +0200 Subject: [PATCH 0514/1357] Change caret position styles --- src/components/Composer/index.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index a8737fdac47a..59f7333433bb 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -289,13 +289,7 @@ function Composer( opacity: 0, }} > - + {`${valueBeforeCaret} `} Date: Wed, 17 Apr 2024 15:07:00 +0100 Subject: [PATCH 0515/1357] remove enabled from autoSync --- .../accounting/qbo/advanced/QuickbooksAdvancedPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx index 333566cdff41..a4f63d5c06f4 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx @@ -35,8 +35,8 @@ function QuickbooksAdvancedPage({policy}: WithPolicyProps) { { title: translate('workspace.qbo.advancedConfig.autoSync'), subtitle: translate('workspace.qbo.advancedConfig.autoSyncDescription'), - isActive: Boolean(autoSync?.enabled), - onToggle: () => Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.AUTO_SYNC, !autoSync?.enabled), + isActive: Boolean(autoSync), + onToggle: () => Policy.updatePolicyConnectionConfig(policyID, CONST.QUICK_BOOKS_CONFIG.AUTO_SYNC, !autoSync), pendingAction: pendingFields?.autoSync, }, { From e2ad69dc9610992ac13438b4316c2f2bbe53d1a2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:08:32 +0200 Subject: [PATCH 0516/1357] integrate OnboardingReportFooterMessage into ReportFooter --- src/libs/ReportUtils.ts | 17 +++++++++-------- .../report/OnboardingReportFooterMessage.tsx | 11 +++++++---- src/pages/home/report/ReportFooter.tsx | 6 ++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6b9ef9031cf0..e20686e1326a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5198,6 +5198,14 @@ function isMoneyRequestReportPendingDeletion(report: OnyxEntry | EmptyOb return parentReportAction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } +/** + * + * Checks if report is in read-only mode. + */ +function isReadOnly(report: OnyxEntry): boolean { + return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; +} + function canUserPerformWriteAction(report: OnyxEntry) { const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); @@ -5206,7 +5214,7 @@ function canUserPerformWriteAction(report: OnyxEntry) { return false; } - return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser; + return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser && !isReadOnly(report); } /** @@ -5989,13 +5997,6 @@ function canReportBeMentionedWithinPolicy(report: OnyxEntry, policyID: s return isChatRoom(report) && !isThread(report); } -/** - * - * Checks if report is in read-only mode. - */ -function isReadOnly(report: OnyxEntry): boolean { - return !report?.permissions?.includes(CONST.REPORT.PERMISSIONS.WRITE) ?? false; -} export { getReportParticipantsTitle, diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx index a48a609b51a7..f6c91d3b9f12 100644 --- a/src/pages/home/report/OnboardingReportFooterMessage.tsx +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -1,8 +1,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxCollection} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; @@ -16,8 +15,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy as PolicyType, Report} from '@src/types/onyx'; -type OnboardingReportFooterMessageOnyxProps = {reports: OnyxCollection; policies: OnyxCollection}; -type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps & {choice: ValueOf}; +// TODO: Use a proper choice type +type OnboardingReportFooterMessageOnyxProps = {choice: OnyxEntry; reports: OnyxCollection; policies: OnyxCollection}; +type OnboardingReportFooterMessageProps = OnboardingReportFooterMessageOnyxProps; function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingReportFooterMessageProps) { const {translate} = useLocalize(); @@ -83,6 +83,9 @@ function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingRe } OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; export default withOnyx({ + choice: { + key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, + }, reports: { key: ONYXKEYS.COLLECTION.REPORT, }, diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index bd143f9ef196..bccec0597fb0 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -20,6 +20,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import OnboardingReportFooterMessage from './OnboardingReportFooterMessage'; import ReportActionCompose from './ReportActionCompose/ReportActionCompose'; type ReportFooterOnyxProps = { @@ -81,6 +82,7 @@ function ReportFooter({ const isSmallSizeLayout = windowWidth - (isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; const hideComposer = !ReportUtils.canUserPerformWriteAction(report); + const isReadOnlyReport = ReportUtils.isReadOnly(report); const allPersonalDetails = usePersonalDetails(); @@ -125,6 +127,10 @@ function ReportFooter({ [report.reportID, handleCreateTask], ); + if (isReadOnlyReport) { + ; + } + return ( <> {hideComposer && ( From 471a02adff994ef218ec3128614a92534fa7f6c4 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:09:13 +0200 Subject: [PATCH 0517/1357] prettify --- src/pages/home/report/OnboardingReportFooterMessage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/home/report/OnboardingReportFooterMessage.tsx b/src/pages/home/report/OnboardingReportFooterMessage.tsx index f6c91d3b9f12..54659c14cf6e 100644 --- a/src/pages/home/report/OnboardingReportFooterMessage.tsx +++ b/src/pages/home/report/OnboardingReportFooterMessage.tsx @@ -81,7 +81,9 @@ function OnboardingReportFooterMessage({choice, reports, policies}: OnboardingRe ); } + OnboardingReportFooterMessage.displayName = 'OnboardingReportFooterMessage'; + export default withOnyx({ choice: { key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, From b4aed0fa5eb550664687f06008359fc21bb881c3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Apr 2024 16:10:17 +0200 Subject: [PATCH 0518/1357] restrict task action in header --- src/components/TaskHeaderActionButton.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx index 2d964f58c253..a7e3abcd3012 100644 --- a/src/components/TaskHeaderActionButton.tsx +++ b/src/components/TaskHeaderActionButton.tsx @@ -25,6 +25,10 @@ function TaskHeaderActionButton({report, session}: TaskHeaderActionButtonProps) const {translate} = useLocalize(); const styles = useThemeStyles(); + if (ReportUtils.isReadOnly(report)) { + return null; + } + return (