From 4cc504a4cddd8a52187525600595d236714ebffd Mon Sep 17 00:00:00 2001 From: Shawn Borton Date: Fri, 17 Apr 2026 12:29:29 +0200 Subject: [PATCH 01/28] Replace Priority Mode with Inbox Tab Filters Replace the hidden Focus/Priority mode toggle in Account > Preferences with visible tab filters at the top of the Inbox: All, Unread, Expenses, and Direct messages. This gives users immediate, discoverable control over which chats they see. - Add CONST.INBOX_TAB and ONYXKEYS.NVP_INBOX_TAB for tab state - Add filterReportsForInboxTab() post-filter in SidebarUtils - Add InboxTabSelector component using TabSelectorBase with small size - Add 'small' size variant to TabSelector components (28px height, 11px font) - Remove isInFocusMode from the entire report pipeline - Remove PriorityModeHandler, PriorityModeController, FocusModeNotification - Remove PriorityModePage and its route/navigation config - Remove updateChatPriorityMode API command - Update all test files Co-Authored-By: Claude Opus 4.6 (1M context) --- src/CONST/index.ts | 9 +- src/Expensify.tsx | 2 - src/ONYXKEYS.ts | 10 +- src/PriorityModeHandler.tsx | 30 ----- src/ROUTES.ts | 1 - src/SCREENS.ts | 1 - src/components/FocusModeNotification.tsx | 46 -------- .../LHNOptionsList/OptionRowLHN.tsx | 23 ++-- src/components/PriorityModeController.tsx | 110 ------------------ .../ScrollOffsetContextProvider.tsx | 10 +- src/components/TabSelector/TabLabel.tsx | 11 +- .../TabSelector/TabSelectorBase.tsx | 4 +- .../TabSelector/TabSelectorItem.tsx | 3 + src/components/TabSelector/types.ts | 10 +- src/hooks/useSidebarOrderedReports.tsx | 70 +++++++---- src/languages/de.ts | 23 +--- src/languages/en.ts | 23 +--- src/languages/es.ts | 24 +--- src/languages/fr.ts | 23 +--- src/languages/it.ts | 23 +--- src/languages/ja.ts | 23 +--- src/languages/nl.ts | 23 +--- src/languages/pl.ts | 23 +--- src/languages/pt-BR.ts | 23 +--- src/languages/zh-hans.ts | 22 +--- .../UpdateChatPriorityModeParams.ts | 9 -- src/libs/API/parameters/index.ts | 1 - src/libs/API/types.ts | 2 - src/libs/DebugUtils.ts | 3 - .../Navigation/AppNavigator/AuthScreens.tsx | 2 - .../ModalStackNavigators/index.tsx | 1 - .../RELATIONS/SETTINGS_TO_RHP.ts | 7 +- .../linkingConfig/RELATIONS/SIDEBAR_TO_RHP.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 4 - src/libs/Navigation/types.ts | 1 - src/libs/OptionsListUtils/index.ts | 1 - src/libs/ReportUtils.ts | 17 +-- src/libs/SidebarUtils.ts | 59 +++++++--- src/libs/UnreadIndicatorUpdater/index.ts | 1 - src/libs/actions/App.ts | 1 - src/libs/actions/Delegate.ts | 1 - src/libs/actions/QueuedOnyxUpdates.ts | 1 - src/libs/actions/Session/index.ts | 1 - src/libs/actions/User.ts | 36 +----- src/pages/Debug/Report/DebugReportPage.tsx | 4 +- src/pages/inbox/sidebar/BaseSidebarScreen.tsx | 2 + src/pages/inbox/sidebar/InboxTabSelector.tsx | 35 ++++++ src/pages/inbox/sidebar/SidebarLinks.tsx | 11 +- src/pages/inbox/sidebar/SidebarLinksData.tsx | 6 - .../settings/Preferences/PreferencesPage.tsx | 10 -- .../settings/Preferences/PriorityModePage.tsx | 68 ----------- src/styles/index.ts | 15 +++ src/types/onyx/InboxTab.ts | 6 + src/types/onyx/PriorityMode.ts | 8 -- tests/actions/QueuedOnyxUpdatesTest.ts | 2 - tests/perf-test/SidebarLinks.perf-test.tsx | 4 +- tests/ui/LHNItemsPresence.tsx | 26 ----- tests/unit/ReportUtilsTest.ts | 71 ----------- tests/unit/SidebarFilterTest.ts | 12 -- tests/unit/SidebarOrderTest.ts | 17 --- tests/unit/SidebarTest.ts | 4 +- tests/unit/SidebarUtilsTest.ts | 14 +-- 62 files changed, 267 insertions(+), 768 deletions(-) delete mode 100644 src/PriorityModeHandler.tsx delete mode 100644 src/components/FocusModeNotification.tsx delete mode 100644 src/components/PriorityModeController.tsx delete mode 100644 src/libs/API/parameters/UpdateChatPriorityModeParams.ts create mode 100644 src/pages/inbox/sidebar/InboxTabSelector.tsx delete mode 100644 src/pages/settings/Preferences/PriorityModePage.tsx create mode 100644 src/types/onyx/InboxTab.ts delete mode 100644 src/types/onyx/PriorityMode.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index b33731461863..3fbca9aa9280 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2083,9 +2083,11 @@ const CONST = { MEMORY_THRESHOLD_IOS_WARNING_MB: 300, // > 300MB monitor closely }, }, - PRIORITY_MODE: { - GSD: 'gsd', - DEFAULT: 'default', + INBOX_TAB: { + ALL: 'all', + UNREADS: 'unreads', + EXPENSES: 'expenses', + DIRECT_MESSAGES: 'directMessages', }, THEME: { DEFAULT: 'system', @@ -9485,7 +9487,6 @@ const CONST = { ADDRESS: 'SettingsProfile-Address', }, SETTINGS_PREFERENCES: { - PRIORITY_MODE: 'SettingsPreferences-PriorityMode', LANGUAGE: 'SettingsPreferences-Language', PAYMENT_CURRENCY: 'SettingsPreferences-PaymentCurrency', THEME: 'SettingsPreferences-Theme', diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 70f8a81666f6..902b9b101e27 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -33,7 +33,6 @@ import {startBootsplashMonitor} from './libs/telemetry/bootsplashTelemetry'; import {cleanupMemoryTrackingTelemetry, initializeMemoryTrackingTelemetry} from './libs/telemetry/TelemetrySynchronizer'; import Visibility from './libs/Visibility'; import ONYXKEYS from './ONYXKEYS'; -import PriorityModeHandler from './PriorityModeHandler'; import type {Route} from './ROUTES'; import {useSplashScreenActions, useSplashScreenState} from './SplashScreenStateContext'; @@ -273,7 +272,6 @@ function Expensify() { return ( <> {shouldInit && } - diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b3062bf03dc7..b7da30c7c155 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -159,8 +159,8 @@ const ONYXKEYS = { /** Contains the platforms for which the user muted the sounds */ NVP_MUTED_PLATFORMS: 'nvp_mutedPlatforms', - /** Contains the user preference for the LHN priority mode */ - NVP_PRIORITY_MODE: 'nvp_priorityMode', + /** Contains the user preference for the active inbox tab filter */ + NVP_INBOX_TAB: 'nvp_inboxTab', /** Contains the users's block expiration (if they have one) */ NVP_BLOCKED_FROM_CONCIERGE: 'nvp_private_blockedFromConcierge', @@ -212,9 +212,6 @@ const ONYXKEYS = { /** Whether the app is currently loading a translation */ RAM_ONLY_ARE_TRANSLATIONS_LOADING: 'areTranslationsLoading', - /** Whether the user has tried focus mode yet */ - NVP_TRY_FOCUS_MODE: 'nvp_tryFocusMode', - /** Whether the user has dismissed the hold educational interstitial */ NVP_DISMISSED_HOLD_USE_EXPLANATION: 'nvp_dismissedHoldUseExplanation', @@ -1356,7 +1353,7 @@ type OnyxValuesMapping = { [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.BETA_CONFIGURATION]: OnyxTypes.BetaConfiguration; [ONYXKEYS.NVP_MUTED_PLATFORMS]: Partial>; - [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; + [ONYXKEYS.NVP_INBOX_TAB]: ValueOf; [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; [ONYXKEYS.QUEUE_FLUSHED_DATA]: AnyOnyxUpdate[]; [ONYXKEYS.TRANSACTIONS_PENDING_3DS_REVIEW]: OnyxTypes.TransactionsPending3DSReview; @@ -1366,7 +1363,6 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_BLOCKED_FROM_CHAT]: string; [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string; [ONYXKEYS.NVP_RECENT_ATTENDEES]: Attendee[]; - [ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean; [ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean; [ONYXKEYS.NVP_DISMISSED_ASAP_SUBMIT_EXPLANATION]: boolean; [ONYXKEYS.NVP_EMPTY_REPORTS_CONFIRMATION_DISMISSED]: boolean; diff --git a/src/PriorityModeHandler.tsx b/src/PriorityModeHandler.tsx deleted file mode 100644 index 3c3326f24807..000000000000 --- a/src/PriorityModeHandler.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import {useEffect} from 'react'; -import CONST from './CONST'; -import useOnyx from './hooks/useOnyx'; -import usePrevious from './hooks/usePrevious'; -import {openApp} from './libs/actions/App'; -import ONYXKEYS from './ONYXKEYS'; - -/** - * Component that does not render anything but isolates priority-mode–related Onyx subscriptions - * (NVP_PRIORITY_MODE, COLLECTION.REPORT_DRAFT_COMMENT) from the root Expensify component so that - * changes to these keys do not re-render the entire navigation tree. - */ -function PriorityModeHandler() { - const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); - const [allReportsWithDraftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); - const prevPriorityMode = usePrevious(priorityMode); - - useEffect(() => { - if (!(prevPriorityMode === CONST.PRIORITY_MODE.GSD && priorityMode === CONST.PRIORITY_MODE.DEFAULT)) { - return; - } - // When a user switches their priority mode away from #focus/GSD we need to call openApp - // to fetch all their chats because #focus mode works with a subset of a user's chats. - openApp(false, allReportsWithDraftComments); - }, [priorityMode, allReportsWithDraftComments, prevPriorityMode]); - - return null; -} - -export default PriorityModeHandler; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 5477d2b21a3c..e30765e78f81 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -556,7 +556,6 @@ const ROUTES = { // eslint-disable-next-line no-restricted-syntax -- Legacy route generation getRoute: (backTo?: string) => getUrlWithBackToParam('settings/subscription/downgrade-blocked', backTo), }, - SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', SETTINGS_LANGUAGE: 'settings/preferences/language', SETTINGS_PAYMENT_CURRENCY: 'setting/preferences/payment-currency', SETTINGS_THEME: 'settings/preferences/theme', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 933edaedffee..8316e488c214 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -160,7 +160,6 @@ const SCREENS = { PREFERENCES: { ROOT: 'Settings_Preferences', - PRIORITY_MODE: 'Settings_Preferences_PriorityMode', LANGUAGE: 'Settings_Preferences_Language', THEME: 'Settings_Preferences_Theme', PAYMENT_CURRENCY: 'Settings_Payment_Currency', diff --git a/src/components/FocusModeNotification.tsx b/src/components/FocusModeNotification.tsx deleted file mode 100644 index d7f82bf7defc..000000000000 --- a/src/components/FocusModeNotification.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import useEnvironment from '@hooks/useEnvironment'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; -import useLocalize from '@hooks/useLocalize'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import colors from '@styles/theme/colors'; -import ConfirmModal from './ConfirmModal'; -import RenderHTML from './RenderHTML'; - -type FocusModeNotificationProps = { - onClose: () => void; -}; - -function FocusModeNotification({onClose}: FocusModeNotificationProps) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const illustrations = useMemoizedLazyIllustrations(['ThreeLeggedLaptopWoman']); - const {environmentURL} = useEnvironment(); - const {translate} = useLocalize(); - const priorityModePageUrl = `${environmentURL}/settings/preferences/priority-mode`; - - return ( - - - - } - success - isVisible - image={illustrations.ThreeLeggedLaptopWoman} - imageStyles={StyleUtils.getBackgroundColorStyle(colors.pink800)} - titleStyles={[styles.textHeadline, styles.mbn3]} - /> - ); -} - -export default FocusModeNotification; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 4f230b11cbf9..c1612c0a17d4 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -89,12 +89,13 @@ function OptionRowLHN({ const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const isInFocusMode = viewMode === CONST.OPTION_MODE.COMPACT; - const sidebarInnerRowStyle = StyleSheet.flatten( - isInFocusMode - ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] - : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], - ); + const sidebarInnerRowStyle = StyleSheet.flatten([ + styles.chatLinkRowPressable, + styles.flexGrow1, + styles.optionItemAvatarNameWrapper, + styles.optionRow, + styles.justifyContentCenter, + ]); const alternateTextContainsCustomEmojiWithText = useMemo( () => containsCustomEmojiUtils(optionItem?.alternateText) && !containsOnlyCustomEmoji(optionItem?.alternateText), @@ -171,11 +172,9 @@ function OptionRowLHN({ const textStyle = isOptionFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = shouldUseBoldText(optionItem) ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; const displayNameStyle = [styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, textUnreadStyle, styles.flexShrink0, style]; - const alternateTextStyle = isInFocusMode - ? [textStyle, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2, style] - : [textStyle, styles.optionAlternateText, styles.textLabelSupporting, style]; + const alternateTextStyle = [textStyle, styles.optionAlternateText, styles.textLabelSupporting, style]; - const contentContainerStyles = isInFocusMode ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; + const contentContainerStyles = [styles.flex1]; const hoveredBackgroundColor = !!styles.sidebarLinkHover && 'backgroundColor' in styles.sidebarLinkHover ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; @@ -329,9 +328,9 @@ function OptionRowLHN({ >) => priorityMode === CONST.PRIORITY_MODE.GSD; - -/** - * This component is used to automatically switch a user into #focus mode when they exceed a certain number of reports. - * We do this primarily for performance reasons. Similar to the "Welcome action" we must wait for a number of things to - * happen when the user signs in or refreshes the page: - * - * - NVP that tracks whether they have already been switched over. We only do this once. - * - Priority mode NVP (that dictates the ordering/filtering logic of the LHN) - * - Reports to load (in ReconnectApp or OpenApp). As we check the count of the reports to determine whether the - * user is eligible to be automatically switched. - * - */ -export default function PriorityModeController() { - const {orderedReportIDs} = useSidebarOrderedReportsState(); - const lhnReportCount = orderedReportIDs.length; - const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); - const [isInFocusMode, isInFocusModeMetadata] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {selector: isInFocusModeSelector}); - const [hasTriedFocusMode, hasTriedFocusModeMetadata] = useOnyx(ONYXKEYS.NVP_TRY_FOCUS_MODE); - const currentRouteName = useCurrentRouteName(); - const [shouldShowModal, setShouldShowModal] = useState(false); - const closeModal = useCallback(() => setShouldShowModal(false), []); - - // We set this when we have finally auto-switched the user of #focus mode to prevent duplication. - const hasSwitched = useRef(false); - - // Listen for state changes and trigger the #focus mode when appropriate - useEffect(() => { - // Wait for Onyx state to fully load - if ( - isLoadingReportData !== false || - isLoadingOnyxValue(isInFocusModeMetadata, hasTriedFocusModeMetadata) || - typeof isInFocusMode !== 'boolean' || - typeof hasTriedFocusMode !== 'boolean' - ) { - return; - } - - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (hasSwitched.current || isInFocusMode || hasTriedFocusMode) { - return; - } - - if (lhnReportCount < CONST.REPORT.MAX_COUNT_BEFORE_FOCUS_UPDATE) { - Log.info('[PriorityModeController] Not switching user to focus mode as they do not have enough reports', false, {lhnReportCount}); - return; - } - - // We wait for the user to navigate back to the home screen before triggering this switch - const isNarrowLayout = getIsNarrowLayout(); - if ((isNarrowLayout && currentRouteName !== SCREENS.INBOX) || (!isNarrowLayout && currentRouteName !== SCREENS.REPORT)) { - Log.info("[PriorityModeController] Not switching user to focus mode as they aren't on the home screen", false, {lhnReportCount, currentRouteName}); - return; - } - - Log.info('[PriorityModeController] Switching user to focus mode', false, {lhnReportCount, hasTriedFocusMode, isInFocusMode, currentRouteName}); - updateChatPriorityMode(CONST.PRIORITY_MODE.GSD, true); - requestAnimationFrame(() => { - setShouldShowModal(true); - }); - hasSwitched.current = true; - }, [currentRouteName, hasTriedFocusMode, hasTriedFocusModeMetadata, isInFocusMode, isInFocusModeMetadata, isLoadingReportData, lhnReportCount]); - - useEffect(() => { - if (!shouldShowModal) { - return; - } - const isNavigatingToPriorityModePage = currentRouteName === SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE; - - // Hide focus modal when settings button is pressed from the prompt. - if (isNavigatingToPriorityModePage) { - setShouldShowModal(false); - } - }, [currentRouteName, shouldShowModal]); - - return shouldShowModal ? : null; -} - -/** - * A funky but reliable way to subscribe to screen changes. - */ -function useCurrentRouteName() { - const navigation = useNavigation(); - const [currentRouteName, setCurrentRouteName] = useState(''); - - useEffect(() => { - const unsubscribe = navigation.addListener('state', () => { - setCurrentRouteName(navigationRef.getCurrentRoute()?.name); - }); - return () => unsubscribe(); - }, [navigation]); - - return currentRouteName; -} diff --git a/src/components/ScrollOffsetContextProvider.tsx b/src/components/ScrollOffsetContextProvider.tsx index f051497606d4..2a59f6ee533a 100644 --- a/src/components/ScrollOffsetContextProvider.tsx +++ b/src/components/ScrollOffsetContextProvider.tsx @@ -60,22 +60,22 @@ function getKey(route: PlatformStackRouteProp | NavigationPartial } function ScrollOffsetContextProvider({children}: ScrollOffsetContextProviderProps) { - const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); + const [inboxTab] = useOnyx(ONYXKEYS.NVP_INBOX_TAB); const scrollOffsetsRef = useRef>({}); - const previousPriorityMode = usePrevious(priorityMode); + const previousInboxTab = usePrevious(inboxTab); useEffect(() => { - if (previousPriorityMode === null || previousPriorityMode === priorityMode) { + if (previousInboxTab === null || previousInboxTab === inboxTab) { return; } - // If the priority mode changes, we need to clear the scroll offsets for the home and search screens because it affects the size of the elements and scroll positions wouldn't be correct. + // If the inbox tab changes, we need to clear the scroll offsets for the home and search screens because it affects the size of the elements and scroll positions wouldn't be correct. for (const key of Object.keys(scrollOffsetsRef.current)) { if (key.includes(SCREENS.INBOX) || key.includes(SCREENS.SEARCH.ROOT)) { delete scrollOffsetsRef.current[key]; } } - }, [priorityMode, previousPriorityMode]); + }, [inboxTab, previousInboxTab]); const saveScrollOffset: ScrollOffsetContextValue['saveScrollOffset'] = useCallback((route, scrollOffset) => { scrollOffsetsRef.current[getKey(route)] = scrollOffset; diff --git a/src/components/TabSelector/TabLabel.tsx b/src/components/TabSelector/TabLabel.tsx index e47490b89f38..c00d939cdb44 100644 --- a/src/components/TabSelector/TabLabel.tsx +++ b/src/components/TabSelector/TabLabel.tsx @@ -5,6 +5,7 @@ import type {StyleProp, TextStyle} from 'react-native'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; +import type {TabSelectorSize} from './types'; type TabLabelProps = { /** Title of the tab */ @@ -21,16 +22,20 @@ type TabLabelProps = { /** Text style */ textStyle?: StyleProp; + + /** Size variant */ + size?: TabSelectorSize; }; -function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1, hasIcon = false, textStyle}: TabLabelProps) { +function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1, hasIcon = false, textStyle, size}: TabLabelProps) { const styles = useThemeStyles(); + const smallTextStyle = size === 'small' ? styles.tabTextSmall : undefined; return ( {title} @@ -38,7 +43,7 @@ function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1, hasIcon = {title} diff --git a/src/components/TabSelector/TabSelectorBase.tsx b/src/components/TabSelector/TabSelectorBase.tsx index 0c48ea1b091a..ff544715247e 100644 --- a/src/components/TabSelector/TabSelectorBase.tsx +++ b/src/components/TabSelector/TabSelectorBase.tsx @@ -27,6 +27,7 @@ function TabSelectorBase({ position, shouldShowLabelWhenInactive = true, equalWidth = false, + size, shouldShowProductTrainingTooltip = false, renderProductTrainingTooltip, }: TabSelectorBaseProps) { @@ -64,7 +65,7 @@ function TabSelectorBase({ }} ref={containerRef} style={styles.scrollableTabSelector} - contentContainerStyle={styles.tabSelectorContentContainer} + contentContainerStyle={[styles.tabSelectorContentContainer, size === 'small' && styles.tabSelectorContentContainerSmall]} horizontal showsHorizontalScrollIndicator={false} keyboardShouldPersistTaps="handled" @@ -123,6 +124,7 @@ function TabSelectorBase({ shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip} renderProductTrainingTooltip={renderProductTrainingTooltip} equalWidth={equalWidth} + size={size} badgeText={tab.badgeText} pendingAction={tab.pendingAction} isDisabled={tab.isDisabled} diff --git a/src/components/TabSelector/TabSelectorItem.tsx b/src/components/TabSelector/TabSelectorItem.tsx index fb5ada5f9d94..47c825b2f2ff 100644 --- a/src/components/TabSelector/TabSelectorItem.tsx +++ b/src/components/TabSelector/TabSelectorItem.tsx @@ -34,6 +34,7 @@ function TabSelectorItem({ shouldShowProductTrainingTooltip = false, renderProductTrainingTooltip, equalWidth = false, + size, badgeText, isDisabled = false, pendingAction, @@ -58,6 +59,7 @@ function TabSelectorItem({ accessibilityRole={CONST.ROLE.TAB} style={[ styles.tabSelectorButton, + size === 'small' && styles.tabSelectorButtonSmall, styles.tabBackground(isHovered, isActive, isDisabled, backgroundColor), styles.userSelectNone, isOfflineWithPendingAction ? styles.offlineFeedbackPending : undefined, @@ -89,6 +91,7 @@ function TabSelectorItem({ activeOpacity={styles.tabOpacity(isDisabled, isHovered, isActive, activeOpacity, inactiveOpacity).opacity} inactiveOpacity={styles.tabOpacity(isDisabled, isHovered, isActive, inactiveOpacity, activeOpacity).opacity} hasIcon={!!icon} + size={size} /> )} {!!badgeText && ( diff --git a/src/components/TabSelector/types.ts b/src/components/TabSelector/types.ts index 4d5ac355b75c..20e0d3e0861d 100644 --- a/src/components/TabSelector/types.ts +++ b/src/components/TabSelector/types.ts @@ -52,6 +52,8 @@ type TabSelectorBaseItem = WithSentryLabel & { pendingAction?: PendingAction; }; +type TabSelectorSize = 'default' | 'small'; + type TabSelectorBaseProps = { /** Tabs to render. */ tabs: TabSelectorBaseItem[]; @@ -77,6 +79,9 @@ type TabSelectorBaseProps = { /** Whether tabs should have equal width. */ equalWidth?: boolean; + /** Size variant for the tabs. 'small' uses a compact 28px height. */ + size?: TabSelectorSize; + /** Determines whether the product training tooltip should be displayed to the user. */ shouldShowProductTrainingTooltip?: boolean; @@ -121,6 +126,9 @@ type TabSelectorItemProps = WithSentryLabel & { /** Whether tabs should have equal width */ equalWidth?: boolean; + /** Size variant for the tabs. */ + size?: TabSelectorSize; + /** Determines whether the product training tooltip should be displayed to the user. */ shouldShowProductTrainingTooltip?: boolean; @@ -182,4 +190,4 @@ type BackgroundColor = Animated.AnimatedInterpolation | string; type Opacity = 1 | 0 | Animated.AnimatedInterpolation; -export type {TabSelectorProps, BackgroundColor, GetBackgroundColorConfig, Opacity, GetOpacityConfig, TabSelectorBaseProps, TabSelectorBaseItem, TabSelectorItemProps}; +export type {TabSelectorProps, BackgroundColor, GetBackgroundColorConfig, Opacity, GetOpacityConfig, TabSelectorBaseProps, TabSelectorBaseItem, TabSelectorItemProps, TabSelectorSize}; diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index 8ae7ad1a86bf..bd2ea03621c7 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -1,6 +1,8 @@ import {deepEqual} from 'fast-equals'; import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import {setInboxTab} from '@libs/actions/User'; import Log from '@libs/Log'; import {getTransactionThreadReportID} from '@libs/MergeTransactionUtils'; import {isOneTransactionReport} from '@libs/ReportUtils'; @@ -33,10 +35,12 @@ type SidebarOrderedReportsStateContextValue = { orderedReportIDs: string[]; currentReportID: string | undefined; chatTabBrickRoad: BrickRoad; + activeTab: ValueOf; }; type SidebarOrderedReportsActionsContextValue = { clearLHNCache: () => void; + setActiveTab: (tab: ValueOf) => void; }; type ReportsToDisplayInLHN = Record; @@ -46,10 +50,12 @@ const SidebarOrderedReportsStateContext = createContext({ clearLHNCache: () => {}, + setActiveTab: () => {}, }); const policyMapper = (policy: OnyxEntry): PartialPolicyForSidebar => @@ -73,7 +79,8 @@ function SidebarOrderedReportsContextProvider({ currentReportIDForTests, }: SidebarOrderedReportsContextProviderProps) { const {localeCompare} = useLocalize(); - const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); + const [inboxTab = CONST.INBOX_TAB.ALL] = useOnyx(ONYXKEYS.NVP_INBOX_TAB); + const activeTab = inboxTab ?? CONST.INBOX_TAB.ALL; const [chatReports, {sourceValue: reportUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const [policies, {sourceValue: policiesUpdates}] = useMappedPolicies(policyMapper); const [transactions, {sourceValue: transactionsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); @@ -94,7 +101,6 @@ function SidebarOrderedReportsContextProvider({ const [clearCacheDummyCounter, setClearCacheDummyCounter] = useState(0); const prevBetas = usePrevious(betas); - const prevPriorityMode = usePrevious(priorityMode); const perfRef = useRef<{hookDuration: number}>({ hookDuration: 0, @@ -107,7 +113,7 @@ function SidebarOrderedReportsContextProvider({ const getUpdatedReports = useCallback(() => { const reportsToUpdate = new Set(); - if (betas !== prevBetas || priorityMode !== prevPriorityMode) { + if (betas !== prevBetas) { for (const key of Object.keys(chatReports ?? {})) { reportsToUpdate.add(key); } @@ -177,9 +183,7 @@ function SidebarOrderedReportsContextProvider({ chatReports, transactions, betas, - priorityMode, prevBetas, - prevPriorityMode, prevDerivedCurrentReportID, derivedCurrentReportID, ]); @@ -200,7 +204,6 @@ function SidebarOrderedReportsContextProvider({ reports: chatReports, updatedReportsKeys: effectiveUpdatedReports, currentReportId: derivedCurrentReportID, - isInFocusMode: priorityMode === CONST.PRIORITY_MODE.GSD, betas, transactionViolations, reportNameValuePairs, @@ -214,7 +217,6 @@ function SidebarOrderedReportsContextProvider({ derivedCurrentReportID, chatReports, betas, - priorityMode, reportsDrafts, transactionViolations, transactions, @@ -226,7 +228,7 @@ function SidebarOrderedReportsContextProvider({ return reportsToDisplay; // Rule disabled intentionally — triggering a re-render on currentReportsToDisplay would cause an infinite loop // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getUpdatedReports, chatReports, derivedCurrentReportID, priorityMode, betas, transactionViolations, reportNameValuePairs, reportAttributes, reportsDrafts, clearCacheDummyCounter]); + }, [getUpdatedReports, chatReports, derivedCurrentReportID, betas, transactionViolations, reportNameValuePairs, reportAttributes, reportsDrafts, clearCacheDummyCounter]); // Derive a stable boolean map indicating which reports have drafts. const hasDraftByReportIDRef = useRef>({}); @@ -253,16 +255,23 @@ function SidebarOrderedReportsContextProvider({ setCurrentReportsToDisplay(reportsToDisplayInLHN); }, [reportsToDisplayInLHN]); + const useAlphabeticalSort = activeTab === CONST.INBOX_TAB.UNREADS; + const getOrderedReportIDs = useCallback( - () => SidebarUtils.sortReportsToDisplayInLHN(reportsToDisplayInLHN, priorityMode, localeCompare, hasDraftByReportID, reportNameValuePairs, reportAttributes), + () => SidebarUtils.sortReportsToDisplayInLHN(reportsToDisplayInLHN, useAlphabeticalSort, localeCompare, hasDraftByReportID, reportNameValuePairs, reportAttributes), // Rule disabled intentionally - reports should be sorted only when the reportsToDisplayInLHN changes // eslint-disable-next-line react-hooks/exhaustive-deps - [reportsToDisplayInLHN, localeCompare, hasDraftByReportID, reportAttributes], + [reportsToDisplayInLHN, useAlphabeticalSort, localeCompare, hasDraftByReportID, reportAttributes], ); const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); - // Get the actual reports based on the ordered IDs + const filteredReportIDs = useMemo( + () => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab, hasDraftByReportID, reportNameValuePairs), + [orderedReportIDs, reportsToDisplayInLHN, activeTab, hasDraftByReportID, reportNameValuePairs], + ); + + // Get the actual reports based on the filtered IDs const getOrderedReports = useCallback( (reportIDs: string[]): OnyxTypes.Report[] => { if (!chatReports) { @@ -273,7 +282,7 @@ function SidebarOrderedReportsContextProvider({ [chatReports], ); - const orderedReports = useMemo(() => getOrderedReports(orderedReportIDs), [getOrderedReports, orderedReportIDs]); + const orderedReports = useMemo(() => getOrderedReports(filteredReportIDs), [getOrderedReports, filteredReportIDs]); const clearLHNCache = useCallback(() => { Log.info('[useSidebarOrderedReports] Clearing sidebar cache manually via debug modal'); @@ -281,6 +290,10 @@ function SidebarOrderedReportsContextProvider({ setClearCacheDummyCounter((current) => current + 1); }, []); + const setActiveTab = useCallback((tab: ValueOf) => { + setInboxTab(tab); + }, []); + const stateValue: SidebarOrderedReportsStateContextValue = useMemo(() => { // 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 @@ -293,33 +306,49 @@ function SidebarOrderedReportsContextProvider({ // any expense, a new LHN item is added in the list and is visible on web. But on mobile, we // just navigate to the screen with expense details, so there seems no point to execute this logic on mobile. if ( - (!shouldUseNarrowLayout || orderedReportIDs.length === 0) && + (!shouldUseNarrowLayout || filteredReportIDs.length === 0) && derivedCurrentReportID && derivedCurrentReportID !== '-1' && - orderedReportIDs.indexOf(derivedCurrentReportID) === -1 + filteredReportIDs.indexOf(derivedCurrentReportID) === -1 ) { const updatedReportIDs = getOrderedReportIDs(); - const updatedReports = getOrderedReports(updatedReportIDs); + const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab, hasDraftByReportID, reportNameValuePairs); + const updatedReports = getOrderedReports(updatedFilteredIDs); return { orderedReports: updatedReports, - orderedReportIDs: updatedReportIDs, + orderedReportIDs: updatedFilteredIDs, currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(updatedReportIDs, reportAttributes), + activeTab, }; } return { orderedReports, - orderedReportIDs, + orderedReportIDs: filteredReportIDs, currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(orderedReportIDs, reportAttributes), + activeTab, }; - }, [getOrderedReportIDs, orderedReportIDs, derivedCurrentReportID, shouldUseNarrowLayout, getOrderedReports, orderedReports, reportAttributes]); + }, [ + getOrderedReportIDs, + orderedReportIDs, + filteredReportIDs, + derivedCurrentReportID, + shouldUseNarrowLayout, + getOrderedReports, + orderedReports, + reportAttributes, + activeTab, + reportsToDisplayInLHN, + hasDraftByReportID, + reportNameValuePairs, + ]); - const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache}), [clearLHNCache]); + const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab}), [clearLHNCache, setActiveTab]); const currentDeps = { - priorityMode, + activeTab, chatReports, policies, transactions, @@ -334,7 +363,6 @@ function SidebarOrderedReportsContextProvider({ derivedCurrentReportID, prevDerivedCurrentReportID, prevBetas, - prevPriorityMode, reportsToDisplayInLHN, orderedReportIDs, orderedReports, diff --git a/src/languages/de.ts b/src/languages/de.ts index e22f119c149c..9c9f75001ff2 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2786,19 +2786,11 @@ ${amount} für ${merchant} – ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Erhalten Sie relevante Funktionsupdates und Expensify-Neuigkeiten', muteAllSounds: 'Alle Expensify-Sounds stummschalten', }, - priorityModePage: { - priorityMode: 'Prioritätsmodus', - explainerText: 'Wähle, ob du dich nur auf ungelesene und angeheftete Chats #fokussieren möchtest oder alles anzeigen willst, wobei die neuesten und angehefteten Chats oben stehen.', - priorityModes: { - default: { - label: 'Neueste', - description: 'Alle Chats nach Neuestem sortiert anzeigen', - }, - gsd: { - label: '#fokus', - description: 'Nur ungelesene alphabetisch sortiert anzeigen', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, @@ -3344,11 +3336,6 @@ ${amount} für ${merchant} – ${date}`, year: 'Jahr', selectYear: 'Bitte ein Jahr auswählen', }, - focusModeUpdateModal: { - title: 'Willkommen im #Fokusmodus!', - prompt: (priorityModePageUrl: string) => - `Behalte den Überblick, indem du nur ungelesene Chats oder Chats siehst, die deine Aufmerksamkeit benötigen. Keine Sorge, du kannst das jederzeit in den Einstellungen ändern.`, - }, notFound: { chatYouLookingForCannotBeFound: 'Der Chat, den du suchst, kann nicht gefunden werden.', getMeOutOfHere: 'Hol mich hier raus', diff --git a/src/languages/en.ts b/src/languages/en.ts index fb20d5d83d9c..12480bbb55c4 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2840,19 +2840,11 @@ const translations = { receiveRelevantFeatureUpdatesAndExpensifyNews: 'Receive relevant feature updates and Expensify news', muteAllSounds: 'Mute all sounds from Expensify', }, - priorityModePage: { - priorityMode: 'Priority mode', - explainerText: 'Choose whether to #focus on unread and pinned chats only, or show everything with the most recent and pinned chats at the top.', - priorityModes: { - default: { - label: 'Most recent', - description: 'Show all chats sorted by most recent', - }, - gsd: { - label: '#focus', - description: 'Only show unread sorted alphabetically', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, @@ -3410,11 +3402,6 @@ const translations = { month: 'Month', selectMonth: 'Please select a month', }, - focusModeUpdateModal: { - title: 'Welcome to #focus mode!', - prompt: (priorityModePageUrl: string) => - `Stay on top of things by only seeing unread chats or chats that need your attention. Don’t worry, you can change this at any point in settings.`, - }, notFound: { chatYouLookingForCannotBeFound: 'The chat you are looking for cannot be found.', getMeOutOfHere: 'Get me out of here', diff --git a/src/languages/es.ts b/src/languages/es.ts index af44621bc5c6..d9243ca2d933 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2657,20 +2657,11 @@ ${amount} para ${merchant} - ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Recibir noticias sobre Expensify y actualizaciones del producto', muteAllSounds: 'Silenciar todos los sonidos de Expensify', }, - priorityModePage: { - priorityMode: 'Modo prioridad', - explainerText: - 'Elige #concentración si deseas enfocarte sólo en los chats no leídos y en los anclados, o mostrarlo todo con los chats más recientes y los anclados en la parte superior.', - priorityModes: { - default: { - label: 'Más recientes', - description: 'Mostrar todos los chats ordenados desde el más reciente', - }, - gsd: { - label: '#concentración', - description: 'Mostrar sólo los no leídos ordenados alfabéticamente', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName) => `en ${policyName}`, @@ -3219,11 +3210,6 @@ ${amount} para ${merchant} - ${date}`, month: 'Mes', selectMonth: 'Por favor, selecciona un mes', }, - focusModeUpdateModal: { - title: '¡Bienvenido al modo #concentración!', - prompt: (priorityModePageUrl) => - `Mantente al tanto de todo viendo sólo los chats no leídos o los que necesitan tu atención. No te preocupes, puedes cambiar el ajuste en cualquier momento desde la configuración.`, - }, notFound: { chatYouLookingForCannotBeFound: 'El chat que estás buscando no se pudo encontrar.', getMeOutOfHere: 'Sácame de aquí', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index dd2cf4363012..57e3862724a6 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2792,19 +2792,11 @@ ${amount} pour ${merchant} - ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Recevoir des mises à jour de fonctionnalités pertinentes et des actualités Expensify', muteAllSounds: 'Couper tous les sons d’Expensify', }, - priorityModePage: { - priorityMode: 'Mode priorité', - explainerText: 'Choisissez de #vous concentrer uniquement sur les discussions non lues et épinglées, ou d’afficher tout avec les discussions les plus récentes et épinglées en haut.', - priorityModes: { - default: { - label: 'Le plus récent', - description: 'Afficher toutes les discussions triées par les plus récentes', - }, - gsd: { - label: '#focus', - description: 'Afficher uniquement les non lus triés par ordre alphabétique', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `dans ${policyName}`, @@ -3353,11 +3345,6 @@ ${amount} pour ${merchant} - ${date}`, year: 'Année', selectYear: 'Veuillez sélectionner une année', }, - focusModeUpdateModal: { - title: 'Bienvenue en mode #focus !', - prompt: (priorityModePageUrl: string) => - `Gardez le contrôle en affichant uniquement les discussions non lues ou celles qui nécessitent votre attention. Ne vous inquiétez pas, vous pouvez modifier ce paramètre à tout moment dans les paramètres.`, - }, notFound: { chatYouLookingForCannotBeFound: 'La discussion que vous recherchez est introuvable.', getMeOutOfHere: 'Faites-moi sortir d’ici', diff --git a/src/languages/it.ts b/src/languages/it.ts index f177ee5d7b4f..eea77cc79424 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2781,19 +2781,11 @@ ${amount} per ${merchant} - ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Ricevi aggiornamenti rilevanti sulle funzionalità e notizie su Expensify', muteAllSounds: 'Disattiva tutti i suoni da Expensify', }, - priorityModePage: { - priorityMode: 'Modalità prioritaria', - explainerText: 'Scegli se #concentrarti solo sulle chat non lette e fissate, oppure mostrare tutto con le chat più recenti e fissate in alto.', - priorityModes: { - default: { - label: 'Più recenti', - description: 'Mostra tutte le chat ordinate dalla più recente', - }, - gsd: { - label: '#focus', - description: 'Mostra solo i non letti in ordine alfabetico', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, @@ -3336,11 +3328,6 @@ ${amount} per ${merchant} - ${date}`, year: 'Anno', selectYear: 'Seleziona un anno', }, - focusModeUpdateModal: { - title: 'Benvenuto/a nella modalità #focus!', - prompt: (priorityModePageUrl: string) => - `Resta sempre aggiornato vedendo solo le chat non lette o quelle che richiedono la tua attenzione. Non preoccuparti, puoi modificare questa impostazione in qualsiasi momento nelle impostazioni.`, - }, notFound: { chatYouLookingForCannotBeFound: 'La chat che stai cercando non può essere trovata.', getMeOutOfHere: 'Fammi uscire di qui', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 5fd9507b6714..455bf7a8850a 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2754,19 +2754,11 @@ ${date} の ${merchant} への ${amount}`, receiveRelevantFeatureUpdatesAndExpensifyNews: '関連する機能のアップデートやExpensifyのニュースを受け取る', muteAllSounds: 'Expensify のすべてのサウンドをミュートする', }, - priorityModePage: { - priorityMode: '優先モード', - explainerText: '未読とピン留めされたチャットのみを#focusに表示するか、すべてのチャットを表示して、最新とピン留めされたチャットを上部に表示するかを選択してください。', - priorityModes: { - default: { - label: '最新', - description: '最新順ですべてのチャットを表示', - }, - gsd: { - label: '#focus', - description: '未読のみをアルファベット順で表示', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `${policyName} 内`, @@ -3308,11 +3300,6 @@ ${integrationName === CONST.ONBOARDING_ACCOUNTING_MAPPING.other ? 'あなたの' year: '年', selectYear: '年を選択してください', }, - focusModeUpdateModal: { - title: '#focusモードへようこそ!', - prompt: (priorityModePageUrl: string) => - `未読のチャットや対応が必要なチャットだけを表示して、常に状況を把握しましょう。いつでも設定から変更できます。`, - }, notFound: { chatYouLookingForCannotBeFound: 'お探しのチャットが見つかりません。', getMeOutOfHere: 'ここから出して', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 4447363ef30f..4038df5299d4 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2779,19 +2779,11 @@ ${amount} voor ${merchant} - ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Ontvang relevante functiewijzigingen en Expensify-nieuws', muteAllSounds: 'Alle geluiden van Expensify dempen', }, - priorityModePage: { - priorityMode: 'Prioriteitsmodus', - explainerText: 'Kies of je je wilt #focussen op alleen ongelezen en vastgezette chats, of alles wilt weergeven met de meest recente en vastgezette chats bovenaan.', - priorityModes: { - default: { - label: 'Meest recent', - description: 'Toon alle chats gesorteerd op meest recent', - }, - gsd: { - label: '#focus', - description: 'Toon alleen ongelezen, alfabetisch gesorteerd', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, @@ -3333,11 +3325,6 @@ ${amount} voor ${merchant} - ${date}`, year: 'Jaar', selectYear: 'Selecteer een jaar', }, - focusModeUpdateModal: { - title: 'Welkom bij de #focus-modus!', - prompt: (priorityModePageUrl: string) => - `Houd het overzicht door alleen ongelezen chats of chats die je aandacht nodig hebben te zien. Geen zorgen, je kunt dit op elk moment wijzigen in de instellingen.`, - }, notFound: { chatYouLookingForCannotBeFound: 'De chat die je zoekt, kan niet worden gevonden.', getMeOutOfHere: 'Haal me hier weg', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index fd9e3b730a1d..496974e494b8 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2772,19 +2772,11 @@ ${amount} dla ${merchant} - ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Otrzymuj istotne aktualizacje funkcji i wiadomości od Expensify', muteAllSounds: 'Wycisz wszystkie dźwięki z Expensify', }, - priorityModePage: { - priorityMode: 'Tryb priorytetowy', - explainerText: 'Wybierz, czy #skupić się tylko na nieprzeczytanych i przypiętych czatach, czy wyświetlać wszystko, z najnowszymi i przypiętymi czatami na górze.', - priorityModes: { - default: { - label: 'Najnowsze', - description: 'Pokaż wszystkie czaty posortowane od najnowszych', - }, - gsd: { - label: '#skupienie', - description: 'Pokaż tylko nieprzeczytane posortowane alfabetycznie', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `w ${policyName}`, @@ -3324,11 +3316,6 @@ ${amount} dla ${merchant} - ${date}`, year: 'Rok', selectYear: 'Wybierz rok', }, - focusModeUpdateModal: { - title: 'Witamy w trybie #focus!', - prompt: (priorityModePageUrl: string) => - `Miej wszystko pod kontrolą, wyświetlając tylko nieprzeczytane czaty lub czaty wymagające Twojej uwagi. Nie martw się, możesz to zmienić w dowolnym momencie w ustawieniach.`, - }, notFound: { chatYouLookingForCannotBeFound: 'Nie można znaleźć czatu, którego szukasz.', getMeOutOfHere: 'Wyprowadź mnie stąd', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 52479b6be5a0..9cecddddfb48 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2772,19 +2772,11 @@ ${amount} para ${merchant} - ${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: 'Receba atualizações relevantes de recursos e novidades da Expensify', muteAllSounds: 'Silenciar todos os sons do Expensify', }, - priorityModePage: { - priorityMode: 'Modo prioridade', - explainerText: 'Escolha se deseja #focar apenas em chats não lidos e fixados ou mostrar tudo, com os chats mais recentes e fixados no topo.', - priorityModes: { - default: { - label: 'Mais recente', - description: 'Mostrar todos os chats ordenados por mais recentes', - }, - gsd: { - label: '#foco', - description: 'Mostrar apenas não lidas em ordem alfabética', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `em ${policyName}`, @@ -3325,11 +3317,6 @@ ${amount} para ${merchant} - ${date}`, year: 'Ano', selectYear: 'Selecione um ano', }, - focusModeUpdateModal: { - title: 'Bem-vindo ao modo #focus!', - prompt: (priorityModePageUrl: string) => - `Mantenha tudo sob controle vendo apenas os chats não lidos ou que precisam da sua atenção. Não se preocupe, você pode alterar isso a qualquer momento em configurações.`, - }, notFound: { chatYouLookingForCannotBeFound: 'O chat que você está procurando não foi encontrado.', getMeOutOfHere: 'Me tire daqui', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 40049ed183d1..aade94e81823 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2704,19 +2704,11 @@ ${amount},商户:${merchant} - 日期:${date}`, receiveRelevantFeatureUpdatesAndExpensifyNews: '接收相关功能更新和 Expensify 新闻', muteAllSounds: '静音所有来自 Expensify 的声音', }, - priorityModePage: { - priorityMode: '优先模式', - explainerText: '选择仅#focus未读和置顶聊天,或显示所有聊天,并将最新和置顶聊天排在顶部。', - priorityModes: { - default: { - label: '最新', - description: '按最新排序显示所有聊天', - }, - gsd: { - label: '#focus', - description: '仅按字母顺序显示未读', - }, - }, + inboxTabs: { + all: 'All', + unreads: 'Unread', + expenses: 'Expenses', + directMessages: 'Direct messages', }, reportDetailsPage: { inWorkspace: (policyName: string) => `在 ${policyName} 中`, @@ -3255,10 +3247,6 @@ ${amount},商户:${merchant} - 日期:${date}`, year: '年份', selectYear: '请选择年份', }, - focusModeUpdateModal: { - title: '欢迎进入 #focus 模式!', - prompt: (priorityModePageUrl: string) => `通过仅查看未读聊天或需要你关注的聊天来随时掌握进展。别担心,你可以随时在设置中更改此项。`, - }, notFound: { chatYouLookingForCannotBeFound: '找不到您要查找的聊天。', getMeOutOfHere: '带我离开这里', diff --git a/src/libs/API/parameters/UpdateChatPriorityModeParams.ts b/src/libs/API/parameters/UpdateChatPriorityModeParams.ts deleted file mode 100644 index 8bbb7bf6943c..000000000000 --- a/src/libs/API/parameters/UpdateChatPriorityModeParams.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type {ValueOf} from 'type-fest'; -import type CONST from '@src/CONST'; - -type UpdateChatPriorityModeParams = { - value: ValueOf; - automatic: boolean; -}; - -export default UpdateChatPriorityModeParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 7c7c54e55efd..96ef0f32a456 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -91,7 +91,6 @@ export type {default as SignInWithShortLivedAuthTokenParams} from './SignInWithS export type {default as SignInWithSupportAuthTokenParams} from './SignInWithSupportAuthTokenParams'; export type {default as UnlinkLoginParams} from './UnlinkLoginParams'; export type {default as UpdateAutomaticTimezoneParams} from './UpdateAutomaticTimezoneParams'; -export type {default as UpdateChatPriorityModeParams} from './UpdateChatPriorityModeParams'; export type {default as DuplicateWorkspaceParams} from './DuplicateWorkspaceParams'; export type {default as UpdateDateOfBirthParams} from './UpdateDateOfBirthParams'; export type {default as UpdateDisplayNameParams} from './UpdateDisplayNameParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 15959c65d415..05255a2dae10 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -90,7 +90,6 @@ const WRITE_COMMANDS = { VALIDATE_LOGIN: 'ValidateLogin', VALIDATE_SECONDARY_LOGIN: 'ValidateSecondaryLogin', UPDATE_PREFERRED_EMOJI_SKIN_TONE: 'UpdatePreferredEmojiSkinTone', - UPDATE_CHAT_PRIORITY_MODE: 'UpdateChatPriorityMode', TOGGLE_PLATFORM_MUTE: 'TogglePlatformMute', SET_CONTACT_METHOD_AS_DEFAULT: 'SetContactMethodAsDefault', UPDATE_THEME: 'UpdateTheme', @@ -642,7 +641,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.VALIDATE_LOGIN]: Parameters.ValidateLoginParams; [WRITE_COMMANDS.VALIDATE_SECONDARY_LOGIN]: Parameters.ValidateSecondaryLoginParams; [WRITE_COMMANDS.UPDATE_PREFERRED_EMOJI_SKIN_TONE]: Parameters.UpdatePreferredEmojiSkinToneParams; - [WRITE_COMMANDS.UPDATE_CHAT_PRIORITY_MODE]: Parameters.UpdateChatPriorityModeParams; [WRITE_COMMANDS.SET_CONTACT_METHOD_AS_DEFAULT]: Parameters.SetContactMethodAsDefaultParams; [WRITE_COMMANDS.TOGGLE_PLATFORM_MUTE]: Parameters.TogglePlatformMuteParams; [WRITE_COMMANDS.UPDATE_THEME]: Parameters.UpdateThemeParams; diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 31a0a1ee5ef5..882b2dc74af7 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -1405,7 +1405,6 @@ function getReasonForShowingRowInLHN({ doesReportHaveViolations, hasRBR = false, isReportArchived, - isInFocusMode = false, betas = undefined, draftComment, }: { @@ -1414,7 +1413,6 @@ function getReasonForShowingRowInLHN({ doesReportHaveViolations: boolean; hasRBR?: boolean; isReportArchived: boolean | undefined; - isInFocusMode?: boolean; betas?: OnyxEntry; draftComment: string | undefined; }): TranslationPaths | null { @@ -1427,7 +1425,6 @@ function getReasonForShowingRowInLHN({ chatReport, // We can't pass report.reportID because it will cause reason to always be isFocused currentReportId: '-1', - isInFocusMode, betas, excludeEmptyChats: true, doesReportHaveViolations, diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 9b381ec63314..63ac14d40790 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -12,7 +12,6 @@ import KYCWallContextProvider from '@components/KYCWall/KYCWallContext'; import LockedAccountModalProvider from '@components/LockedAccountModalProvider'; import OpenAppFailureModal from '@components/OpenAppFailureModal'; import OptionsListContextProvider from '@components/OptionListContextProvider'; -import PriorityModeController from '@components/PriorityModeController'; import {ProductTrainingContextProvider} from '@components/ProductTrainingContext'; import {SearchContextProvider} from '@components/Search/SearchContext'; import {SearchRouterContextProvider} from '@components/Search/SearchRouter/SearchRouterContext'; @@ -410,7 +409,6 @@ function AuthScreens() { - diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 36e3c84a261f..3774bbf6a49f 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -407,7 +407,6 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/Contacts/NewContactMethodConfirmMagicCodePage').default, [SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VERIFY_ACCOUNT]: () => require('../../../../pages/settings/Profile/Contacts/VerifyAccountPage').default, - [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: () => require('../../../../pages/settings/Preferences/PriorityModePage').default, [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: () => require('../../../../pages/workspace/accounting/PolicyAccountingPage').default, [SCREENS.SETTINGS.PREFERENCES.LANGUAGE]: () => require('../../../../pages/settings/Preferences/LanguagePage').default, [SCREENS.SETTINGS.PREFERENCES.THEME]: () => require('../../../../pages/settings/Preferences/ThemePage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index 7e59623ed3ac..7ae9ae4d67dc 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -24,12 +24,7 @@ const SETTINGS_TO_RHP: Partial> = { - [SCREENS.SETTINGS.ROOT]: [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS, SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE], + [SCREENS.SETTINGS.ROOT]: [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS], }; export default SIDEBAR_TO_RHP; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 3057fbe0034d..401ac54bbe29 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -149,10 +149,6 @@ const config: LinkingOptions['config'] = { screens: { [SCREENS.RIGHT_MODAL.SETTINGS]: { screens: { - [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: { - path: ROUTES.SETTINGS_PRIORITY_MODE, - exact: true, - }, [SCREENS.SETTINGS.PREFERENCES.LANGUAGE]: { path: ROUTES.SETTINGS_LANGUAGE, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 214d096a99d1..c662d36fbdd2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -125,7 +125,6 @@ type SettingsNavigatorParamList = { backTo?: Routes; forwardTo?: Routes; }; - [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: undefined; [SCREENS.SETTINGS.PREFERENCES.PAYMENT_CURRENCY]: undefined; [SCREENS.SETTINGS.PREFERENCES.LANGUAGE]: undefined; [SCREENS.SETTINGS.PREFERENCES.THEME]: undefined; diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 6b372a0b54bf..a6a0619ebdc6 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2157,7 +2157,6 @@ function isValidReport(option: SearchOption, policy: OnyxEntry, currentReportId: topmostReportId, betas, doesReportHaveViolations, - isInFocusMode: false, excludeEmptyChats: false, includeSelfDM, login: option.login, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3c04a6ebab12..d238e7f22b62 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9508,7 +9508,6 @@ type ShouldReportBeInOptionListParams = { report: OnyxEntry; chatReport: OnyxEntry; currentReportId: string | undefined; - isInFocusMode: boolean; betas: OnyxEntry; excludeEmptyChats: boolean; doesReportHaveViolations: boolean; @@ -9525,7 +9524,6 @@ function reasonForReportToBeInOptionList({ report, chatReport, currentReportId, - isInFocusMode, betas, excludeEmptyChats, doesReportHaveViolations, @@ -9536,8 +9534,6 @@ function reasonForReportToBeInOptionList({ isReportArchived, requiresAttention, }: ShouldReportBeInOptionListParams): ValueOf | null { - const isInDefaultMode = !isInFocusMode; - // Include the currently viewed report. If we excluded the currently viewed report, then there // would be no way to highlight it in the options list and it would be confusing to users because they lose // a sense of context. @@ -9656,17 +9652,8 @@ function reasonForReportToBeInOptionList({ return CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS; } - // All unread chats (even archived ones) in GSD mode will be shown. This is because GSD mode is specifically for focusing the user on the most relevant chats, primarily, the unread ones - if (isInFocusMode) { - const oneTransactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, currentReportActions); - const oneTransactionThreadReport = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneTransactionThreadReportID}`]; - return isUnread(report, oneTransactionThreadReport, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE - ? CONST.REPORT_IN_LHN_REASONS.IS_UNREAD - : null; - } - - // Archived reports should always be shown when in default (most recent) mode. This is because you should still be able to access and search for the chats to find them. - if (isInDefaultMode && isArchivedNonExpenseReport(report, isReportArchived)) { + // Archived reports should always be shown so users can still access and search for them. + if (isArchivedNonExpenseReport(report, isReportArchived)) { return CONST.REPORT_IN_LHN_REASONS.IS_ARCHIVED; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index f85b14ff3336..623caf8ad8d8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -21,7 +21,6 @@ import type Beta from '@src/types/onyx/Beta'; import type {ReportAttributes} from '@src/types/onyx/DerivedValues'; import type {Errors} from '@src/types/onyx/OnyxCommon'; 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 ReportAction from '@src/types/onyx/ReportAction'; import {formatPhoneNumber as formatPhoneNumberPhoneUtils} from './LocalePhoneNumber'; @@ -169,6 +168,7 @@ import { isChatThread, isConciergeChatReport, isDeprecatedGroupDM, + isDM, isDomainRoom, isExpenseReport, isExpenseRequest, @@ -177,6 +177,7 @@ import { isInvoiceReport, isInvoiceRoom, isIOUOwnedByCurrentUser, + isIOUReport, isJoinRequestInAdminRoom, isMoneyRequestReport, isOneOnOneChat, @@ -264,7 +265,6 @@ function shouldDisplayReportInLHN( report: Report, reports: OnyxCollection, currentReportId: string | undefined, - isInFocusMode: boolean, betas: OnyxEntry, transactionViolations: OnyxCollection, draftComment: OnyxEntry, @@ -320,7 +320,6 @@ function shouldDisplayReportInLHN( report, chatReport, currentReportId, - isInFocusMode, betas, excludeEmptyChats: true, doesReportHaveViolations, @@ -337,14 +336,12 @@ function getReportsToDisplayInLHN( currentReportId: string | undefined, reports: OnyxCollection, betas: OnyxEntry, - priorityMode: OnyxEntry, draftComments: OnyxCollection, transactionViolations: OnyxCollection, transactions: OnyxCollection, reportNameValuePairs?: OnyxCollection, reportAttributes?: ReportAttributesDerivedValue['reports'], ) { - const isInFocusMode = priorityMode === CONST.PRIORITY_MODE.GSD; const allReportsDictValues = reports ?? {}; const reportsToDisplay: ReportsToDisplayInLHN = {}; @@ -359,7 +356,6 @@ function getReportsToDisplayInLHN( report, reports, currentReportId, - isInFocusMode, betas, transactionViolations, reportDraftComment, @@ -383,7 +379,6 @@ type UpdateReportsToDisplayInLHNProps = { reports: OnyxCollection; updatedReportsKeys: string[]; currentReportId: string | undefined; - isInFocusMode: boolean; betas: OnyxEntry; transactionViolations: OnyxCollection; reportNameValuePairs?: OnyxCollection; @@ -397,7 +392,6 @@ function updateReportsToDisplayInLHN({ reports, updatedReportsKeys, currentReportId, - isInFocusMode, betas, transactionViolations, reportNameValuePairs, @@ -431,7 +425,6 @@ function updateReportsToDisplayInLHN({ report, reports, currentReportId, - isInFocusMode, betas, transactionViolations, reportDraftComment, @@ -626,24 +619,23 @@ function combineReportCategories( */ function sortReportsToDisplayInLHN( reportsToDisplay: ReportsToDisplayInLHN, - priorityMode: OnyxEntry, + useAlphabeticalSort: boolean, localeCompare: LocaleContextProps['localeCompare'], reportsDrafts: Record | undefined, reportNameValuePairs: OnyxCollection | undefined, reportAttributes: ReportAttributesDerivedValue['reports'] | undefined, ): string[] { - const isInFocusMode = priorityMode === CONST.PRIORITY_MODE.GSD; - const isInDefaultMode = !isInFocusMode; + const isInDefaultMode = !useAlphabeticalSort; // The LHN is split into five distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: // 1. Pinned/GBR - Always sorted by reportDisplayName // 2. Error reports - Always sorted by reportDisplayName // 3. Drafts - Always sorted by reportDisplayName // 4. Non-archived reports and settled IOUs - // - Sorted by lastVisibleActionCreated in default (most recent) view mode - // - Sorted by reportDisplayName in GSD (focus) view mode + // - Sorted by lastVisibleActionCreated when not using alphabetical sort + // - Sorted by reportDisplayName when using alphabetical sort // 5. Archived reports - // - Sorted by lastVisibleActionCreated in default (most recent) view mode - // - Sorted by reportDisplayName in GSD (focus) view mode + // - Sorted by lastVisibleActionCreated when not using alphabetical sort + // - Sorted by reportDisplayName when using alphabetical sort // Step 1: Categorize reports const categories = categorizeReportsForLHN(reportsToDisplay, reportsDrafts, reportAttributes, reportNameValuePairs); @@ -1416,6 +1408,40 @@ function getRoomWelcomeMessage( return welcomeMessage; } +function filterReportsForInboxTab( + reportIDs: string[], + reportsToDisplay: ReportsToDisplayInLHN, + activeTab: ValueOf, + reportsDrafts?: Record, + reportNameValuePairs?: OnyxCollection, +): string[] { + if (activeTab === CONST.INBOX_TAB.ALL) { + return reportIDs; + } + + return reportIDs.filter((reportID) => { + const report = reportsToDisplay[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + if (!report) { + return false; + } + + switch (activeTab) { + case CONST.INBOX_TAB.UNREADS: { + const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); + const isReportUnread = isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + const hasDraft = !!reportsDrafts?.[reportID]; + return isReportUnread || !!report.isPinned || !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt || hasDraft; + } + case CONST.INBOX_TAB.EXPENSES: + return isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report) || isPolicyExpenseChat(report); + case CONST.INBOX_TAB.DIRECT_MESSAGES: + return isDM(report) || isSelfDM(report) || isGroupChatUtil(report); + default: + return true; + } + }); +} + // Exported for unit testing only. Do not use directly in production code. export { categorizeReportsForLHN as _categorizeReportsForLHN, @@ -1427,6 +1453,7 @@ export { export default { getOptionData, sortReportsToDisplayInLHN, + filterReportsForInboxTab, getWelcomeMessage, getReasonAndReportActionThatHasRedBrickRoad, getReportsToDisplayInLHN, diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 272620e70b58..b3066332fa2f 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -58,7 +58,6 @@ function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, cur currentReportId: currentReportID, betas: [], doesReportHaveViolations: false, - isInFocusMode: false, excludeEmptyChats: false, isReportArchived, draftComment, diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 82a90babd548..2cc411ee3d2f 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -128,7 +128,6 @@ const KEYS_TO_PRESERVE: OnyxKey[] = [ ONYXKEYS.NETWORK, ONYXKEYS.SESSION, ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, - ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.CREDENTIALS, diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index a31097d7e3a7..bea82b9297c4 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -21,7 +21,6 @@ import updateSessionAuthTokens from './Session/updateSessionAuthTokens'; import updateSessionUser from './Session/updateSessionUser'; const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [ - ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING, diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 62ce86816243..32ff59170cc7 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -35,7 +35,6 @@ function flushQueue(): Promise { if (!currentAccountID && !CONFIG.IS_TEST_ENV) { const preservedKeys = new Set([ ONYXKEYS.NVP_TRY_NEW_DOT, - ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING, diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 8047f230359c..c0f55b9817ab 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -296,7 +296,6 @@ function isExpiredSession(sessionCreationDate: number): boolean { } const KEYS_TO_PRESERVE_SUPPORTAL = [ - ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING, diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index b1fce73ddde6..138d02d821d5 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -18,7 +18,6 @@ import type { SetContactMethodAsDefaultParams, SetNameValuePairParams, TogglePlatformMuteParams, - UpdateChatPriorityModeParams, UpdateNewsletterSubscriptionParams, UpdatePreferredEmojiSkinToneParams, UpdateStatusParams, @@ -950,37 +949,8 @@ function updatePreferredSkinTone(skinTone: number) { API.write(WRITE_COMMANDS.UPDATE_PREFERRED_EMOJI_SKIN_TONE, parameters, {optimisticData}); } -/** - * Sync user chat priority mode with Onyx and Server - * @param mode - * @param [automatic] if we changed the mode automatically - */ -function updateChatPriorityMode(mode: ValueOf, automatic = false) { - const autoSwitchedToFocusMode = mode === CONST.PRIORITY_MODE.GSD && automatic; - const optimisticData: Array> = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_PRIORITY_MODE, - value: mode, - }, - ]; - - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_TRY_FOCUS_MODE, - value: true, - }); - - const parameters: UpdateChatPriorityModeParams = { - value: mode, - automatic, - }; - - API.write(WRITE_COMMANDS.UPDATE_CHAT_PRIORITY_MODE, parameters, {optimisticData}); - - if (!autoSwitchedToFocusMode) { - Navigation.goBack(); - } +function setInboxTab(tab: ValueOf) { + Onyx.merge(ONYXKEYS.NVP_INBOX_TAB, tab); } function setShouldUseStagingServer(shouldUseStagingServer: boolean) { @@ -1907,12 +1877,12 @@ export { isBlockedFromConcierge, subscribeToUserEvents, updatePreferredSkinTone, + setInboxTab, setShouldUseStagingServer, togglePlatformMute, joinScreenShare, clearScreenShareRequest, generateStatementPDF, - updateChatPriorityMode, setContactMethodAsDefault, updateTheme, resetContactMethodValidateCodeSentState, diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index 1510b45cfddc..de203b55cc32 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -69,7 +69,6 @@ function DebugReportPage({ [reportAttributesSelector], ); const [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`); - const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); const [betas] = useOnyx(ONYXKEYS.BETAS); const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); @@ -106,7 +105,6 @@ function DebugReportPage({ doesReportHaveViolations: shouldDisplayViolations, hasRBR, isReportArchived, - isInFocusMode: priorityMode === CONST.PRIORITY_MODE.GSD, draftComment, }); @@ -153,7 +151,7 @@ function DebugReportPage({ : undefined, }, ]; - }, [report, transactionViolations, isReportArchived, chatReport, reportActions, transactions, reportAttributes?.reportErrors, betas, priorityMode, draftComment, translate]); + }, [report, transactionViolations, isReportArchived, chatReport, reportActions, transactions, reportAttributes?.reportErrors, betas, draftComment, translate]); const icons = useMemoizedLazyExpensifyIcons(['Eye']); diff --git a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx index f20aa890543e..fb4a748e7460 100644 --- a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx +++ b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx @@ -14,6 +14,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {isMobile} from '@libs/Browser'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import ONYXKEYS from '@src/ONYXKEYS'; +import InboxTabSelector from './InboxTabSelector'; import SidebarLinksData from './SidebarLinksData'; // Once the app finishes loading for the first time, we never show the skeleton again @@ -59,6 +60,7 @@ function BaseSidebarScreen() { shouldDisplaySearch={shouldUseNarrowLayout} shouldDisplayHelpButton={shouldUseNarrowLayout} /> + {!shouldShowSkeleton && } {shouldShowSkeleton ? ( + + + ); +} + +InboxTabSelector.displayName = 'InboxTabSelector'; + +export default InboxTabSelector; diff --git a/src/pages/inbox/sidebar/SidebarLinks.tsx b/src/pages/inbox/sidebar/SidebarLinks.tsx index 6793096bc0fe..38d7d019de41 100644 --- a/src/pages/inbox/sidebar/SidebarLinks.tsx +++ b/src/pages/inbox/sidebar/SidebarLinks.tsx @@ -1,8 +1,6 @@ import React, {memo, useCallback, useEffect, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; -import type {ValueOf} from 'type-fest'; import LHNEmptyState from '@components/LHNOptionsList/LHNEmptyState'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; @@ -28,14 +26,11 @@ type SidebarLinksProps = { /** List of options to display */ optionListItems: Report[]; - /** The chat priority mode */ - priorityMode?: OnyxEntry>; - /** Method to change currently active report */ isActiveReport: (reportID: string) => boolean; }; -function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) { +function SidebarLinks({insets, optionListItems, isActiveReport}: SidebarLinksProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -74,8 +69,6 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO [shouldUseNarrowLayout, isActiveReport], ); - const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT; - const sidebarSkeletonReasonAttributes: SkeletonSpanReasonAttributes = { context: 'SidebarLinks', isLoadingReportData, @@ -101,7 +94,7 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO data={optionListItems} onSelectRow={showReportPage} shouldDisableFocusOptions={shouldUseNarrowLayout} - optionMode={viewMode} + optionMode={CONST.OPTION_MODE.DEFAULT} onFirstItemRendered={setSidebarLoaded} /> )} diff --git a/src/pages/inbox/sidebar/SidebarLinksData.tsx b/src/pages/inbox/sidebar/SidebarLinksData.tsx index 1c4879c15884..39e75e8a5396 100644 --- a/src/pages/inbox/sidebar/SidebarLinksData.tsx +++ b/src/pages/inbox/sidebar/SidebarLinksData.tsx @@ -4,12 +4,10 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {EdgeInsets} from 'react-native-safe-area-context'; import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; import {useSidebarOrderedReportsState} from '@hooks/useSidebarOrderedReports'; import useThemeStyles from '@hooks/useThemeStyles'; import {cancelSpan, endSpan, getSpan} from '@libs/telemetry/activeSpans'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import SidebarLinks from './SidebarLinks'; type SidebarLinksDataProps = { @@ -21,7 +19,6 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) { const isFocused = useIsFocused(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); const {orderedReports, currentReportID} = useSidebarOrderedReportsState('SidebarLinksData'); @@ -75,10 +72,7 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) { onLayout={onLayout} > diff --git a/src/pages/settings/Preferences/PreferencesPage.tsx b/src/pages/settings/Preferences/PreferencesPage.tsx index b3426c834830..28bdc3836966 100755 --- a/src/pages/settings/Preferences/PreferencesPage.tsx +++ b/src/pages/settings/Preferences/PreferencesPage.tsx @@ -32,8 +32,6 @@ function PreferencesPage() { const {getCurrencySymbol} = useCurrencyListActions(); const illustrations = useMemoizedLazyIllustrations(['Gears']); const preferencesIllustration = usePreferencesSectionIllustration(); - const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); - const platform = getPlatform(true); const [mutedPlatforms = getEmptyObject>>()] = useOnyx(ONYXKEYS.NVP_MUTED_PLATFORMS); const isPlatformMuted = mutedPlatforms[platform]; @@ -113,14 +111,6 @@ function PreferencesPage() { /> - Navigation.navigate(ROUTES.SETTINGS_PRIORITY_MODE)} - wrapperStyle={styles.sectionMenuItemTopDescription} - sentryLabel={CONST.SENTRY_LABEL.SETTINGS_PREFERENCES.PRIORITY_MODE} - /> ; - text: string; - alternateText: string; - keyForList: ValueOf; - isSelected: boolean; -}; - -function PriorityModePage() { - const {translate} = useLocalize(); - const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); - const styles = useThemeStyles(); - const priorityModes = Object.values(CONST.PRIORITY_MODE).map((mode) => ({ - value: mode, - text: translate(`priorityModePage.priorityModes.${mode}.label`), - alternateText: translate(`priorityModePage.priorityModes.${mode}.description`), - keyForList: mode, - isSelected: priorityMode === mode, - })); - - const updateMode = useCallback( - (mode: PriorityModeItem) => { - if (mode.value === priorityMode) { - Navigation.goBack(); - return; - } - updateChatPriorityMode(mode.value); - }, - [priorityMode], - ); - - return ( - - Navigation.goBack()} - /> - {translate('priorityModePage.explainerText')} - mode.isSelected)?.keyForList} - /> - - ); -} - -export default PriorityModePage; diff --git a/src/styles/index.ts b/src/styles/index.ts index 71ec72fea456..eae83119cc2f 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4359,6 +4359,11 @@ const staticStyles = (theme: ThemeColors) => scrollMarginInline: variables.tabSelectorScrollMarginInline, }, + tabSelectorButtonSmall: { + height: variables.componentSizeSmall, + paddingHorizontal: 12, + }, + tabSelector: { flexDirection: 'row', paddingHorizontal: 20, @@ -4371,6 +4376,11 @@ const staticStyles = (theme: ThemeColors) => paddingHorizontal: 20, }, + tabSelectorContentContainerSmall: { + paddingTop: 4, + paddingBottom: 4, + }, + scrollableTabSelector: { flexGrow: 0, }, @@ -6209,6 +6219,11 @@ const dynamicStyles = (theme: ThemeColors) => fontSize: variables.fontSizeLabel, }) satisfies TextStyle, + tabTextSmall: { + fontSize: variables.fontSizeSmall, + lineHeight: 16, + } satisfies TextStyle, + tabBackground: (hovered: boolean, isFocused: boolean, isDisabled: boolean, background: string | Animated.AnimatedInterpolation) => { if (isDisabled) { return {backgroundColor: undefined}; diff --git a/src/types/onyx/InboxTab.ts b/src/types/onyx/InboxTab.ts new file mode 100644 index 000000000000..616f5b89f764 --- /dev/null +++ b/src/types/onyx/InboxTab.ts @@ -0,0 +1,6 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type InboxTab = ValueOf; + +export default InboxTab; diff --git a/src/types/onyx/PriorityMode.ts b/src/types/onyx/PriorityMode.ts deleted file mode 100644 index 404c678945ad..000000000000 --- a/src/types/onyx/PriorityMode.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type {OnyxEntry} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; -import type CONST from '@src/CONST'; - -/** Modes that define how the user's chats are displayed in his chat list */ -type PriorityMode = OnyxEntry>; - -export default PriorityMode; diff --git a/tests/actions/QueuedOnyxUpdatesTest.ts b/tests/actions/QueuedOnyxUpdatesTest.ts index 5a7190f42945..10c0b93c3f7b 100644 --- a/tests/actions/QueuedOnyxUpdatesTest.ts +++ b/tests/actions/QueuedOnyxUpdatesTest.ts @@ -100,7 +100,6 @@ describe('actions/QueuedOnyxUpdates', () => { await flushQueue(); - await testOnyxKeyValue(ONYXKEYS.NVP_TRY_FOCUS_MODE); await testOnyxKeyValue(ONYXKEYS.PREFERRED_THEME); await testOnyxKeyValue(ONYXKEYS.NVP_PREFERRED_LOCALE); await testOnyxKeyValue(ONYXKEYS.SESSION); @@ -145,7 +144,6 @@ describe('actions/QueuedOnyxUpdates', () => { await flushQueue(); - await testOnyxKeyValue(ONYXKEYS.NVP_TRY_FOCUS_MODE); await testOnyxKeyValue(ONYXKEYS.PREFERRED_THEME); await testOnyxKeyValue(ONYXKEYS.NVP_PREFERRED_LOCALE); await testOnyxKeyValue(ONYXKEYS.SESSION); diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index 34c7b84e2b6b..f26496ca27e3 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -80,7 +80,7 @@ describe('SidebarLinks', () => { await Onyx.multiSet({ [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, ...mockedResponseMap, }); @@ -102,7 +102,7 @@ describe('SidebarLinks', () => { await Onyx.multiSet({ [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, ...mockedResponseMap, }); diff --git a/tests/ui/LHNItemsPresence.tsx b/tests/ui/LHNItemsPresence.tsx index a6aa9fd36dbb..2241f4654d7a 100644 --- a/tests/ui/LHNItemsPresence.tsx +++ b/tests/ui/LHNItemsPresence.tsx @@ -138,7 +138,6 @@ describe('SidebarLinksData', () => { await waitForBatchedUpdates(); await act(async () => { await Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -396,9 +395,7 @@ describe('SidebarLinksData', () => { await waitForBatchedUpdatesWithAct(); - // When the user is in the default mode await act(async () => { - await Onyx.merge(ONYXKEYS.NVP_PRIORITY_MODE, CONST.PRIORITY_MODE.DEFAULT); await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${archivedReport.reportID}`, reportNameValuePairs); }); @@ -440,12 +437,6 @@ describe('SidebarLinksData', () => { await waitForBatchedUpdatesWithAct(); // When the user is in focus mode - await act(async () => { - await Onyx.merge(ONYXKEYS.NVP_PRIORITY_MODE, CONST.PRIORITY_MODE.GSD); - }); - - await waitForBatchedUpdatesWithAct(); - // Then the report should appear in the sidebar because it's unread expect(getOptionRows()).toHaveLength(1); @@ -704,25 +695,8 @@ describe('SidebarLinksData', () => { await waitForBatchedUpdatesWithAct(); - // And the user is in default mode - await act(async () => { - await Onyx.merge(ONYXKEYS.NVP_PRIORITY_MODE, CONST.PRIORITY_MODE.DEFAULT); - }); - - await waitForBatchedUpdatesWithAct(); - // Then the report should appear in the sidebar expect(getOptionRows()).toHaveLength(1); - - await act(async () => { - // When the user is in focus mode - await Onyx.merge(ONYXKEYS.NVP_PRIORITY_MODE, CONST.PRIORITY_MODE.GSD); - }); - - await waitForBatchedUpdatesWithAct(); - - // Then the report should not disappear in the sidebar because it's read - expect(getOptionRows()).toHaveLength(0); }); it('should not display an empty submitted report having only a CREATED action', async () => { diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index fce1994b920e..847038475834 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -5881,14 +5881,12 @@ describe('ReportUtils', () => { it('should return true when the report is current active report', () => { const report = LHNTestUtils.getFakeReport(); const currentReportId = report.reportID; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -5901,8 +5899,6 @@ describe('ReportUtils', () => { it('should return true for empty submitted report if it is the current focused report', async () => { const report: Report = {...LHNTestUtils.getFakeReport(), total: 0, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED}; const currentReportId = report.reportID; - - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; const createdReportAction: ReportAction = {...LHNTestUtils.getFakeReportAction(), actionName: CONST.REPORT.ACTIONS.TYPE.CREATED}; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {[createdReportAction.reportActionID]: createdReportAction}); @@ -5912,7 +5908,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -5930,8 +5925,6 @@ describe('ReportUtils', () => { stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }; const currentReportId = `${report.reportID}1`; - - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; const createdReportAction: ReportAction = {...LHNTestUtils.getFakeReportAction(), actionName: CONST.REPORT.ACTIONS.TYPE.CREATED}; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {[createdReportAction.reportActionID]: createdReportAction}); @@ -5941,7 +5934,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -5982,7 +5974,6 @@ describe('ReportUtils', () => { }); const transactionThreadReport = buildTransactionThread(expenseCreatedAction1, expenseReport); const currentReportId = '1'; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport); await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, { @@ -5994,7 +5985,6 @@ describe('ReportUtils', () => { report: transactionThreadReport, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: true, excludeEmptyChats: false, @@ -6010,14 +6000,12 @@ describe('ReportUtils', () => { hasOutstandingChildRequest: true, }; const currentReportId = '3'; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report: chatReport, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6030,7 +6018,6 @@ describe('ReportUtils', () => { it('should return true when the report has valid draft comment', async () => { const report = LHNTestUtils.getFakeReport(); const currentReportId = '3'; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`, 'fake draft'); @@ -6040,7 +6027,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6056,14 +6042,12 @@ describe('ReportUtils', () => { isPinned: true, }; const currentReportId = '3'; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6087,7 +6071,6 @@ describe('ReportUtils', () => { lastMessageText: 'fake', }; const currentReportId = '3'; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(ONYXKEYS.SESSION, { @@ -6099,7 +6082,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6119,7 +6101,6 @@ describe('ReportUtils', () => { private_isArchived: DateUtils.getDBTime(), }; const currentReportId = '3'; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${archivedReport.reportID}`, reportNameValuePairs); @@ -6130,7 +6111,6 @@ describe('ReportUtils', () => { report: archivedReport, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6150,7 +6130,6 @@ describe('ReportUtils', () => { private_isArchived: DateUtils.getDBTime(), }; const currentReportId = '3'; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${archivedReport.reportID}`, reportNameValuePairs); @@ -6161,7 +6140,6 @@ describe('ReportUtils', () => { report: archivedReport, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6177,7 +6155,6 @@ describe('ReportUtils', () => { chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, }; const currentReportId = '3'; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; const includeSelfDM = true; expect( @@ -6185,7 +6162,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6206,14 +6182,12 @@ describe('ReportUtils', () => { }, }; const currentReportId = ''; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6226,14 +6200,12 @@ describe('ReportUtils', () => { it('should return false when the report does not have participants', () => { const report = LHNTestUtils.getFakeReport([]); const currentReportId = ''; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6249,14 +6221,12 @@ describe('ReportUtils', () => { chatType: CONST.REPORT.CHAT_TYPE.DOMAIN_ALL, }; const currentReportId = ''; - const isInFocusMode = false; const betas: Beta[] = []; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6288,7 +6258,6 @@ describe('ReportUtils', () => { const transactionThreadReport = buildTransactionThread(expenseCreatedAction, expenseReport); expenseCreatedAction.childReportID = transactionThreadReport.reportID; const currentReportId = '1'; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport); await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, { @@ -6299,7 +6268,6 @@ describe('ReportUtils', () => { report: transactionThreadReport, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6312,14 +6280,12 @@ describe('ReportUtils', () => { it('should return false when the report is empty chat and the excludeEmptyChats setting is true', () => { const report = LHNTestUtils.getFakeReport(); const currentReportId = ''; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: true, @@ -6336,7 +6302,6 @@ describe('ReportUtils', () => { reportID: conciergeReportID, }; const currentReportId = ''; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.set(ONYXKEYS.CONCIERGE_REPORT_ID, conciergeReportID); @@ -6347,7 +6312,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: true, @@ -6360,14 +6324,12 @@ describe('ReportUtils', () => { it('should return false when the users email is domain-based and the includeDomainEmail is false', () => { const report = LHNTestUtils.getFakeReport(); const currentReportId = ''; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, login: '+@domain.com', @@ -6402,7 +6364,6 @@ describe('ReportUtils', () => { report.parentReportID = parentReport.reportID; report.parentReportActionID = parentReportAction.reportActionID; const currentReportId = ''; - const isInFocusMode = false; const betas = [CONST.BETAS.DEFAULT_ROOMS]; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, parentReport); @@ -6415,7 +6376,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6428,14 +6388,12 @@ describe('ReportUtils', () => { it('should return false when the report is read and we are in the focus mode', () => { const report = LHNTestUtils.getFakeReport(); const currentReportId = ''; - const isInFocusMode = true; const betas = [CONST.BETAS.DEFAULT_ROOMS]; expect( shouldReportBeInOptionList({ report, chatReport: mockedChatReport, currentReportId, - isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6469,7 +6427,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [], doesReportHaveViolations: false, excludeEmptyChats: true, @@ -6500,7 +6457,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: true, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6529,7 +6485,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: true, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6551,7 +6506,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6595,7 +6549,6 @@ describe('ReportUtils', () => { report: threadReport, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6646,7 +6599,6 @@ describe('ReportUtils', () => { report: threadReport, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6667,7 +6619,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6689,7 +6640,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: true, @@ -6710,7 +6660,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6731,7 +6680,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '999', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6752,7 +6700,6 @@ describe('ReportUtils', () => { report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6768,7 +6715,6 @@ describe('ReportUtils', () => { report: null as unknown as Report, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -6824,7 +6770,6 @@ describe('ReportUtils', () => { report: transactionThreadReport, chatReport: mockedChatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: true, excludeEmptyChats: false, @@ -11986,7 +11931,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -12044,7 +11988,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -12151,7 +12094,6 @@ describe('ReportUtils', () => { report: expenseReport, chatReport: undefined, currentReportId: undefined, - isInFocusMode: true, betas: undefined, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -12194,7 +12136,6 @@ describe('ReportUtils', () => { report: expenseReport, chatReport: undefined, currentReportId: undefined, - isInFocusMode: true, betas: undefined, doesReportHaveViolations: false, excludeEmptyChats: false, @@ -12545,7 +12486,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: shouldShowRBR, excludeEmptyChats: false, @@ -12728,7 +12668,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -12913,7 +12852,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -12935,7 +12873,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -13036,7 +12973,6 @@ describe('ReportUtils', () => { report: expenseReportChat, chatReport: expenseReportChat, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -13076,7 +13012,6 @@ describe('ReportUtils', () => { report: expenseReportChat, chatReport: expenseReportChat, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -13552,7 +13487,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -13619,7 +13553,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -13690,7 +13623,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -13760,7 +13692,6 @@ describe('ReportUtils', () => { report: chatReport, chatReport, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -15525,7 +15456,6 @@ describe('ReportUtils', () => { report: policyExpenseChat, chatReport: policyExpenseChat, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, @@ -15594,7 +15524,6 @@ describe('ReportUtils', () => { report: policyExpenseChat, chatReport: policyExpenseChat, currentReportId: '', - isInFocusMode: false, betas: [CONST.BETAS.DEFAULT_ROOMS], doesReportHaveViolations: false, excludeEmptyChats: false, diff --git a/tests/unit/SidebarFilterTest.ts b/tests/unit/SidebarFilterTest.ts index 1970472dfdc3..c377ef0c6e25 100644 --- a/tests/unit/SidebarFilterTest.ts +++ b/tests/unit/SidebarFilterTest.ts @@ -17,7 +17,6 @@ jest.mock('@libs/Permissions'); const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', IS_LOADING_APP: 'isLoadingApp', - NVP_PRIORITY_MODE: 'nvp_priorityMode', SESSION: 'session', BETAS: 'betas', COLLECTION: { @@ -372,7 +371,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -421,7 +419,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -492,7 +489,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${draftReport.reportID}`]: 'draft report message', @@ -546,7 +542,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -612,7 +607,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -714,7 +708,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -769,7 +762,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -822,7 +814,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -873,7 +864,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -920,7 +910,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -956,7 +945,6 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 11975ca61b92..55a7cfbc6cbe 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -122,7 +122,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -160,7 +159,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -209,7 +207,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report1.reportID}`]: 'report1 draft', @@ -256,7 +253,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -319,7 +315,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -393,7 +388,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -472,7 +466,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.POLICY}${fakeReport.policyID}`]: fakePolicy, @@ -523,7 +516,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT + report2.reportID]: 'This is a draft', @@ -570,7 +562,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]: 'This is a draft', @@ -612,7 +603,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -696,7 +686,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report2.reportID}`]: 'Report2 draft comment', @@ -754,7 +743,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -816,7 +804,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportDraftCommentCollectionDataSet, @@ -888,7 +875,6 @@ describe('Sidebar', () => { .then(() => Onyx.multiSet({ [ONYXKEYS.BETAS]: betas, - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportNameValuePairsCollectionDataSet, @@ -932,7 +918,6 @@ describe('Sidebar', () => { // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -973,7 +958,6 @@ describe('Sidebar', () => { // with all reports having unread comments .then(() => Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, }), @@ -1035,7 +1019,6 @@ describe('Sidebar', () => { .then(() => Onyx.multiSet({ [ONYXKEYS.BETAS]: betas, - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportNameValuePairsCollectionDataSet, diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index a0fbc44f2995..54a42f4a4ecd 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -94,7 +94,7 @@ describe('Sidebar', () => { return act(async () => { await Onyx.multiSet({ [ONYXKEYS.BETAS]: betas, - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportNameValuePairsCollection, @@ -157,7 +157,7 @@ describe('Sidebar', () => { return act(async () => { await Onyx.multiSet({ [ONYXKEYS.BETAS]: betas, - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportNameValuePairsCollection, diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index 31acccfe67cf..e4a52b030dd0 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -3471,10 +3471,10 @@ describe('SidebarUtils', () => { ]); const mockLocaleCompare = (a: string, b: string) => a.localeCompare(b); - const priorityMode = CONST.PRIORITY_MODE.DEFAULT; + const useAlphabeticalSort = false; // When the reports are sorted - const result = SidebarUtils.sortReportsToDisplayInLHN(reports, priorityMode, mockLocaleCompare, undefined, undefined, undefined); + const result = SidebarUtils.sortReportsToDisplayInLHN(reports, useAlphabeticalSort, mockLocaleCompare, undefined, undefined, undefined); // Then the reports are sorted in the correct order expect(result).toEqual(['0', '1', '2']); // Pinned first, Error second, Normal third @@ -3499,11 +3499,11 @@ describe('SidebarUtils', () => { const mockLocaleCompare = (a: string, b: string) => a.localeCompare(b); - // When the reports are sorted in default mode - const defaultResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.DEFAULT, mockLocaleCompare, undefined, undefined, undefined); + // When the reports are sorted in default mode (not alphabetical) + const defaultResult = SidebarUtils.sortReportsToDisplayInLHN(reports, false, mockLocaleCompare, undefined, undefined, undefined); - // When the reports are sorted in GSD mode - const gsdResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.GSD, mockLocaleCompare, undefined, undefined, undefined); + // When the reports are sorted in alphabetical mode + const gsdResult = SidebarUtils.sortReportsToDisplayInLHN(reports, true, mockLocaleCompare, undefined, undefined, undefined); // Then the reports are sorted in the correct order expect(defaultResult).toEqual(['1', '0']); // Most recent first (index 1 has later date) @@ -3523,7 +3523,6 @@ describe('SidebarUtils', () => { reports, updatedReportsKeys: [`${ONYXKEYS.COLLECTION.REPORT}999`], currentReportId: '1', - isInFocusMode: false, betas: [], transactions: {}, transactionViolations: {}, @@ -3544,7 +3543,6 @@ describe('SidebarUtils', () => { reports, updatedReportsKeys: ['0'], currentReportId: undefined, - isInFocusMode: false, betas: [], transactions: {}, transactionViolations: {}, From 9af6b204a644f685ae4648f107a917a35497c736 Mon Sep 17 00:00:00 2001 From: Shawn Borton Date: Fri, 17 Apr 2026 15:55:47 +0200 Subject: [PATCH 02/28] Update tabs to All/To do/Expenses/DMs and add Unreads toggle Replace the Unread tab with a "To do" tab that shows items needing action (GBR/RBR indicators). Rename Direct Messages to DMs. Add a separate Unreads toggle in the Inbox header using a small 20px Switch component. The toggle filters to show only unread items and works across all tabs. State persists via NVP_INBOX_UNREAD_FILTER. - Add 'small' size variant to Switch component (36x20 track, 14x14 thumb) - Add switchTrackSmall/switchThumbSmall styles - Add InboxUnreadToggle component with "Unreads" label + small switch - Update filterReportsForInboxTab to apply unread filter orthogonally Co-Authored-By: Claude Opus 4.6 (1M context) --- src/CONST/index.ts | 4 +-- src/ONYXKEYS.ts | 4 +++ src/components/Switch.tsx | 22 ++++++++++---- src/hooks/useSidebarOrderedReports.tsx | 30 ++++++++++++------- src/languages/de.ts | 5 ++-- src/languages/en.ts | 5 ++-- src/languages/es.ts | 5 ++-- src/languages/fr.ts | 5 ++-- src/languages/it.ts | 5 ++-- src/languages/ja.ts | 5 ++-- src/languages/nl.ts | 5 ++-- src/languages/pl.ts | 5 ++-- src/languages/pt-BR.ts | 5 ++-- src/languages/zh-hans.ts | 5 ++-- src/libs/SidebarUtils.ts | 29 ++++++++++-------- src/libs/actions/User.ts | 5 ++++ src/pages/inbox/sidebar/BaseSidebarScreen.tsx | 5 +++- src/pages/inbox/sidebar/InboxTabSelector.tsx | 4 +-- src/pages/inbox/sidebar/InboxUnreadToggle.tsx | 30 +++++++++++++++++++ src/styles/index.ts | 14 +++++++++ 20 files changed, 144 insertions(+), 53 deletions(-) create mode 100644 src/pages/inbox/sidebar/InboxUnreadToggle.tsx diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 3fbca9aa9280..be0e4fd88728 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2085,9 +2085,9 @@ const CONST = { }, INBOX_TAB: { ALL: 'all', - UNREADS: 'unreads', + TODO: 'todo', EXPENSES: 'expenses', - DIRECT_MESSAGES: 'directMessages', + DMS: 'dms', }, THEME: { DEFAULT: 'system', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b7da30c7c155..3dce345ee6e7 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -162,6 +162,9 @@ const ONYXKEYS = { /** Contains the user preference for the active inbox tab filter */ NVP_INBOX_TAB: 'nvp_inboxTab', + /** Whether to show only unread chats in the inbox */ + NVP_INBOX_UNREAD_FILTER: 'nvp_inboxUnreadFilter', + /** Contains the users's block expiration (if they have one) */ NVP_BLOCKED_FROM_CONCIERGE: 'nvp_private_blockedFromConcierge', @@ -1354,6 +1357,7 @@ type OnyxValuesMapping = { [ONYXKEYS.BETA_CONFIGURATION]: OnyxTypes.BetaConfiguration; [ONYXKEYS.NVP_MUTED_PLATFORMS]: Partial>; [ONYXKEYS.NVP_INBOX_TAB]: ValueOf; + [ONYXKEYS.NVP_INBOX_UNREAD_FILTER]: boolean; [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; [ONYXKEYS.QUEUE_FLUSHED_DATA]: AnyOnyxUpdate[]; [ONYXKEYS.TRANSACTIONS_PENDING_3DS_REVIEW]: OnyxTypes.TransactionsPending3DSReview; diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx index ed82f75422e4..0c888a2efa66 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -26,6 +26,9 @@ type SwitchProps = { /** Callback to fire when the switch is toggled in disabled state */ disabledAction?: () => void; + + /** Size variant. 'small' renders a compact 20px tall switch. */ + size?: 'default' | 'small'; }; const OFFSET_X = { @@ -33,14 +36,21 @@ const OFFSET_X = { ON: 20, }; -function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, disabledAction}: SwitchProps) { +const OFFSET_X_SMALL = { + OFF: 0, + ON: 16, +}; + +function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, disabledAction, size}: SwitchProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const offsetX = useSharedValue(isOn ? OFFSET_X.ON : OFFSET_X.OFF); + const isSmall = size === 'small'; + const offsets = isSmall ? OFFSET_X_SMALL : OFFSET_X; + const offsetX = useSharedValue(isOn ? offsets.ON : offsets.OFF); const theme = useTheme(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Lock']); - const targetOffsetX = isOn ? OFFSET_X.ON : OFFSET_X.OFF; + const targetOffsetX = isOn ? offsets.ON : offsets.OFF; const prevIsOn = useRef(isOn); const hasUserToggled = useRef(false); @@ -85,7 +95,7 @@ function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, dis })); const animatedSwitchTrackStyle = useAnimatedStyle(() => ({ - backgroundColor: interpolateColor(offsetX.get(), [OFFSET_X.OFF, OFFSET_X.ON], [theme.icon, theme.success]), + backgroundColor: interpolateColor(offsetX.get(), [offsets.OFF, offsets.ON], [theme.icon, theme.success]), })); // Enhance accessibility label to include locked state when disabled @@ -109,8 +119,8 @@ function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, dis pressDimmingValue={0.8} sentryLabel={enhancedAccessibilityLabel} > - - + + {(!!disabled || !!showLockIcon) && ( ; + showUnreadOnly: boolean; }; type SidebarOrderedReportsActionsContextValue = { clearLHNCache: () => void; setActiveTab: (tab: ValueOf) => void; + toggleUnreadFilter: () => void; }; type ReportsToDisplayInLHN = Record; @@ -51,11 +53,13 @@ const SidebarOrderedReportsStateContext = createContext({ clearLHNCache: () => {}, setActiveTab: () => {}, + toggleUnreadFilter: () => {}, }); const policyMapper = (policy: OnyxEntry): PartialPolicyForSidebar => @@ -81,6 +85,8 @@ function SidebarOrderedReportsContextProvider({ const {localeCompare} = useLocalize(); const [inboxTab = CONST.INBOX_TAB.ALL] = useOnyx(ONYXKEYS.NVP_INBOX_TAB); const activeTab = inboxTab ?? CONST.INBOX_TAB.ALL; + const [inboxUnreadFilter = false] = useOnyx(ONYXKEYS.NVP_INBOX_UNREAD_FILTER); + const showUnreadOnly = inboxUnreadFilter ?? false; const [chatReports, {sourceValue: reportUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const [policies, {sourceValue: policiesUpdates}] = useMappedPolicies(policyMapper); const [transactions, {sourceValue: transactionsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); @@ -255,20 +261,18 @@ function SidebarOrderedReportsContextProvider({ setCurrentReportsToDisplay(reportsToDisplayInLHN); }, [reportsToDisplayInLHN]); - const useAlphabeticalSort = activeTab === CONST.INBOX_TAB.UNREADS; - const getOrderedReportIDs = useCallback( - () => SidebarUtils.sortReportsToDisplayInLHN(reportsToDisplayInLHN, useAlphabeticalSort, localeCompare, hasDraftByReportID, reportNameValuePairs, reportAttributes), + () => SidebarUtils.sortReportsToDisplayInLHN(reportsToDisplayInLHN, false, localeCompare, hasDraftByReportID, reportNameValuePairs, reportAttributes), // Rule disabled intentionally - reports should be sorted only when the reportsToDisplayInLHN changes // eslint-disable-next-line react-hooks/exhaustive-deps - [reportsToDisplayInLHN, useAlphabeticalSort, localeCompare, hasDraftByReportID, reportAttributes], + [reportsToDisplayInLHN, localeCompare, hasDraftByReportID, reportAttributes], ); const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); const filteredReportIDs = useMemo( - () => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab, hasDraftByReportID, reportNameValuePairs), - [orderedReportIDs, reportsToDisplayInLHN, activeTab, hasDraftByReportID, reportNameValuePairs], + () => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab, showUnreadOnly, reportNameValuePairs), + [orderedReportIDs, reportsToDisplayInLHN, activeTab, showUnreadOnly, reportNameValuePairs], ); // Get the actual reports based on the filtered IDs @@ -294,6 +298,10 @@ function SidebarOrderedReportsContextProvider({ setInboxTab(tab); }, []); + const toggleUnreadFilter = useCallback(() => { + setInboxUnreadFilter(!showUnreadOnly); + }, [showUnreadOnly]); + const stateValue: SidebarOrderedReportsStateContextValue = useMemo(() => { // 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 @@ -312,7 +320,7 @@ function SidebarOrderedReportsContextProvider({ filteredReportIDs.indexOf(derivedCurrentReportID) === -1 ) { const updatedReportIDs = getOrderedReportIDs(); - const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab, hasDraftByReportID, reportNameValuePairs); + const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab, showUnreadOnly, reportNameValuePairs); const updatedReports = getOrderedReports(updatedFilteredIDs); return { orderedReports: updatedReports, @@ -320,6 +328,7 @@ function SidebarOrderedReportsContextProvider({ currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(updatedReportIDs, reportAttributes), activeTab, + showUnreadOnly, }; } @@ -329,6 +338,7 @@ function SidebarOrderedReportsContextProvider({ currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(orderedReportIDs, reportAttributes), activeTab, + showUnreadOnly, }; }, [ getOrderedReportIDs, @@ -340,12 +350,12 @@ function SidebarOrderedReportsContextProvider({ orderedReports, reportAttributes, activeTab, + showUnreadOnly, reportsToDisplayInLHN, - hasDraftByReportID, reportNameValuePairs, ]); - const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab}), [clearLHNCache, setActiveTab]); + const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab, toggleUnreadFilter}), [clearLHNCache, setActiveTab, toggleUnreadFilter]); const currentDeps = { activeTab, diff --git a/src/languages/de.ts b/src/languages/de.ts index 9c9f75001ff2..befcb46025c3 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2788,9 +2788,10 @@ ${amount} für ${merchant} – ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/en.ts b/src/languages/en.ts index 12480bbb55c4..9be6ecbc314c 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2842,9 +2842,10 @@ const translations = { }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index d9243ca2d933..741f20fc7dfb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2659,9 +2659,10 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName) => `en ${policyName}`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 57e3862724a6..d90ee798f375 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2794,9 +2794,10 @@ ${amount} pour ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `dans ${policyName}`, diff --git a/src/languages/it.ts b/src/languages/it.ts index eea77cc79424..dd14322c29c2 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2783,9 +2783,10 @@ ${amount} per ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 455bf7a8850a..1600d5034687 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2756,9 +2756,10 @@ ${date} の ${merchant} への ${amount}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `${policyName} 内`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 4038df5299d4..9617a0d09edb 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2781,9 +2781,10 @@ ${amount} voor ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 496974e494b8..4950507eb6d8 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2774,9 +2774,10 @@ ${amount} dla ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `w ${policyName}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 9cecddddfb48..a15d7cc509c4 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2774,9 +2774,10 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `em ${policyName}`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index aade94e81823..3db3f03dda5c 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2706,9 +2706,10 @@ ${amount},商户:${merchant} - 日期:${date}`, }, inboxTabs: { all: 'All', - unreads: 'Unread', + todo: 'To do', expenses: 'Expenses', - directMessages: 'Direct messages', + dms: 'DMs', + unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `在 ${policyName} 中`, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 623caf8ad8d8..2bec6d18432c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1412,29 +1412,34 @@ function filterReportsForInboxTab( reportIDs: string[], reportsToDisplay: ReportsToDisplayInLHN, activeTab: ValueOf, - reportsDrafts?: Record, + showUnreadOnly: boolean, reportNameValuePairs?: OnyxCollection, ): string[] { - if (activeTab === CONST.INBOX_TAB.ALL) { - return reportIDs; - } - return reportIDs.filter((reportID) => { const report = reportsToDisplay[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (!report) { return false; } - switch (activeTab) { - case CONST.INBOX_TAB.UNREADS: { - const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); - const isReportUnread = isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; - const hasDraft = !!reportsDrafts?.[reportID]; - return isReportUnread || !!report.isPinned || !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt || hasDraft; + // Apply unread filter when the toggle is active + if (showUnreadOnly) { + const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); + const isReportUnread = isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + if (!isReportUnread && !report.isPinned && !report.requiresAttention && !report.hasErrorsOtherThanFailedReceipt) { + return false; } + } + + if (activeTab === CONST.INBOX_TAB.ALL) { + return true; + } + + switch (activeTab) { + case CONST.INBOX_TAB.TODO: + return !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt; case CONST.INBOX_TAB.EXPENSES: return isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report) || isPolicyExpenseChat(report); - case CONST.INBOX_TAB.DIRECT_MESSAGES: + case CONST.INBOX_TAB.DMS: return isDM(report) || isSelfDM(report) || isGroupChatUtil(report); default: return true; diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 138d02d821d5..975dfeaafc19 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -953,6 +953,10 @@ function setInboxTab(tab: ValueOf) { Onyx.merge(ONYXKEYS.NVP_INBOX_TAB, tab); } +function setInboxUnreadFilter(enabled: boolean) { + Onyx.merge(ONYXKEYS.NVP_INBOX_UNREAD_FILTER, enabled); +} + function setShouldUseStagingServer(shouldUseStagingServer: boolean) { if (CONFIG.IS_HYBRID_APP) { HybridAppModule.shouldUseStaging(shouldUseStagingServer); @@ -1878,6 +1882,7 @@ export { subscribeToUserEvents, updatePreferredSkinTone, setInboxTab, + setInboxUnreadFilter, setShouldUseStagingServer, togglePlatformMute, joinScreenShare, diff --git a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx index fb4a748e7460..8f3bf3d553d8 100644 --- a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx +++ b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx @@ -15,6 +15,7 @@ import {isMobile} from '@libs/Browser'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import ONYXKEYS from '@src/ONYXKEYS'; import InboxTabSelector from './InboxTabSelector'; +import InboxUnreadToggle from './InboxUnreadToggle'; import SidebarLinksData from './SidebarLinksData'; // Once the app finishes loading for the first time, we never show the skeleton again @@ -59,7 +60,9 @@ function BaseSidebarScreen() { breadcrumbLabel={translate('common.inbox')} shouldDisplaySearch={shouldUseNarrowLayout} shouldDisplayHelpButton={shouldUseNarrowLayout} - /> + > + {!shouldShowSkeleton && } + {!shouldShowSkeleton && } {shouldShowSkeleton ? ( diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index d8e75506b891..0cc87b328b3e 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -13,9 +13,9 @@ function InboxTabSelector() { const tabs: TabSelectorBaseItem[] = [ {key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all')}, - {key: CONST.INBOX_TAB.UNREADS, title: translate('inboxTabs.unreads')}, + {key: CONST.INBOX_TAB.TODO, title: translate('inboxTabs.todo')}, {key: CONST.INBOX_TAB.EXPENSES, title: translate('inboxTabs.expenses')}, - {key: CONST.INBOX_TAB.DIRECT_MESSAGES, title: translate('inboxTabs.directMessages')}, + {key: CONST.INBOX_TAB.DMS, title: translate('inboxTabs.dms')}, ]; return ( diff --git a/src/pages/inbox/sidebar/InboxUnreadToggle.tsx b/src/pages/inbox/sidebar/InboxUnreadToggle.tsx new file mode 100644 index 000000000000..e5d38653b2a8 --- /dev/null +++ b/src/pages/inbox/sidebar/InboxUnreadToggle.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import {View} from 'react-native'; +import Switch from '@components/Switch'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import {useSidebarOrderedReportsActions, useSidebarOrderedReportsState} from '@hooks/useSidebarOrderedReports'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function InboxUnreadToggle() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {showUnreadOnly} = useSidebarOrderedReportsState(); + const {toggleUnreadFilter} = useSidebarOrderedReportsActions(); + + return ( + + {translate('inboxTabs.unreadToggle')} + toggleUnreadFilter()} + accessibilityLabel={translate('inboxTabs.unreadToggle')} + size="small" + /> + + ); +} + +InboxUnreadToggle.displayName = 'InboxUnreadToggle'; + +export default InboxUnreadToggle; diff --git a/src/styles/index.ts b/src/styles/index.ts index eae83119cc2f..172579d142fb 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3039,6 +3039,13 @@ const staticStyles = (theme: ThemeColors) => padding: 15, }, + switchTrackSmall: { + width: 36, + height: 20, + borderRadius: 10, + padding: 10, + }, + switchThumb: { width: 22, height: 22, @@ -3050,6 +3057,13 @@ const staticStyles = (theme: ThemeColors) => backgroundColor: theme.appBG, }, + switchThumbSmall: { + width: 14, + height: 14, + borderRadius: 7, + left: 3, + }, + radioButtonContainer: { backgroundColor: theme.componentBG, borderRadius: 14, From 8634be9aef25b9b6c02ab26feb9fd67635c86a6d Mon Sep 17 00:00:00 2001 From: Shawn Borton Date: Fri, 17 Apr 2026 15:56:42 +0200 Subject: [PATCH 03/28] Change 'To do' tab label to 'To-do' Co-Authored-By: Claude Opus 4.6 (1M context) --- src/languages/de.ts | 2 +- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/languages/fr.ts | 2 +- src/languages/it.ts | 2 +- src/languages/ja.ts | 2 +- src/languages/nl.ts | 2 +- src/languages/pl.ts | 2 +- src/languages/pt-BR.ts | 2 +- src/languages/zh-hans.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index befcb46025c3..f524383c939a 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2788,7 +2788,7 @@ ${amount} für ${merchant} – ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/en.ts b/src/languages/en.ts index 9be6ecbc314c..65997ac79bc6 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2842,7 +2842,7 @@ const translations = { }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/es.ts b/src/languages/es.ts index 741f20fc7dfb..a0e6fcbbe9d2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2659,7 +2659,7 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index d90ee798f375..c7eabf81d038 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2794,7 +2794,7 @@ ${amount} pour ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/it.ts b/src/languages/it.ts index dd14322c29c2..b1f599039dc7 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2783,7 +2783,7 @@ ${amount} per ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 1600d5034687..abf3fc73df15 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2756,7 +2756,7 @@ ${date} の ${merchant} への ${amount}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 9617a0d09edb..4cc5e5d067f1 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2781,7 +2781,7 @@ ${amount} voor ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 4950507eb6d8..7cc40fa1f87c 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2774,7 +2774,7 @@ ${amount} dla ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index a15d7cc509c4..912f0c2f827d 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2774,7 +2774,7 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 3db3f03dda5c..2eba054e6446 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2706,7 +2706,7 @@ ${amount},商户:${merchant} - 日期:${date}`, }, inboxTabs: { all: 'All', - todo: 'To do', + todo: 'To-do', expenses: 'Expenses', dms: 'DMs', unreadToggle: 'Unreads', From e2ebe4607b02abdaeac731b9e04f48017d3fe441 Mon Sep 17 00:00:00 2001 From: Shawn Borton Date: Tue, 21 Apr 2026 18:14:56 +0200 Subject: [PATCH 04/28] Replace Priority Mode with Inbox Tab Filters Simplify inbox filtering to four straightforward tabs: All, Unread, Expenses, and DMs. Remove the separate Unreads toggle and To-do tab in favor of a clean Unread tab that shows only unread and non-muted chats. Pinned/GBR/RBR items show only if they qualify for the active tab filter. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/CONST/index.ts | 2 +- src/ONYXKEYS.ts | 4 --- src/hooks/useSidebarOrderedReports.tsx | 23 ++++---------- src/languages/de.ts | 3 +- src/languages/en.ts | 3 +- src/languages/es.ts | 3 +- src/languages/fr.ts | 3 +- src/languages/it.ts | 3 +- src/languages/ja.ts | 3 +- src/languages/nl.ts | 3 +- src/languages/pl.ts | 3 +- src/languages/pt-BR.ts | 3 +- src/languages/zh-hans.ts | 3 +- src/libs/SidebarUtils.ts | 24 +++++---------- src/libs/actions/User.ts | 5 ---- src/pages/inbox/sidebar/BaseSidebarScreen.tsx | 5 +--- src/pages/inbox/sidebar/InboxTabSelector.tsx | 2 +- src/pages/inbox/sidebar/InboxUnreadToggle.tsx | 30 ------------------- 18 files changed, 26 insertions(+), 99 deletions(-) delete mode 100644 src/pages/inbox/sidebar/InboxUnreadToggle.tsx diff --git a/src/CONST/index.ts b/src/CONST/index.ts index be0e4fd88728..6bf0ab68c2c2 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2085,7 +2085,7 @@ const CONST = { }, INBOX_TAB: { ALL: 'all', - TODO: 'todo', + UNREAD: 'unread', EXPENSES: 'expenses', DMS: 'dms', }, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3dce345ee6e7..b7da30c7c155 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -162,9 +162,6 @@ const ONYXKEYS = { /** Contains the user preference for the active inbox tab filter */ NVP_INBOX_TAB: 'nvp_inboxTab', - /** Whether to show only unread chats in the inbox */ - NVP_INBOX_UNREAD_FILTER: 'nvp_inboxUnreadFilter', - /** Contains the users's block expiration (if they have one) */ NVP_BLOCKED_FROM_CONCIERGE: 'nvp_private_blockedFromConcierge', @@ -1357,7 +1354,6 @@ type OnyxValuesMapping = { [ONYXKEYS.BETA_CONFIGURATION]: OnyxTypes.BetaConfiguration; [ONYXKEYS.NVP_MUTED_PLATFORMS]: Partial>; [ONYXKEYS.NVP_INBOX_TAB]: ValueOf; - [ONYXKEYS.NVP_INBOX_UNREAD_FILTER]: boolean; [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; [ONYXKEYS.QUEUE_FLUSHED_DATA]: AnyOnyxUpdate[]; [ONYXKEYS.TRANSACTIONS_PENDING_3DS_REVIEW]: OnyxTypes.TransactionsPending3DSReview; diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index c54abee7d3b8..f0164fadc729 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -2,7 +2,7 @@ import {deepEqual} from 'fast-equals'; import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import {setInboxTab, setInboxUnreadFilter} from '@libs/actions/User'; +import {setInboxTab} from '@libs/actions/User'; import Log from '@libs/Log'; import {getTransactionThreadReportID} from '@libs/MergeTransactionUtils'; import {isOneTransactionReport} from '@libs/ReportUtils'; @@ -36,13 +36,11 @@ type SidebarOrderedReportsStateContextValue = { currentReportID: string | undefined; chatTabBrickRoad: BrickRoad; activeTab: ValueOf; - showUnreadOnly: boolean; }; type SidebarOrderedReportsActionsContextValue = { clearLHNCache: () => void; setActiveTab: (tab: ValueOf) => void; - toggleUnreadFilter: () => void; }; type ReportsToDisplayInLHN = Record; @@ -53,13 +51,11 @@ const SidebarOrderedReportsStateContext = createContext({ clearLHNCache: () => {}, setActiveTab: () => {}, - toggleUnreadFilter: () => {}, }); const policyMapper = (policy: OnyxEntry): PartialPolicyForSidebar => @@ -85,8 +81,6 @@ function SidebarOrderedReportsContextProvider({ const {localeCompare} = useLocalize(); const [inboxTab = CONST.INBOX_TAB.ALL] = useOnyx(ONYXKEYS.NVP_INBOX_TAB); const activeTab = inboxTab ?? CONST.INBOX_TAB.ALL; - const [inboxUnreadFilter = false] = useOnyx(ONYXKEYS.NVP_INBOX_UNREAD_FILTER); - const showUnreadOnly = inboxUnreadFilter ?? false; const [chatReports, {sourceValue: reportUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const [policies, {sourceValue: policiesUpdates}] = useMappedPolicies(policyMapper); const [transactions, {sourceValue: transactionsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); @@ -271,8 +265,8 @@ function SidebarOrderedReportsContextProvider({ const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); const filteredReportIDs = useMemo( - () => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab, showUnreadOnly, reportNameValuePairs), - [orderedReportIDs, reportsToDisplayInLHN, activeTab, showUnreadOnly, reportNameValuePairs], + () => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab, reportNameValuePairs), + [orderedReportIDs, reportsToDisplayInLHN, activeTab, reportNameValuePairs], ); // Get the actual reports based on the filtered IDs @@ -298,10 +292,6 @@ function SidebarOrderedReportsContextProvider({ setInboxTab(tab); }, []); - const toggleUnreadFilter = useCallback(() => { - setInboxUnreadFilter(!showUnreadOnly); - }, [showUnreadOnly]); - const stateValue: SidebarOrderedReportsStateContextValue = useMemo(() => { // 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 @@ -320,7 +310,7 @@ function SidebarOrderedReportsContextProvider({ filteredReportIDs.indexOf(derivedCurrentReportID) === -1 ) { const updatedReportIDs = getOrderedReportIDs(); - const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab, showUnreadOnly, reportNameValuePairs); + const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab, reportNameValuePairs); const updatedReports = getOrderedReports(updatedFilteredIDs); return { orderedReports: updatedReports, @@ -328,7 +318,6 @@ function SidebarOrderedReportsContextProvider({ currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(updatedReportIDs, reportAttributes), activeTab, - showUnreadOnly, }; } @@ -338,7 +327,6 @@ function SidebarOrderedReportsContextProvider({ currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(orderedReportIDs, reportAttributes), activeTab, - showUnreadOnly, }; }, [ getOrderedReportIDs, @@ -350,12 +338,11 @@ function SidebarOrderedReportsContextProvider({ orderedReports, reportAttributes, activeTab, - showUnreadOnly, reportsToDisplayInLHN, reportNameValuePairs, ]); - const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab, toggleUnreadFilter}), [clearLHNCache, setActiveTab, toggleUnreadFilter]); + const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab}), [clearLHNCache, setActiveTab]); const currentDeps = { activeTab, diff --git a/src/languages/de.ts b/src/languages/de.ts index f524383c939a..80b0180e4926 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2788,10 +2788,9 @@ ${amount} für ${merchant} – ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/en.ts b/src/languages/en.ts index 65997ac79bc6..8e2103eff286 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2842,10 +2842,9 @@ const translations = { }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index a0e6fcbbe9d2..7a8425dd7fec 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2659,10 +2659,9 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName) => `en ${policyName}`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index c7eabf81d038..8633626d7080 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2794,10 +2794,9 @@ ${amount} pour ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `dans ${policyName}`, diff --git a/src/languages/it.ts b/src/languages/it.ts index b1f599039dc7..cd28d7c31dad 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2783,10 +2783,9 @@ ${amount} per ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index abf3fc73df15..0da264509b32 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2756,10 +2756,9 @@ ${date} の ${merchant} への ${amount}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `${policyName} 内`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 4cc5e5d067f1..ead8c12235e4 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2781,10 +2781,9 @@ ${amount} voor ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 7cc40fa1f87c..0a08aad6fd97 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2774,10 +2774,9 @@ ${amount} dla ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `w ${policyName}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 912f0c2f827d..e2b81c2f510e 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2774,10 +2774,9 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `em ${policyName}`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 2eba054e6446..cfbd7bf3d21d 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2706,10 +2706,9 @@ ${amount},商户:${merchant} - 日期:${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + unread: 'Unread', expenses: 'Expenses', dms: 'DMs', - unreadToggle: 'Unreads', }, reportDetailsPage: { inWorkspace: (policyName: string) => `在 ${policyName} 中`, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2bec6d18432c..0cf4dd3b86aa 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1412,31 +1412,23 @@ function filterReportsForInboxTab( reportIDs: string[], reportsToDisplay: ReportsToDisplayInLHN, activeTab: ValueOf, - showUnreadOnly: boolean, reportNameValuePairs?: OnyxCollection, ): string[] { + if (activeTab === CONST.INBOX_TAB.ALL) { + return reportIDs; + } + return reportIDs.filter((reportID) => { const report = reportsToDisplay[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (!report) { return false; } - // Apply unread filter when the toggle is active - if (showUnreadOnly) { - const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); - const isReportUnread = isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; - if (!isReportUnread && !report.isPinned && !report.requiresAttention && !report.hasErrorsOtherThanFailedReceipt) { - return false; - } - } - - if (activeTab === CONST.INBOX_TAB.ALL) { - return true; - } - switch (activeTab) { - case CONST.INBOX_TAB.TODO: - return !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt; + case CONST.INBOX_TAB.UNREAD: { + const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); + return isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + } case CONST.INBOX_TAB.EXPENSES: return isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report) || isPolicyExpenseChat(report); case CONST.INBOX_TAB.DMS: diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 975dfeaafc19..138d02d821d5 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -953,10 +953,6 @@ function setInboxTab(tab: ValueOf) { Onyx.merge(ONYXKEYS.NVP_INBOX_TAB, tab); } -function setInboxUnreadFilter(enabled: boolean) { - Onyx.merge(ONYXKEYS.NVP_INBOX_UNREAD_FILTER, enabled); -} - function setShouldUseStagingServer(shouldUseStagingServer: boolean) { if (CONFIG.IS_HYBRID_APP) { HybridAppModule.shouldUseStaging(shouldUseStagingServer); @@ -1882,7 +1878,6 @@ export { subscribeToUserEvents, updatePreferredSkinTone, setInboxTab, - setInboxUnreadFilter, setShouldUseStagingServer, togglePlatformMute, joinScreenShare, diff --git a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx index 8f3bf3d553d8..fb4a748e7460 100644 --- a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx +++ b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx @@ -15,7 +15,6 @@ import {isMobile} from '@libs/Browser'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import ONYXKEYS from '@src/ONYXKEYS'; import InboxTabSelector from './InboxTabSelector'; -import InboxUnreadToggle from './InboxUnreadToggle'; import SidebarLinksData from './SidebarLinksData'; // Once the app finishes loading for the first time, we never show the skeleton again @@ -60,9 +59,7 @@ function BaseSidebarScreen() { breadcrumbLabel={translate('common.inbox')} shouldDisplaySearch={shouldUseNarrowLayout} shouldDisplayHelpButton={shouldUseNarrowLayout} - > - {!shouldShowSkeleton && } - + /> {!shouldShowSkeleton && } {shouldShowSkeleton ? ( diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 0cc87b328b3e..4de90b6aa6e5 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -13,7 +13,7 @@ function InboxTabSelector() { const tabs: TabSelectorBaseItem[] = [ {key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all')}, - {key: CONST.INBOX_TAB.TODO, title: translate('inboxTabs.todo')}, + {key: CONST.INBOX_TAB.UNREAD, title: translate('inboxTabs.unread')}, {key: CONST.INBOX_TAB.EXPENSES, title: translate('inboxTabs.expenses')}, {key: CONST.INBOX_TAB.DMS, title: translate('inboxTabs.dms')}, ]; diff --git a/src/pages/inbox/sidebar/InboxUnreadToggle.tsx b/src/pages/inbox/sidebar/InboxUnreadToggle.tsx deleted file mode 100644 index e5d38653b2a8..000000000000 --- a/src/pages/inbox/sidebar/InboxUnreadToggle.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import Switch from '@components/Switch'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import {useSidebarOrderedReportsActions, useSidebarOrderedReportsState} from '@hooks/useSidebarOrderedReports'; -import useThemeStyles from '@hooks/useThemeStyles'; - -function InboxUnreadToggle() { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const {showUnreadOnly} = useSidebarOrderedReportsState(); - const {toggleUnreadFilter} = useSidebarOrderedReportsActions(); - - return ( - - {translate('inboxTabs.unreadToggle')} - toggleUnreadFilter()} - accessibilityLabel={translate('inboxTabs.unreadToggle')} - size="small" - /> - - ); -} - -InboxUnreadToggle.displayName = 'InboxUnreadToggle'; - -export default InboxUnreadToggle; From 53cd10fe3a4a990b86f917052b1d3904f79360ed Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sat, 30 May 2026 21:47:39 +0000 Subject: [PATCH 05/28] Remove DM & expense filters --- src/CONST/index.ts | 3 +-- src/languages/de.ts | 3 +-- src/languages/en.ts | 3 +-- src/languages/es.ts | 3 +-- src/languages/fr.ts | 3 +-- src/languages/it.ts | 3 +-- src/languages/ja.ts | 3 +-- src/languages/nl.ts | 3 +-- src/languages/pl.ts | 3 +-- src/languages/pt-BR.ts | 3 +-- src/languages/zh-hans.ts | 3 +-- src/libs/SidebarUtils.ts | 9 +++------ src/pages/inbox/sidebar/InboxTabSelector.tsx | 6 +++--- src/types/onyx/InboxTab.ts | 1 + tests/perf-test/SidebarLinks.perf-test.tsx | 4 ++-- tests/unit/SidebarFilterTest.ts | 12 ++++++++++++ tests/unit/SidebarTest.ts | 4 ++-- tests/unit/SidebarUtilsTest.ts | 14 ++++++++------ 18 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index d3eed1c741bc..d06fa29aa180 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2245,9 +2245,8 @@ const CONST = { }, INBOX_TAB: { ALL: 'all', + TODO: 'todo', UNREAD: 'unread', - EXPENSES: 'expenses', - DMS: 'dms', }, THEME: { DEFAULT: 'system', diff --git a/src/languages/de.ts b/src/languages/de.ts index b57d98074f67..e3222cf3a286 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2896,9 +2896,8 @@ ${amount} für ${merchant} – ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/en.ts b/src/languages/en.ts index 3dd1a57e6add..62629563c128 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2967,9 +2967,8 @@ const translations = { }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 3412103bef82..a222c9dd554d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2775,9 +2775,8 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName) => `en ${policyName}`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 7a7719b72631..48d1f73eb2dd 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2904,9 +2904,8 @@ ${amount} pour ${merchant} - ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `dans ${policyName}`, diff --git a/src/languages/it.ts b/src/languages/it.ts index 7a16dcfd21e9..74008d5b625a 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2892,9 +2892,8 @@ ${amount} per ${merchant} - ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 744f96150eab..846c4766166c 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2868,9 +2868,8 @@ ${date} の ${merchant} への ${amount}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `${policyName} 内`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index e057783c5813..c522b5421e4e 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2889,9 +2889,8 @@ ${amount} voor ${merchant} - ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 3a736a4f510f..58a4b7b49757 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2883,9 +2883,8 @@ ${amount} dla ${merchant} - ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `w ${policyName}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 1bb944ab259b..0de92a894209 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2883,9 +2883,8 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `em ${policyName}`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 2b07dcc88697..c0641efcf72e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2810,9 +2810,8 @@ ${amount},商户:${merchant} - 日期:${date}`, }, inboxTabs: { all: 'All', + todo: 'To-do', unread: 'Unread', - expenses: 'Expenses', - dms: 'DMs', }, reportDetailsPage: { inWorkspace: (policyName: string) => `在 ${policyName} 中`, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 7594fb6e9a81..0e8efa2ba988 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -171,7 +171,6 @@ import { isChatThread, isConciergeChatReport, isDeprecatedGroupDM, - isDM, isDomainRoom, isExpenseReport, isExpenseRequest, @@ -180,7 +179,6 @@ import { isInvoiceReport, isInvoiceRoom, isIOUOwnedByCurrentUser, - isIOUReport, isJoinRequestInAdminRoom, isMoneyRequestReport, isOneOnOneChat, @@ -1505,14 +1503,13 @@ function filterReportsForInboxTab( } switch (activeTab) { + case CONST.INBOX_TAB.TODO: + // Reports with an outstanding GBR (requiresAttention) or RBR (errors) require the user's action. + return !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt; case CONST.INBOX_TAB.UNREAD: { const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); return isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; } - case CONST.INBOX_TAB.EXPENSES: - return isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report) || isPolicyExpenseChat(report); - case CONST.INBOX_TAB.DMS: - return isDM(report) || isSelfDM(report) || isGroupChatUtil(report); default: return true; } diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 4de90b6aa6e5..89c44a76b9c0 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type {ValueOf} from 'type-fest'; import TabSelectorBase from '@components/TabSelector/TabSelectorBase'; import TabSelectorContextProvider from '@components/TabSelector/TabSelectorContext'; import type {TabSelectorBaseItem} from '@components/TabSelector/types'; @@ -13,9 +14,8 @@ function InboxTabSelector() { const tabs: TabSelectorBaseItem[] = [ {key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all')}, + {key: CONST.INBOX_TAB.TODO, title: translate('inboxTabs.todo')}, {key: CONST.INBOX_TAB.UNREAD, title: translate('inboxTabs.unread')}, - {key: CONST.INBOX_TAB.EXPENSES, title: translate('inboxTabs.expenses')}, - {key: CONST.INBOX_TAB.DMS, title: translate('inboxTabs.dms')}, ]; return ( @@ -23,7 +23,7 @@ function InboxTabSelector() { setActiveTab(key as ValueOf)} size="small" /> diff --git a/src/types/onyx/InboxTab.ts b/src/types/onyx/InboxTab.ts index 616f5b89f764..3fc35c106b39 100644 --- a/src/types/onyx/InboxTab.ts +++ b/src/types/onyx/InboxTab.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** The active Inbox tab filter (All, To-do, or Unread) */ type InboxTab = ValueOf; export default InboxTab; diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index 46f35544a7ee..66f9493aa726 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -81,7 +81,7 @@ describe('SidebarLinks', () => { await Onyx.multiSet({ [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, ...mockedResponseMap, }); @@ -103,7 +103,7 @@ describe('SidebarLinks', () => { await Onyx.multiSet({ [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, ...mockedResponseMap, }); diff --git a/tests/unit/SidebarFilterTest.ts b/tests/unit/SidebarFilterTest.ts index c377ef0c6e25..1970472dfdc3 100644 --- a/tests/unit/SidebarFilterTest.ts +++ b/tests/unit/SidebarFilterTest.ts @@ -17,6 +17,7 @@ jest.mock('@libs/Permissions'); const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', IS_LOADING_APP: 'isLoadingApp', + NVP_PRIORITY_MODE: 'nvp_priorityMode', SESSION: 'session', BETAS: 'betas', COLLECTION: { @@ -371,6 +372,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -419,6 +421,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, @@ -489,6 +492,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${draftReport.reportID}`]: 'draft report message', @@ -542,6 +546,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -607,6 +612,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -708,6 +714,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -762,6 +769,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -814,6 +822,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -864,6 +873,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -910,6 +920,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.BETAS]: betas, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, @@ -945,6 +956,7 @@ xdescribe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportCollectionDataSet, diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index 54a42f4a4ecd..a0fbc44f2995 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -94,7 +94,7 @@ describe('Sidebar', () => { return act(async () => { await Onyx.multiSet({ [ONYXKEYS.BETAS]: betas, - + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportNameValuePairsCollection, @@ -157,7 +157,7 @@ describe('Sidebar', () => { return act(async () => { await Onyx.multiSet({ [ONYXKEYS.BETAS]: betas, - + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, ...reportNameValuePairsCollection, diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index 21d5e19e41f4..058c41917496 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -3776,10 +3776,10 @@ describe('SidebarUtils', () => { ]); const mockLocaleCompare = (a: string, b: string) => a.localeCompare(b); - const useAlphabeticalSort = false; + const priorityMode = CONST.PRIORITY_MODE.DEFAULT; // When the reports are sorted - const result = SidebarUtils.sortReportsToDisplayInLHN(reports, useAlphabeticalSort, mockLocaleCompare, undefined, undefined, undefined); + const result = SidebarUtils.sortReportsToDisplayInLHN(reports, priorityMode, mockLocaleCompare, undefined, undefined, undefined); // Then the reports are sorted in the correct order expect(result).toEqual(['0', '1', '2']); // Pinned first, Error second, Normal third @@ -3804,11 +3804,11 @@ describe('SidebarUtils', () => { const mockLocaleCompare = (a: string, b: string) => a.localeCompare(b); - // When the reports are sorted in default mode (not alphabetical) - const defaultResult = SidebarUtils.sortReportsToDisplayInLHN(reports, false, mockLocaleCompare, undefined, undefined, undefined); + // When the reports are sorted in default mode + const defaultResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.DEFAULT, mockLocaleCompare, undefined, undefined, undefined); - // When the reports are sorted in alphabetical mode - const gsdResult = SidebarUtils.sortReportsToDisplayInLHN(reports, true, mockLocaleCompare, undefined, undefined, undefined); + // When the reports are sorted in GSD mode + const gsdResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.GSD, mockLocaleCompare, undefined, undefined, undefined); // Then the reports are sorted in the correct order expect(defaultResult).toEqual(['1', '0']); // Most recent first (index 1 has later date) @@ -3828,6 +3828,7 @@ describe('SidebarUtils', () => { reports, updatedReportsKeys: [`${ONYXKEYS.COLLECTION.REPORT}999`], currentReportId: '1', + isInFocusMode: false, betas: [], transactions: {}, transactionViolations: {}, @@ -3851,6 +3852,7 @@ describe('SidebarUtils', () => { reports, updatedReportsKeys: ['0'], currentReportId: undefined, + isInFocusMode: false, betas: [], transactions: {}, transactionViolations: {}, From d8015354bb7a3a51585ca503f8d701fe42dec709 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sat, 30 May 2026 21:58:22 +0000 Subject: [PATCH 06/28] Perf: improve computing of unread reports --- src/hooks/useSidebarOrderedReports.tsx | 10 ++---- src/libs/SidebarUtils.ts | 43 +++++++++++++++----------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index 21d39d51b348..7dd37487b1bd 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -43,7 +43,7 @@ type SidebarOrderedReportsActionsContextValue = { setActiveTab: (tab: ValueOf) => void; }; -type ReportsToDisplayInLHN = Record; +type ReportsToDisplayInLHN = Record; const SidebarOrderedReportsStateContext = createContext({ orderedReports: [], @@ -296,10 +296,7 @@ function SidebarOrderedReportsContextProvider({ // Narrow the ordered reports down to the ones belonging to the active Inbox tab. The "All" tab // returns everything (still honoring Most Recent / Focus mode from the ordering above). - const filteredReportIDs = useMemo( - () => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab, reportNameValuePairs), - [orderedReportIDs, reportsToDisplayInLHN, activeTab, reportNameValuePairs], - ); + const filteredReportIDs = useMemo(() => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab), [orderedReportIDs, reportsToDisplayInLHN, activeTab]); // Get the actual reports based on the filtered IDs const getOrderedReports = useCallback( @@ -342,7 +339,7 @@ function SidebarOrderedReportsContextProvider({ filteredReportIDs.indexOf(derivedCurrentReportID) === -1 ) { const updatedReportIDs = getOrderedReportIDs(); - const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab, reportNameValuePairs); + const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab); const updatedReports = getOrderedReports(updatedFilteredIDs); return { orderedReports: updatedReports, @@ -371,7 +368,6 @@ function SidebarOrderedReportsContextProvider({ reportAttributes, activeTab, reportsToDisplayInLHN, - reportNameValuePairs, ]); const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab}), [clearLHNCache, setActiveTab]); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0e8efa2ba988..8fda67f74a6b 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -393,6 +393,7 @@ function getReportsToDisplayInLHN({ } const reportDraftComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]; + const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]); const {shouldDisplay, hasErrorsOtherThanFailedReceipt} = shouldDisplayReportInLHN({ report, @@ -404,7 +405,7 @@ function getReportsToDisplayInLHN({ draftComment: reportDraftComment, transactions, isOffline, - isReportArchived: isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]), + isReportArchived, reportAttributes, currentUserLogin, currentUserAccountID, @@ -412,8 +413,9 @@ function getReportsToDisplayInLHN({ if (shouldDisplay) { const requiresAttention = reportAttributes?.[report?.reportID]?.requiresAttention ?? false; - const hasAttentionOrError = requiresAttention || hasErrorsOtherThanFailedReceipt; - reportsToDisplay[reportID] = hasAttentionOrError ? {...report, requiresAttention, hasErrorsOtherThanFailedReceipt} : report; + const isUnreadReport = getIsUnreadReportForInboxTab(report, isReportArchived); + reportsToDisplay[reportID] = + requiresAttention || hasErrorsOtherThanFailedReceipt || isUnreadReport ? {...report, requiresAttention, hasErrorsOtherThanFailedReceipt, isUnreadReport} : report; } } @@ -474,6 +476,7 @@ function updateReportsToDisplayInLHN({ // Get the specific draft comment for this report instead of using a single draft comment for all reports // This fixes the issue where the current report's draft comment was incorrectly used to filter all reports const reportDraftComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]; + const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`] ?? {}); const {shouldDisplay, hasErrorsOtherThanFailedReceipt} = shouldDisplayReportInLHN({ report, @@ -485,7 +488,7 @@ function updateReportsToDisplayInLHN({ draftComment: reportDraftComment, transactions, isOffline, - isReportArchived: isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`] ?? {}), + isReportArchived, reportAttributes, currentUserLogin, currentUserAccountID, @@ -493,16 +496,19 @@ function updateReportsToDisplayInLHN({ if (shouldDisplay) { const requiresAttention = reportAttributes?.[report?.reportID]?.requiresAttention ?? false; - const hasAttentionOrError = requiresAttention || hasErrorsOtherThanFailedReceipt; + const isUnreadReport = getIsUnreadReportForInboxTab(report, isReportArchived); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const hasFlags = requiresAttention || hasErrorsOtherThanFailedReceipt || isUnreadReport; const existingEntry = displayedReports[reportID]; - if (hasAttentionOrError) { + if (hasFlags) { if ( existingEntry !== report || existingEntry?.requiresAttention !== requiresAttention || - existingEntry?.hasErrorsOtherThanFailedReceipt !== hasErrorsOtherThanFailedReceipt + existingEntry?.hasErrorsOtherThanFailedReceipt !== hasErrorsOtherThanFailedReceipt || + existingEntry?.isUnreadReport !== isUnreadReport ) { - getMutableCopy()[reportID] = {...report, requiresAttention, hasErrorsOtherThanFailedReceipt}; + getMutableCopy()[reportID] = {...report, requiresAttention, hasErrorsOtherThanFailedReceipt, isUnreadReport}; } } else if (existingEntry !== report) { getMutableCopy()[reportID] = report; @@ -1486,12 +1492,15 @@ function getRoomWelcomeMessage( * The "All" tab returns everything (and still honors Most Recent / Focus mode upstream); the other * tabs narrow that same set to unread, expense-related, or direct-message reports. */ -function filterReportsForInboxTab( - reportIDs: string[], - reportsToDisplay: ReportsToDisplayInLHN, - activeTab: ValueOf, - reportNameValuePairs?: OnyxCollection, -): string[] { +/** + * Whether a report should appear in the "Unread" Inbox tab: it has unread messages and is not muted. + * Computed once while building the LHN report set (which is cached/incremental) so the tab filter only reads a flag. + */ +function getIsUnreadReportForInboxTab(report: Report, isReportArchived: boolean): boolean { + return isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; +} + +function filterReportsForInboxTab(reportIDs: string[], reportsToDisplay: ReportsToDisplayInLHN, activeTab: ValueOf): string[] { if (activeTab === CONST.INBOX_TAB.ALL) { return reportIDs; } @@ -1506,10 +1515,8 @@ function filterReportsForInboxTab( case CONST.INBOX_TAB.TODO: // Reports with an outstanding GBR (requiresAttention) or RBR (errors) require the user's action. return !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt; - case CONST.INBOX_TAB.UNREAD: { - const isReportArchived = isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]); - return isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; - } + case CONST.INBOX_TAB.UNREAD: + return !!report.isUnreadReport; default: return true; } From a45d7d47d44948185454d10ed3c57cbc5f81241a Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Sun, 31 May 2026 00:00:32 +0000 Subject: [PATCH 07/28] Fix checks --- src/styles/index.ts | 25 ++++++++++----------- tests/unit/useSidebarOrderedReportsTest.tsx | 1 + 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 296ade6c010f..1bbd24d798ee 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4478,6 +4478,11 @@ const staticStyles = (theme: ThemeColors) => paddingBottom: 4, }, + tabTextSmall: { + fontSize: variables.fontSizeSmall, + lineHeight: 16, + }, + scrollableTabSelector: { flexGrow: 0, }, @@ -6351,19 +6356,13 @@ const dynamicStyles = (theme: ThemeColors) => top: fileTopPosition, }) satisfies ViewStyle, - tabText: (isSelected: boolean, hasIcon = false) => - ({ - marginLeft: hasIcon ? 8 : 0, - ...FontUtils.fontFamily.platform.EXP_NEUE_BOLD, - color: isSelected ? theme.text : theme.textSupporting, - lineHeight: variables.lineHeightLarge, - fontSize: variables.fontSizeLabel, - }) satisfies TextStyle, - - tabTextSmall: { - fontSize: variables.fontSizeSmall, - lineHeight: 16, - } satisfies TextStyle, + tabText: (isSelected: boolean, hasIcon = false): TextStyle => ({ + marginLeft: hasIcon ? 8 : 0, + ...FontUtils.fontFamily.platform.EXP_NEUE_BOLD, + color: isSelected ? theme.text : theme.textSupporting, + lineHeight: variables.lineHeightLarge, + fontSize: variables.fontSizeLabel, + }), tabBackground: (hovered: boolean, isFocused: boolean, isDisabled: boolean, background: string | Animated.AnimatedInterpolation) => { if (isDisabled) { diff --git a/tests/unit/useSidebarOrderedReportsTest.tsx b/tests/unit/useSidebarOrderedReportsTest.tsx index 442bd868d9d9..16da3a2ad99a 100644 --- a/tests/unit/useSidebarOrderedReportsTest.tsx +++ b/tests/unit/useSidebarOrderedReportsTest.tsx @@ -16,6 +16,7 @@ jest.mock('@libs/SidebarUtils', () => ({ sortReportsToDisplayInLHN: jest.fn(), getReportsToDisplayInLHN: jest.fn(), updateReportsToDisplayInLHN: jest.fn(), + filterReportsForInboxTab: jest.fn((reportIDs: string[]) => reportIDs), })); jest.mock('@libs/Navigation/Navigation', () => ({ getActiveRouteWithoutParams: jest.fn(() => ''), From efb10fa5a4298afe88a8f554ee437d2cba0e5cb9 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jun 2026 12:53:51 +0000 Subject: [PATCH 08/28] Add count badges --- src/hooks/useSidebarOrderedReports.tsx | 12 ++++++ src/libs/SidebarUtils.ts | 44 ++++++++++++++++---- src/pages/inbox/sidebar/InboxTabSelector.tsx | 10 +++-- tests/unit/useSidebarOrderedReportsTest.tsx | 1 + 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index 7dd37487b1bd..32f77dd9cb87 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -36,6 +36,7 @@ type SidebarOrderedReportsStateContextValue = { currentReportID: string | undefined; chatTabBrickRoad: BrickRoad; activeTab: ValueOf; + inboxTabCounts: Record, number>; }; type SidebarOrderedReportsActionsContextValue = { @@ -51,6 +52,11 @@ const SidebarOrderedReportsStateContext = createContext({ @@ -298,6 +304,9 @@ function SidebarOrderedReportsContextProvider({ // returns everything (still honoring Most Recent / Focus mode from the ordering above). const filteredReportIDs = useMemo(() => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab), [orderedReportIDs, reportsToDisplayInLHN, activeTab]); + // The count shown in each tab's badge, derived from the full "All" set (not the currently filtered view). + const inboxTabCounts = useMemo(() => SidebarUtils.getInboxTabCounts(orderedReportIDs, reportsToDisplayInLHN), [orderedReportIDs, reportsToDisplayInLHN]); + // Get the actual reports based on the filtered IDs const getOrderedReports = useCallback( (reportIDs: string[]): OnyxTypes.Report[] => { @@ -347,6 +356,7 @@ function SidebarOrderedReportsContextProvider({ currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(updatedReportIDs, reportAttributes), activeTab, + inboxTabCounts, }; } @@ -356,6 +366,7 @@ function SidebarOrderedReportsContextProvider({ currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(orderedReportIDs, reportAttributes), activeTab, + inboxTabCounts, }; }, [ getOrderedReportIDs, @@ -367,6 +378,7 @@ function SidebarOrderedReportsContextProvider({ orderedReports, reportAttributes, activeTab, + inboxTabCounts, reportsToDisplayInLHN, ]); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8fda67f74a6b..6fce8796468e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1487,11 +1487,6 @@ function getRoomWelcomeMessage( return welcomeMessage; } -/** - * Filters the already-ordered LHN report IDs down to the ones that belong to the active Inbox tab. - * The "All" tab returns everything (and still honors Most Recent / Focus mode upstream); the other - * tabs narrow that same set to unread, expense-related, or direct-message reports. - */ /** * Whether a report should appear in the "Unread" Inbox tab: it has unread messages and is not muted. * Computed once while building the LHN report set (which is cached/incremental) so the tab filter only reads a flag. @@ -1500,6 +1495,16 @@ function getIsUnreadReportForInboxTab(report: Report, isReportArchived: boolean) return isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; } +/** Whether a report belongs in the "To-do" Inbox tab: it has an outstanding GBR (requiresAttention) or RBR (errors). */ +function getIsTodoReportForInboxTab(report: ReportsToDisplayInLHN[string]): boolean { + return !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt; +} + +/** + * Filters the already-ordered LHN report IDs down to the ones that belong to the active Inbox tab. + * The "All" tab returns everything (and still honors Most Recent / Focus mode upstream); the other + * tabs narrow that same set to reports requiring action (To-do) or with unread messages (Unread). + */ function filterReportsForInboxTab(reportIDs: string[], reportsToDisplay: ReportsToDisplayInLHN, activeTab: ValueOf): string[] { if (activeTab === CONST.INBOX_TAB.ALL) { return reportIDs; @@ -1513,8 +1518,7 @@ function filterReportsForInboxTab(reportIDs: string[], reportsToDisplay: Reports switch (activeTab) { case CONST.INBOX_TAB.TODO: - // Reports with an outstanding GBR (requiresAttention) or RBR (errors) require the user's action. - return !!report.requiresAttention || !!report.hasErrorsOtherThanFailedReceipt; + return getIsTodoReportForInboxTab(report); case CONST.INBOX_TAB.UNREAD: return !!report.isUnreadReport; default: @@ -1523,6 +1527,31 @@ function filterReportsForInboxTab(reportIDs: string[], reportsToDisplay: Reports }); } +/** Counts how many of the ordered reports fall into each Inbox tab, for the count shown next to each tab label. */ +function getInboxTabCounts(reportIDs: string[], reportsToDisplay: ReportsToDisplayInLHN): Record, number> { + let todoCount = 0; + let unreadCount = 0; + + for (const reportID of reportIDs) { + const report = reportsToDisplay[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + if (!report) { + continue; + } + if (getIsTodoReportForInboxTab(report)) { + todoCount++; + } + if (report.isUnreadReport) { + unreadCount++; + } + } + + return { + [CONST.INBOX_TAB.ALL]: reportIDs.length, + [CONST.INBOX_TAB.TODO]: todoCount, + [CONST.INBOX_TAB.UNREAD]: unreadCount, + }; +} + // Exported for unit testing only. Do not use directly in production code. export { categorizeReportsForLHN as _categorizeReportsForLHN, @@ -1540,4 +1569,5 @@ export default { updateReportsToDisplayInLHN, shouldDisplayReportInLHN, filterReportsForInboxTab, + getInboxTabCounts, }; diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 89c44a76b9c0..61196cc2d81d 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -9,13 +9,15 @@ import CONST from '@src/CONST'; function InboxTabSelector() { const {translate} = useLocalize(); - const {activeTab} = useSidebarOrderedReportsState(); + const {activeTab, inboxTabCounts} = useSidebarOrderedReportsState(); const {setActiveTab} = useSidebarOrderedReportsActions(); + const getBadgeText = (count: number) => (count > 0 ? count.toString() : undefined); + const tabs: TabSelectorBaseItem[] = [ - {key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all')}, - {key: CONST.INBOX_TAB.TODO, title: translate('inboxTabs.todo')}, - {key: CONST.INBOX_TAB.UNREAD, title: translate('inboxTabs.unread')}, + {key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all'), badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.ALL])}, + {key: CONST.INBOX_TAB.TODO, title: translate('inboxTabs.todo'), badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.TODO])}, + {key: CONST.INBOX_TAB.UNREAD, title: translate('inboxTabs.unread'), badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.UNREAD])}, ]; return ( diff --git a/tests/unit/useSidebarOrderedReportsTest.tsx b/tests/unit/useSidebarOrderedReportsTest.tsx index 16da3a2ad99a..aa8d3bf916da 100644 --- a/tests/unit/useSidebarOrderedReportsTest.tsx +++ b/tests/unit/useSidebarOrderedReportsTest.tsx @@ -17,6 +17,7 @@ jest.mock('@libs/SidebarUtils', () => ({ getReportsToDisplayInLHN: jest.fn(), updateReportsToDisplayInLHN: jest.fn(), filterReportsForInboxTab: jest.fn((reportIDs: string[]) => reportIDs), + getInboxTabCounts: jest.fn(() => ({})), })); jest.mock('@libs/Navigation/Navigation', () => ({ getActiveRouteWithoutParams: jest.fn(() => ''), From 2b36db9aa456c41e6d780482c52ba20907135685 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jun 2026 14:03:09 +0000 Subject: [PATCH 09/28] Change copy --- src/languages/de.ts | 2 +- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/languages/fr.ts | 2 +- src/languages/it.ts | 2 +- src/languages/ja.ts | 2 +- src/languages/nl.ts | 2 +- src/languages/pl.ts | 2 +- src/languages/pt-BR.ts | 2 +- src/languages/zh-hans.ts | 2 +- src/pages/inbox/sidebar/InboxTabSelector.tsx | 42 +++++++++++++++----- 11 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index e3222cf3a286..6ffbda993d8d 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2896,7 +2896,7 @@ ${amount} für ${merchant} – ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 62629563c128..817b74657fda 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2967,7 +2967,7 @@ const translations = { }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/es.ts b/src/languages/es.ts index a222c9dd554d..a5f734aeade3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2775,7 +2775,7 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 48d1f73eb2dd..75089caf711c 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2904,7 +2904,7 @@ ${amount} pour ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/it.ts b/src/languages/it.ts index 74008d5b625a..b3262b4dc363 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2892,7 +2892,7 @@ ${amount} per ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 846c4766166c..5d612b92f598 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2868,7 +2868,7 @@ ${date} の ${merchant} への ${amount}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index c522b5421e4e..9797bfd35fde 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2889,7 +2889,7 @@ ${amount} voor ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 58a4b7b49757..b3d149e4a055 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2883,7 +2883,7 @@ ${amount} dla ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 0de92a894209..fb2c6aeec2b0 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2883,7 +2883,7 @@ ${amount} para ${merchant} - ${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index c0641efcf72e..2ee02c1d7e3f 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2810,7 +2810,7 @@ ${amount},商户:${merchant} - 日期:${date}`, }, inboxTabs: { all: 'All', - todo: 'To-do', + todo: 'To-dos', unread: 'Unread', }, reportDetailsPage: { diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 61196cc2d81d..c049a6037533 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -1,33 +1,57 @@ import React from 'react'; +import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; import TabSelectorBase from '@components/TabSelector/TabSelectorBase'; import TabSelectorContextProvider from '@components/TabSelector/TabSelectorContext'; import type {TabSelectorBaseItem} from '@components/TabSelector/types'; import useLocalize from '@hooks/useLocalize'; import {useSidebarOrderedReportsActions, useSidebarOrderedReportsState} from '@hooks/useSidebarOrderedReports'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; function InboxTabSelector() { const {translate} = useLocalize(); + const styles = useThemeStyles(); const {activeTab, inboxTabCounts} = useSidebarOrderedReportsState(); const {setActiveTab} = useSidebarOrderedReportsActions(); const getBadgeText = (count: number) => (count > 0 ? count.toString() : undefined); const tabs: TabSelectorBaseItem[] = [ - {key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all'), badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.ALL])}, - {key: CONST.INBOX_TAB.TODO, title: translate('inboxTabs.todo'), badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.TODO])}, - {key: CONST.INBOX_TAB.UNREAD, title: translate('inboxTabs.unread'), badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.UNREAD])}, + { + key: CONST.INBOX_TAB.ALL, + title: translate('inboxTabs.all'), + badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.ALL]), + isBadgeCondensed: true, + badgeStyles: styles.inboxTabBadge, + }, + { + key: CONST.INBOX_TAB.UNREAD, + title: translate('inboxTabs.unread'), + badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.UNREAD]), + isBadgeCondensed: true, + badgeStyles: styles.inboxTabBadge, + }, + { + key: CONST.INBOX_TAB.TODO, + title: translate('inboxTabs.todo'), + badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.TODO]), + isBadgeCondensed: true, + badgeStyles: styles.inboxTabBadge, + }, ]; return ( - setActiveTab(key as ValueOf)} - size="small" - /> + + setActiveTab(key as ValueOf)} + size="small" + equalWidth + /> + ); } From 7216fe1259c9e2817d24e9ef08b71747179f8312 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jun 2026 14:03:26 +0000 Subject: [PATCH 10/28] Adjust styles --- src/components/TabSelector/TabSelectorBase.tsx | 2 ++ src/components/TabSelector/TabSelectorItem.tsx | 4 ++++ src/components/TabSelector/types.ts | 14 +++++++++++++- src/styles/index.ts | 11 +++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/components/TabSelector/TabSelectorBase.tsx b/src/components/TabSelector/TabSelectorBase.tsx index 5f400417248f..03b89482707c 100644 --- a/src/components/TabSelector/TabSelectorBase.tsx +++ b/src/components/TabSelector/TabSelectorBase.tsx @@ -125,6 +125,8 @@ function TabSelectorBase({ equalWidth={equalWidth} size={size} badgeText={tab.badgeText} + isBadgeCondensed={tab.isBadgeCondensed} + badgeStyles={tab.badgeStyles} pendingAction={tab.pendingAction} isDisabled={tab.isDisabled} /> diff --git a/src/components/TabSelector/TabSelectorItem.tsx b/src/components/TabSelector/TabSelectorItem.tsx index 47c825b2f2ff..7723d8722750 100644 --- a/src/components/TabSelector/TabSelectorItem.tsx +++ b/src/components/TabSelector/TabSelectorItem.tsx @@ -36,6 +36,8 @@ function TabSelectorItem({ equalWidth = false, size, badgeText, + isBadgeCondensed = false, + badgeStyles, isDisabled = false, pendingAction, }: TabSelectorItemProps) { @@ -98,6 +100,8 @@ function TabSelectorItem({ )} diff --git a/src/components/TabSelector/types.ts b/src/components/TabSelector/types.ts index 20e0d3e0861d..813525bbc8ce 100644 --- a/src/components/TabSelector/types.ts +++ b/src/components/TabSelector/types.ts @@ -1,6 +1,6 @@ import type {MaterialTopTabBarProps} from '@react-navigation/material-top-tabs'; // eslint-disable-next-line no-restricted-imports -import type {Animated} from 'react-native'; +import type {Animated, StyleProp, ViewStyle} from 'react-native'; import type {ThemeColors} from '@styles/theme/types'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -45,6 +45,12 @@ type TabSelectorBaseItem = WithSentryLabel & { /** Text to display on the badge on the tab. */ badgeText?: string; + /** Whether the tab's badge should use the condensed (smaller) style. */ + isBadgeCondensed?: boolean; + + /** Additional styles for the tab's badge. */ + badgeStyles?: StyleProp; + /** Whether this tab is disabled */ isDisabled?: boolean; @@ -138,6 +144,12 @@ type TabSelectorItemProps = WithSentryLabel & { /** Text to display on the badge on the tab. */ badgeText?: string; + /** Whether the tab's badge should use the condensed (smaller) style. */ + isBadgeCondensed?: boolean; + + /** Additional styles for the tab's badge. */ + badgeStyles?: StyleProp; + /** Whether this tab is disabled */ isDisabled?: boolean; diff --git a/src/styles/index.ts b/src/styles/index.ts index 1bbd24d798ee..0d8a64abf75a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4474,8 +4474,8 @@ const staticStyles = (theme: ThemeColors) => }, tabSelectorContentContainerSmall: { - paddingTop: 4, - paddingBottom: 4, + paddingTop: 0, + paddingBottom: 0, }, tabTextSmall: { @@ -4483,6 +4483,13 @@ const staticStyles = (theme: ThemeColors) => lineHeight: 16, }, + inboxTabBadge: { + minWidth: 18, + height: 16, + marginLeft: 4, + justifyContent: 'center', + }, + scrollableTabSelector: { flexGrow: 0, }, From a81d7a669ea479670b2b3be1d0fe76012bbdb876 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jun 2026 14:24:58 +0000 Subject: [PATCH 11/28] Remove count badge from the "All" tab --- src/hooks/useSidebarOrderedReports.tsx | 3 +-- src/libs/SidebarUtils.ts | 5 ++--- src/pages/inbox/sidebar/InboxTabSelector.tsx | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index 32f77dd9cb87..9c9350fa19c6 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -36,7 +36,7 @@ type SidebarOrderedReportsStateContextValue = { currentReportID: string | undefined; chatTabBrickRoad: BrickRoad; activeTab: ValueOf; - inboxTabCounts: Record, number>; + inboxTabCounts: Record; }; type SidebarOrderedReportsActionsContextValue = { @@ -53,7 +53,6 @@ const SidebarOrderedReportsStateContext = createContext, number> { +/** Counts how many of the ordered reports fall into the To-do and Unread Inbox tabs, for the count badge shown on each. */ +function getInboxTabCounts(reportIDs: string[], reportsToDisplay: ReportsToDisplayInLHN): Record { let todoCount = 0; let unreadCount = 0; @@ -1546,7 +1546,6 @@ function getInboxTabCounts(reportIDs: string[], reportsToDisplay: ReportsToDispl } return { - [CONST.INBOX_TAB.ALL]: reportIDs.length, [CONST.INBOX_TAB.TODO]: todoCount, [CONST.INBOX_TAB.UNREAD]: unreadCount, }; diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index c049a6037533..294a5dd3a2fb 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -21,9 +21,6 @@ function InboxTabSelector() { { key: CONST.INBOX_TAB.ALL, title: translate('inboxTabs.all'), - badgeText: getBadgeText(inboxTabCounts[CONST.INBOX_TAB.ALL]), - isBadgeCondensed: true, - badgeStyles: styles.inboxTabBadge, }, { key: CONST.INBOX_TAB.UNREAD, From a85f0f3f12b8cadc9daf425be77a675c37420357 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 1 Jun 2026 14:28:34 +0000 Subject: [PATCH 12/28] Translations update --- src/languages/de.ts | 8 ++------ src/languages/es.ts | 12 ++++-------- src/languages/fr.ts | 16 ++++++---------- src/languages/it.ts | 14 +++++--------- src/languages/ja.ts | 10 +++------- src/languages/nl.ts | 10 +++------- src/languages/pl.ts | 10 +++------- src/languages/pt-BR.ts | 8 ++------ src/languages/zh-hans.ts | 8 ++------ 9 files changed, 30 insertions(+), 66 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 6ffbda993d8d..a006bca102bc 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2892,13 +2892,9 @@ ${amount} für ${merchant} – ${date}`, focusModeUpdateModal: { title: 'Willkommen im #Fokusmodus!', prompt: (priorityModePageUrl: string) => - `Behalte den Überblick, indem du nur ungelesene Chats oder Chats siehst, die deine Aufmerksamkeit benötigen. Keine Sorge, du kannst das jederzeit in den Einstellungen ändern.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `Behalten Sie den Überblick, indem Sie nur ungelesene Chats oder Chats anzeigen, die Ihre Aufmerksamkeit erfordern. Keine Sorge, Sie können dies jederzeit in den Einstellungen ändern.`, }, + inboxTabs: {all: 'Alle', todo: 'To-dos', unread: 'Ungelesen'}, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, generatingPDF: 'PDF erstellen', diff --git a/src/languages/es.ts b/src/languages/es.ts index a5f734aeade3..75d8450d23d7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2769,15 +2769,11 @@ ${amount} para ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: '¡Bienvenido al modo #concentración!', - prompt: (priorityModePageUrl) => - `Mantente al tanto de todo viendo sólo los chats no leídos o los que necesitan tu atención. No te preocupes, puedes cambiar el ajuste en cualquier momento desde la configuración.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + title: '¡Bienvenido al modo #focus!', + prompt: (priorityModePageUrl: string) => + `Mantente al tanto de todo viendo solo los chats sin leer o los chats que necesitan tu atención. No te preocupes, puedes cambiar esto en cualquier momento en los ajustes.`, }, + inboxTabs: {all: 'Todo', todo: 'Tareas pendientes', unread: 'Sin leer'}, reportDetailsPage: { inWorkspace: (policyName) => `en ${policyName}`, generatingPDF: 'Generar PDF', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 75089caf711c..f193a2ddae59 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2814,9 +2814,9 @@ ${amount} pour ${merchant} - ${date}`, title: 'Modifier l’agent', agentName: 'Nom de l’agent', instructions: 'Écrire des instructions personnalisées', - chatWithAgent: 'Discuter avec l\u2019agent', + chatWithAgent: 'Discuter avec l’agent', copilotIntoAccount: 'Copilote dans le compte', - deleteAgent: 'Supprimer l\u2019agent', + deleteAgent: 'Supprimer l’agent', deleteAgentTitle: 'Supprimer l’agent ?', deleteAgentMessage: 'Voulez-vous vraiment supprimer cet agent ? Cette action est irréversible.', }, @@ -2898,15 +2898,11 @@ ${amount} pour ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: 'Bienvenue en mode #focus !', + title: 'Bienvenue dans le mode #focus !', prompt: (priorityModePageUrl: string) => - `Gardez le contrôle en affichant uniquement les discussions non lues ou celles qui nécessitent votre attention. Ne vous inquiétez pas, vous pouvez modifier ce paramètre à tout moment dans les paramètres.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `Gardez le contrôle en n’affichant que les discussions non lues ou celles qui nécessitent votre attention. Ne vous inquiétez pas, vous pouvez modifier ce réglage à tout moment dans les paramètres.`, }, + inboxTabs: {all: 'Tout', todo: 'Tâches à faire', unread: 'Non lu'}, reportDetailsPage: { inWorkspace: (policyName: string) => `dans ${policyName}`, generatingPDF: 'Générer le PDF', @@ -2989,7 +2985,7 @@ ${amount} pour ${merchant} - ${date}`, phoneOrEmail: 'Téléphone ou e-mail', error: { agentSignInBlocked: - 'Les comptes d\u2019agent ne permettent pas de se connecter directement. Pour utiliser un agent, connectez-vous avec votre propre compte et accédez-y via Copilot.', + 'Les comptes d’agent ne permettent pas de se connecter directement. Pour utiliser un agent, connectez-vous avec votre propre compte et accédez-y via Copilot.', invalidFormatEmailLogin: 'L’adresse e-mail saisie est invalide. Veuillez corriger le format et réessayer.', }, cannotGetAccountDetails: 'Impossible de récupérer les détails du compte. Veuillez essayer de vous reconnecter.', diff --git a/src/languages/it.ts b/src/languages/it.ts index b3262b4dc363..a28298ae0973 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2802,8 +2802,8 @@ ${amount} per ${merchant} - ${date}`, title: 'Modifica agente', agentName: 'Nome agente', instructions: 'Scrivi istruzioni personalizzate', - chatWithAgent: 'Chatta con l\u2019agente', - copilotIntoAccount: 'Copilot nell\u2019account', + chatWithAgent: 'Chatta con l’agente', + copilotIntoAccount: 'Copilot nell’account', deleteAgent: 'Elimina agente', deleteAgentTitle: 'Eliminare agente?', deleteAgentMessage: 'Sei sicuro di voler eliminare questo agente? Questa azione non può essere annullata.', @@ -2886,15 +2886,11 @@ ${amount} per ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: 'Benvenuto/a nella modalità #focus!', + title: 'Benvenuto nella modalità #focus!', prompt: (priorityModePageUrl: string) => - `Resta sempre aggiornato vedendo solo le chat non lette o quelle che richiedono la tua attenzione. Non preoccuparti, puoi modificare questa impostazione in qualsiasi momento nelle impostazioni.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `Tieniti sempre aggiornato vedendo solo le chat non lette o quelle che richiedono la tua attenzione. Non preoccuparti, puoi cambiare questa impostazione in qualsiasi momento nelle impostazioni.`, }, + inboxTabs: {all: 'Tutto', todo: 'Attività da fare', unread: 'Non letti'}, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, generatingPDF: 'Genera PDF', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 5d612b92f598..dc575c058d53 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2862,15 +2862,11 @@ ${date} の ${merchant} への ${amount}`, }, }, focusModeUpdateModal: { - title: '#focusモードへようこそ!', + title: '#focus モードへようこそ!', prompt: (priorityModePageUrl: string) => - `未読のチャットや対応が必要なチャットだけを表示して、常に状況を把握しましょう。いつでも設定から変更できます。`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `未読のチャットや対応が必要なチャットだけを表示して、状況を常に把握しましょう。設定から、いつでもこの設定を変更できます。`, }, + inboxTabs: {all: 'すべて', todo: 'To-do リスト', unread: '未読'}, reportDetailsPage: { inWorkspace: (policyName: string) => `${policyName} 内`, generatingPDF: 'PDFを生成', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 9797bfd35fde..fb26f2494b1b 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2883,15 +2883,11 @@ ${amount} voor ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: 'Welkom bij de #focus-modus!', + title: 'Welkom in de #focus-modus!', prompt: (priorityModePageUrl: string) => - `Houd het overzicht door alleen ongelezen chats of chats die je aandacht nodig hebben te zien. Geen zorgen, je kunt dit op elk moment wijzigen in de instellingen.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `Blijf alles bij door alleen ongelezen chats of chats te zien die je aandacht nodig hebben. Geen zorgen, je kunt dit op elk moment wijzigen in de instellingen.`, }, + inboxTabs: {all: 'Alles', todo: 'To-do’s', unread: 'Ongelezen'}, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, generatingPDF: 'PDF genereren', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index b3d149e4a055..4d982df7ad57 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2877,15 +2877,11 @@ ${amount} dla ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: 'Witamy w trybie #focus!', + title: 'Witaj w trybie #focus!', prompt: (priorityModePageUrl: string) => - `Miej wszystko pod kontrolą, wyświetlając tylko nieprzeczytane czaty lub czaty wymagające Twojej uwagi. Nie martw się, możesz to zmienić w dowolnym momencie w ustawieniach.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `Bądź na bieżąco, pokazując tylko nieprzeczytane czaty lub czaty wymagające twojej uwagi. Spokojnie, możesz to zmienić w każdej chwili w ustawieniach.`, }, + inboxTabs: {all: 'Wszystkie', todo: 'Zadania', unread: 'Nieprzeczytane'}, reportDetailsPage: { inWorkspace: (policyName: string) => `w ${policyName}`, generatingPDF: 'Wygeneruj PDF', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index fb2c6aeec2b0..a87b31515b8b 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2879,13 +2879,9 @@ ${amount} para ${merchant} - ${date}`, focusModeUpdateModal: { title: 'Bem-vindo ao modo #focus!', prompt: (priorityModePageUrl: string) => - `Mantenha tudo sob controle vendo apenas os chats não lidos ou que precisam da sua atenção. Não se preocupe, você pode alterar isso a qualquer momento em configurações.`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + `Fique por dentro de tudo vendo apenas os chats não lidos ou que precisam da sua atenção. Não se preocupe, você pode mudar isso a qualquer momento em configurações.`, }, + inboxTabs: {all: 'Tudo', todo: 'Tarefas', unread: 'Não lidas'}, reportDetailsPage: { inWorkspace: (policyName: string) => `em ${policyName}`, generatingPDF: 'Gerar PDF', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 2ee02c1d7e3f..ed45e36029f5 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2806,13 +2806,9 @@ ${amount},商户:${merchant} - 日期:${date}`, }, focusModeUpdateModal: { title: '欢迎进入 #focus 模式!', - prompt: (priorityModePageUrl: string) => `通过仅查看未读聊天或需要你关注的聊天来随时掌握进展。别担心,你可以随时在设置中更改此项。`, - }, - inboxTabs: { - all: 'All', - todo: 'To-dos', - unread: 'Unread', + prompt: (priorityModePageUrl: string) => `只查看未读或需要你关注的聊天,时刻掌握最新进展。别担心,你随时可以在设置中更改此项。`, }, + inboxTabs: {all: '全部', todo: '待办事项', unread: '未读'}, reportDetailsPage: { inWorkspace: (policyName: string) => `在 ${policyName} 中`, generatingPDF: '生成 PDF', From 600c296af4d3bf030dde09d7c894c4c9e56bd43a Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 2 Jun 2026 12:39:37 +0000 Subject: [PATCH 13/28] Use regular sized tabs --- src/components/TabSelector/TabLabel.tsx | 11 +++-------- src/components/TabSelector/TabSelectorBase.tsx | 4 +--- src/components/TabSelector/TabSelectorItem.tsx | 3 --- src/components/TabSelector/types.ts | 10 +--------- src/pages/inbox/sidebar/InboxTabSelector.tsx | 1 - src/styles/index.ts | 15 --------------- 6 files changed, 5 insertions(+), 39 deletions(-) diff --git a/src/components/TabSelector/TabLabel.tsx b/src/components/TabSelector/TabLabel.tsx index c00d939cdb44..e47490b89f38 100644 --- a/src/components/TabSelector/TabLabel.tsx +++ b/src/components/TabSelector/TabLabel.tsx @@ -5,7 +5,6 @@ import type {StyleProp, TextStyle} from 'react-native'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import type {TabSelectorSize} from './types'; type TabLabelProps = { /** Title of the tab */ @@ -22,20 +21,16 @@ type TabLabelProps = { /** Text style */ textStyle?: StyleProp; - - /** Size variant */ - size?: TabSelectorSize; }; -function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1, hasIcon = false, textStyle, size}: TabLabelProps) { +function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1, hasIcon = false, textStyle}: TabLabelProps) { const styles = useThemeStyles(); - const smallTextStyle = size === 'small' ? styles.tabTextSmall : undefined; return ( {title} @@ -43,7 +38,7 @@ function TabLabel({title = '', activeOpacity = 0, inactiveOpacity = 1, hasIcon = {title} diff --git a/src/components/TabSelector/TabSelectorBase.tsx b/src/components/TabSelector/TabSelectorBase.tsx index 03b89482707c..f9b456c150fd 100644 --- a/src/components/TabSelector/TabSelectorBase.tsx +++ b/src/components/TabSelector/TabSelectorBase.tsx @@ -26,7 +26,6 @@ function TabSelectorBase({ position, shouldShowLabelWhenInactive = true, equalWidth = false, - size, shouldShowProductTrainingTooltip = false, renderProductTrainingTooltip, }: TabSelectorBaseProps) { @@ -64,7 +63,7 @@ function TabSelectorBase({ }} ref={containerRef} style={styles.scrollableTabSelector} - contentContainerStyle={[styles.tabSelectorContentContainer, size === 'small' && styles.tabSelectorContentContainerSmall]} + contentContainerStyle={styles.tabSelectorContentContainer} horizontal showsHorizontalScrollIndicator={false} keyboardShouldPersistTaps="handled" @@ -123,7 +122,6 @@ function TabSelectorBase({ shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip} renderProductTrainingTooltip={renderProductTrainingTooltip} equalWidth={equalWidth} - size={size} badgeText={tab.badgeText} isBadgeCondensed={tab.isBadgeCondensed} badgeStyles={tab.badgeStyles} diff --git a/src/components/TabSelector/TabSelectorItem.tsx b/src/components/TabSelector/TabSelectorItem.tsx index 7723d8722750..f99ff4889ed8 100644 --- a/src/components/TabSelector/TabSelectorItem.tsx +++ b/src/components/TabSelector/TabSelectorItem.tsx @@ -34,7 +34,6 @@ function TabSelectorItem({ shouldShowProductTrainingTooltip = false, renderProductTrainingTooltip, equalWidth = false, - size, badgeText, isBadgeCondensed = false, badgeStyles, @@ -61,7 +60,6 @@ function TabSelectorItem({ accessibilityRole={CONST.ROLE.TAB} style={[ styles.tabSelectorButton, - size === 'small' && styles.tabSelectorButtonSmall, styles.tabBackground(isHovered, isActive, isDisabled, backgroundColor), styles.userSelectNone, isOfflineWithPendingAction ? styles.offlineFeedbackPending : undefined, @@ -93,7 +91,6 @@ function TabSelectorItem({ activeOpacity={styles.tabOpacity(isDisabled, isHovered, isActive, activeOpacity, inactiveOpacity).opacity} inactiveOpacity={styles.tabOpacity(isDisabled, isHovered, isActive, inactiveOpacity, activeOpacity).opacity} hasIcon={!!icon} - size={size} /> )} {!!badgeText && ( diff --git a/src/components/TabSelector/types.ts b/src/components/TabSelector/types.ts index 813525bbc8ce..fe0092815357 100644 --- a/src/components/TabSelector/types.ts +++ b/src/components/TabSelector/types.ts @@ -58,8 +58,6 @@ type TabSelectorBaseItem = WithSentryLabel & { pendingAction?: PendingAction; }; -type TabSelectorSize = 'default' | 'small'; - type TabSelectorBaseProps = { /** Tabs to render. */ tabs: TabSelectorBaseItem[]; @@ -85,9 +83,6 @@ type TabSelectorBaseProps = { /** Whether tabs should have equal width. */ equalWidth?: boolean; - /** Size variant for the tabs. 'small' uses a compact 28px height. */ - size?: TabSelectorSize; - /** Determines whether the product training tooltip should be displayed to the user. */ shouldShowProductTrainingTooltip?: boolean; @@ -132,9 +127,6 @@ type TabSelectorItemProps = WithSentryLabel & { /** Whether tabs should have equal width */ equalWidth?: boolean; - /** Size variant for the tabs. */ - size?: TabSelectorSize; - /** Determines whether the product training tooltip should be displayed to the user. */ shouldShowProductTrainingTooltip?: boolean; @@ -202,4 +194,4 @@ type BackgroundColor = Animated.AnimatedInterpolation | string; type Opacity = 1 | 0 | Animated.AnimatedInterpolation; -export type {TabSelectorProps, BackgroundColor, GetBackgroundColorConfig, Opacity, GetOpacityConfig, TabSelectorBaseProps, TabSelectorBaseItem, TabSelectorItemProps, TabSelectorSize}; +export type {TabSelectorProps, BackgroundColor, GetBackgroundColorConfig, Opacity, GetOpacityConfig, TabSelectorBaseProps, TabSelectorBaseItem, TabSelectorItemProps}; diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 294a5dd3a2fb..8b22b909f140 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -45,7 +45,6 @@ function InboxTabSelector() { tabs={tabs} activeTabKey={activeTab} onTabPress={(key) => setActiveTab(key as ValueOf)} - size="small" equalWidth /> diff --git a/src/styles/index.ts b/src/styles/index.ts index bfed7d38289a..61dfc4816cec 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4456,11 +4456,6 @@ const staticStyles = (theme: ThemeColors) => scrollMarginInline: variables.tabSelectorScrollMarginInline, }, - tabSelectorButtonSmall: { - height: variables.componentSizeSmall, - paddingHorizontal: 12, - }, - tabSelector: { flexDirection: 'row', paddingHorizontal: 20, @@ -4473,16 +4468,6 @@ const staticStyles = (theme: ThemeColors) => paddingHorizontal: 20, }, - tabSelectorContentContainerSmall: { - paddingTop: 0, - paddingBottom: 0, - }, - - tabTextSmall: { - fontSize: variables.fontSizeSmall, - lineHeight: 16, - }, - inboxTabBadge: { minWidth: 18, height: 16, From 35feb10fbacd6f5d12d35bf4760c119bfe896c24 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 2 Jun 2026 14:15:21 +0000 Subject: [PATCH 14/28] Adjust styles --- src/pages/inbox/sidebar/InboxTabSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index 8b22b909f140..bc5856afa956 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -40,7 +40,7 @@ function InboxTabSelector() { return ( - + Date: Tue, 2 Jun 2026 14:17:20 +0000 Subject: [PATCH 15/28] Remove unused type --- src/types/onyx/InboxTab.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/types/onyx/InboxTab.ts diff --git a/src/types/onyx/InboxTab.ts b/src/types/onyx/InboxTab.ts deleted file mode 100644 index 3fc35c106b39..000000000000 --- a/src/types/onyx/InboxTab.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type {ValueOf} from 'type-fest'; -import type CONST from '@src/CONST'; - -/** The active Inbox tab filter (All, To-do, or Unread) */ -type InboxTab = ValueOf; - -export default InboxTab; From 5ab1b65be031a522f3c66d1b21ab903d3169c4a6 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jun 2026 00:10:14 +0000 Subject: [PATCH 16/28] Reduce bottom padding --- src/components/TabSelector/TabSelectorBase.tsx | 3 ++- src/components/TabSelector/types.ts | 3 +++ src/pages/inbox/sidebar/InboxTabSelector.tsx | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/TabSelector/TabSelectorBase.tsx b/src/components/TabSelector/TabSelectorBase.tsx index f9b456c150fd..bae90ce0a59c 100644 --- a/src/components/TabSelector/TabSelectorBase.tsx +++ b/src/components/TabSelector/TabSelectorBase.tsx @@ -26,6 +26,7 @@ function TabSelectorBase({ position, shouldShowLabelWhenInactive = true, equalWidth = false, + contentContainerStyles, shouldShowProductTrainingTooltip = false, renderProductTrainingTooltip, }: TabSelectorBaseProps) { @@ -63,7 +64,7 @@ function TabSelectorBase({ }} ref={containerRef} style={styles.scrollableTabSelector} - contentContainerStyle={styles.tabSelectorContentContainer} + contentContainerStyle={[styles.tabSelectorContentContainer, contentContainerStyles]} horizontal showsHorizontalScrollIndicator={false} keyboardShouldPersistTaps="handled" diff --git a/src/components/TabSelector/types.ts b/src/components/TabSelector/types.ts index fe0092815357..23e6f94e4317 100644 --- a/src/components/TabSelector/types.ts +++ b/src/components/TabSelector/types.ts @@ -83,6 +83,9 @@ type TabSelectorBaseProps = { /** Whether tabs should have equal width. */ equalWidth?: boolean; + /** Additional styles for the tabs' scroll content container. */ + contentContainerStyles?: StyleProp; + /** Determines whether the product training tooltip should be displayed to the user. */ shouldShowProductTrainingTooltip?: boolean; diff --git a/src/pages/inbox/sidebar/InboxTabSelector.tsx b/src/pages/inbox/sidebar/InboxTabSelector.tsx index bc5856afa956..6e80677cfbfe 100644 --- a/src/pages/inbox/sidebar/InboxTabSelector.tsx +++ b/src/pages/inbox/sidebar/InboxTabSelector.tsx @@ -40,12 +40,13 @@ function InboxTabSelector() { return ( - + setActiveTab(key as ValueOf)} equalWidth + contentContainerStyles={styles.pb1} /> From 8cd8c698fb09e086ea77577f426760e680289623 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jun 2026 00:15:19 +0000 Subject: [PATCH 17/28] Increase gap size between text and badge --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index ae4ce584aa13..8367adf255eb 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4471,7 +4471,7 @@ const staticStyles = (theme: ThemeColors) => inboxTabBadge: { minWidth: 18, height: 16, - marginLeft: 4, + marginLeft: 8, justifyContent: 'center', }, From bfca90f4beb1ba6cf608eb47ab361916311cab85 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Wed, 3 Jun 2026 23:19:24 +0000 Subject: [PATCH 18/28] Address comments and fix bug --- src/hooks/useSidebarOrderedReports.tsx | 63 ++++++++++++++++---- src/pages/inbox/sidebar/SidebarLinks.tsx | 6 +- src/pages/inbox/sidebar/SidebarLinksData.tsx | 4 +- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index a49ac761f681..d0361fc74c60 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -29,7 +29,9 @@ type SidebarOrderedReportsContextProviderProps = { }; type SidebarOrderedReportsStateContextValue = { - orderedReports: OnyxTypes.Report[]; + /** The reports rendered in the LHN for the active Inbox tab (a filtered subset of orderedReportIDs). */ + filteredReports: OnyxTypes.Report[]; + /** All ordered LHN report IDs, unfiltered by the active Inbox tab. Used for total counts (e.g. focus-mode switch) and brick road. */ orderedReportIDs: string[]; currentReportID: string | undefined; chatTabBrickRoad: BrickRoad; @@ -40,12 +42,13 @@ type SidebarOrderedReportsStateContextValue = { type SidebarOrderedReportsActionsContextValue = { clearLHNCache: () => void; setActiveTab: (tab: ValueOf) => void; + setStickyReportID: (reportID: string) => void; }; type ReportsToDisplayInLHN = Record; const SidebarOrderedReportsStateContext = createContext({ - orderedReports: [], + filteredReports: [], orderedReportIDs: [], currentReportID: '', chatTabBrickRoad: undefined, @@ -59,6 +62,7 @@ const SidebarOrderedReportsStateContext = createContext({ clearLHNCache: () => {}, setActiveTab: () => {}, + setStickyReportID: () => {}, }); const policyMapper = (policy: OnyxEntry): PartialPolicyForSidebar => @@ -291,9 +295,31 @@ function SidebarOrderedReportsContextProvider({ const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); - // Narrow the ordered reports down to the ones belonging to the active Inbox tab. The "All" tab + // When a report is opened from the To-do/Unread tab (see setStickyReportID), we remember it so it + // stays visible after viewing it removes it from the tab (e.g. it gets read). It's only set on a + // non-All tab, so opening a chat from the All tab never makes it appear under Unread/To-do. + const [stickyReport, setStickyReport] = useState<{reportID: string; tab: ValueOf} | undefined>(undefined); + + // Narrow the ordered reports down to the ones belonging to the active Inbox tab, plus the sticky + // report (kept visible while it's the one being viewed on the tab it was opened from). The "All" tab // returns everything (still honoring Most Recent / Focus mode from the ordering above). - const filteredReportIDs = useMemo(() => SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab), [orderedReportIDs, reportsToDisplayInLHN, activeTab]); + const stickyReportID = stickyReport?.reportID; + const stickyReportTab = stickyReport?.tab; + const filteredReportIDs = useMemo(() => { + const base = SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab); + if ( + activeTab === CONST.INBOX_TAB.ALL || + !stickyReportID || + stickyReportTab !== activeTab || + stickyReportID !== derivedCurrentReportID || + base.includes(stickyReportID) || + !orderedReportIDs.includes(stickyReportID) + ) { + return base; + } + const baseSet = new Set(base); + return orderedReportIDs.filter((reportID) => baseSet.has(reportID) || reportID === stickyReportID); + }, [orderedReportIDs, reportsToDisplayInLHN, activeTab, stickyReportTab, stickyReportID, derivedCurrentReportID]); // The count shown in each tab's badge, derived from the full "All" set (not the currently filtered view). const inboxTabCounts = useMemo(() => SidebarUtils.getInboxTabCounts(orderedReportIDs, reportsToDisplayInLHN), [orderedReportIDs, reportsToDisplayInLHN]); @@ -309,7 +335,7 @@ function SidebarOrderedReportsContextProvider({ [chatReports], ); - const orderedReports = useMemo(() => getOrderedReports(filteredReportIDs), [getOrderedReports, filteredReportIDs]); + const filteredReports = useMemo(() => getOrderedReports(filteredReportIDs), [getOrderedReports, filteredReportIDs]); const clearLHNCache = useCallback(() => { Log.info('[useSidebarOrderedReports] Clearing sidebar cache manually via debug modal'); @@ -319,8 +345,23 @@ function SidebarOrderedReportsContextProvider({ const setActiveTab = useCallback((tab: ValueOf) => { setInboxTab(tab); + + // The sticky report is scoped to the tab it was opened from, so reset it when switching tabs. + setStickyReport(undefined); }, []); + // Called when a report is opened from the LHN. On the To-do/Unread tabs we remember it so it stays + // visible after viewing it removes it from the tab. On the All tab we keep nothing sticky. + const setStickyReportID = useCallback( + (reportID: string) => { + if (activeTab === CONST.INBOX_TAB.ALL) { + return; + } + setStickyReport({reportID, tab: activeTab}); + }, + [activeTab], + ); + const stateValue: SidebarOrderedReportsStateContextValue = useMemo(() => { // 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 @@ -342,8 +383,8 @@ function SidebarOrderedReportsContextProvider({ const updatedFilteredIDs = SidebarUtils.filterReportsForInboxTab(updatedReportIDs, reportsToDisplayInLHN, activeTab); const updatedReports = getOrderedReports(updatedFilteredIDs); return { - orderedReports: updatedReports, - orderedReportIDs: updatedFilteredIDs, + filteredReports: updatedReports, + orderedReportIDs: updatedReportIDs, currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(updatedReportIDs, reportAttributes), activeTab, @@ -352,8 +393,8 @@ function SidebarOrderedReportsContextProvider({ } return { - orderedReports, - orderedReportIDs: filteredReportIDs, + filteredReports, + orderedReportIDs, currentReportID: derivedCurrentReportID, chatTabBrickRoad: getChatTabBrickRoad(orderedReportIDs, reportAttributes), activeTab, @@ -366,14 +407,14 @@ function SidebarOrderedReportsContextProvider({ derivedCurrentReportID, shouldUseNarrowLayout, getOrderedReports, - orderedReports, + filteredReports, reportAttributes, activeTab, inboxTabCounts, reportsToDisplayInLHN, ]); - const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab}), [clearLHNCache, setActiveTab]); + const actionsValue: SidebarOrderedReportsActionsContextValue = useMemo(() => ({clearLHNCache, setActiveTab, setStickyReportID}), [clearLHNCache, setActiveTab, setStickyReportID]); useEffect(() => { const hookExecutionDuration = performance.now() - hookStartTime.current; diff --git a/src/pages/inbox/sidebar/SidebarLinks.tsx b/src/pages/inbox/sidebar/SidebarLinks.tsx index a367a114ca02..46e379148e9d 100644 --- a/src/pages/inbox/sidebar/SidebarLinks.tsx +++ b/src/pages/inbox/sidebar/SidebarLinks.tsx @@ -8,6 +8,7 @@ import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import {useSidebarOrderedReportsActions} from '@hooks/useSidebarOrderedReports'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {setSidebarLoaded} from '@libs/actions/App'; @@ -39,6 +40,7 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {setStickyReportID} = useSidebarOrderedReportsActions(); const [isLoadingReportData = true] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); useEffect(() => { @@ -69,9 +71,11 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO cancelSpan(`${CONST.TELEMETRY.SPAN_OPEN_REPORT}_${option.reportID}`); return; } + // Keep this report visible in the active To-do/Unread tab even after opening it marks it read. + setStickyReportID(option.reportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID, actionTargetReportActionID)); }, - [shouldUseNarrowLayout, isActiveReport], + [shouldUseNarrowLayout, isActiveReport, setStickyReportID], ); const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT; diff --git a/src/pages/inbox/sidebar/SidebarLinksData.tsx b/src/pages/inbox/sidebar/SidebarLinksData.tsx index 1c4879c15884..f6cac99bfaae 100644 --- a/src/pages/inbox/sidebar/SidebarLinksData.tsx +++ b/src/pages/inbox/sidebar/SidebarLinksData.tsx @@ -23,7 +23,7 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) { const {translate} = useLocalize(); const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE); - const {orderedReports, currentReportID} = useSidebarOrderedReportsState('SidebarLinksData'); + const {filteredReports, currentReportID} = useSidebarOrderedReportsState('SidebarLinksData'); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; @@ -80,7 +80,7 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) { priorityMode={priorityMode ?? CONST.PRIORITY_MODE.DEFAULT} // Data props: isActiveReport={isActiveReport} - optionListItems={orderedReports} + optionListItems={filteredReports} /> ); From be8fdad3131cd155023298a1277e2f75f0007b83 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Thu, 4 Jun 2026 00:02:35 +0000 Subject: [PATCH 19/28] Fix empty view flashing briefly when opening the only unread/todo report --- src/hooks/useSidebarOrderedReports.tsx | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index d0361fc74c60..c15e52fe29ae 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -300,26 +300,22 @@ function SidebarOrderedReportsContextProvider({ // non-All tab, so opening a chat from the All tab never makes it appear under Unread/To-do. const [stickyReport, setStickyReport] = useState<{reportID: string; tab: ValueOf} | undefined>(undefined); - // Narrow the ordered reports down to the ones belonging to the active Inbox tab, plus the sticky - // report (kept visible while it's the one being viewed on the tab it was opened from). The "All" tab - // returns everything (still honoring Most Recent / Focus mode from the ordering above). + // The reports for the active tab, plus the sticky report opened from it (kept visible even after it's read). const stickyReportID = stickyReport?.reportID; const stickyReportTab = stickyReport?.tab; const filteredReportIDs = useMemo(() => { - const base = SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab); - if ( - activeTab === CONST.INBOX_TAB.ALL || - !stickyReportID || - stickyReportTab !== activeTab || - stickyReportID !== derivedCurrentReportID || - base.includes(stickyReportID) || - !orderedReportIDs.includes(stickyReportID) - ) { - return base; + const baseFilteredReportIDs = SidebarUtils.filterReportsForInboxTab(orderedReportIDs, reportsToDisplayInLHN, activeTab); + if (activeTab === CONST.INBOX_TAB.ALL || !stickyReportID || stickyReportTab !== activeTab || baseFilteredReportIDs.includes(stickyReportID)) { + return baseFilteredReportIDs; + } + if (!orderedReportIDs.includes(stickyReportID)) { + // While opening the report, reading it can briefly drop it from the LHN set entirely (before + // navigation marks it as the focused report). Keep it at the top so the list doesn't flash empty. + return [stickyReportID, ...baseFilteredReportIDs]; } - const baseSet = new Set(base); + const baseSet = new Set(baseFilteredReportIDs); return orderedReportIDs.filter((reportID) => baseSet.has(reportID) || reportID === stickyReportID); - }, [orderedReportIDs, reportsToDisplayInLHN, activeTab, stickyReportTab, stickyReportID, derivedCurrentReportID]); + }, [orderedReportIDs, reportsToDisplayInLHN, activeTab, stickyReportTab, stickyReportID]); // The count shown in each tab's badge, derived from the full "All" set (not the currently filtered view). const inboxTabCounts = useMemo(() => SidebarUtils.getInboxTabCounts(orderedReportIDs, reportsToDisplayInLHN), [orderedReportIDs, reportsToDisplayInLHN]); @@ -373,7 +369,11 @@ function SidebarOrderedReportsContextProvider({ // requirement for web. Consider a case, where we have report with expenses and we click on // any expense, a new LHN item is added in the list and is visible on web. But on mobile, we // just navigate to the screen with expense details, so there seems no point to execute this logic on mobile. + // Only the "All" tab force-regenerates to surface the current report. On the To-do/Unread tabs the + // sticky-aware filteredReportIDs already keeps the opened report visible, and re-filtering here + // (without the sticky report) would briefly empty the list while opening it. if ( + activeTab === CONST.INBOX_TAB.ALL && (!shouldUseNarrowLayout || filteredReportIDs.length === 0) && derivedCurrentReportID && derivedCurrentReportID !== '-1' && From 7afa1ab788d6ca1178bc9e613bfc0b04ab2b8c86 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Thu, 4 Jun 2026 21:40:44 +0000 Subject: [PATCH 20/28] Address comment --- src/libs/SidebarUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2ef287ce2357..2848d3984d74 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1502,7 +1502,9 @@ function getRoomWelcomeMessage( * Computed once while building the LHN report set (which is cached/incremental) so the tab filter only reads a flag. */ function getIsUnreadReportForInboxTab(report: Report, isReportArchived: boolean): boolean { - return isUnread(report, undefined, isReportArchived) && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; + // The `lastActorAccountID` guard matches getOptionData: it keeps chats whose only visible message was + // deleted out of the Unread tab even though isUnread() can still be true (lastVisibleActionCreated isn't reset). + return isUnread(report, undefined, isReportArchived) && !!report.lastActorAccountID && getReportNotificationPreference(report) !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE; } /** Whether a report belongs in the "To-do" Inbox tab: it has an outstanding GBR (requiresAttention) or RBR (errors). */ From 6f2fa59233760402dfdcc72357b6d6939c284cfd Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 5 Jun 2026 23:47:25 +0000 Subject: [PATCH 21/28] Fix unequal tab width in native --- src/components/TabSelector/TabSelectorBase.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/TabSelector/TabSelectorBase.tsx b/src/components/TabSelector/TabSelectorBase.tsx index b5aaab0a130e..7209dc573e51 100644 --- a/src/components/TabSelector/TabSelectorBase.tsx +++ b/src/components/TabSelector/TabSelectorBase.tsx @@ -62,7 +62,11 @@ function TabSelectorBase({ }} ref={containerRef} style={styles.scrollableTabSelector} - contentContainerStyle={[styles.tabSelectorContentContainer, contentContainerStyles]} + // On iOS a horizontal ScrollView lays out its content along an unbounded main axis, so flex-1 tabs + // (equalWidth) divide their intrinsic content width instead of the viewport. Giving the content + // container a definite width lets the flex children split it evenly. Scoped to equalWidth so normal + // overflowing/scrollable tab rows are not constrained. + contentContainerStyle={[styles.tabSelectorContentContainer, equalWidth && styles.w100, contentContainerStyles]} horizontal showsHorizontalScrollIndicator={false} keyboardShouldPersistTaps="handled" From 031aa66291dd824d74ec8ef8e6520b1b31bea1a2 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 8 Jun 2026 23:50:35 +0000 Subject: [PATCH 22/28] Update empty view copy and add link to the "all" tab --- .../LHNOptionsList/LHNEmptyState.tsx | 20 +++++++++++++++++++ src/languages/de.ts | 4 ++++ src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ src/languages/fr.ts | 4 ++++ src/languages/it.ts | 4 ++++ src/languages/ja.ts | 4 ++++ src/languages/nl.ts | 4 ++++ src/languages/pl.ts | 4 ++++ src/languages/pt-BR.ts | 4 ++++ src/languages/zh-hans.ts | 4 ++++ 11 files changed, 60 insertions(+) diff --git a/src/components/LHNOptionsList/LHNEmptyState.tsx b/src/components/LHNOptionsList/LHNEmptyState.tsx index 40fed2097bc6..b4681315fc39 100644 --- a/src/components/LHNOptionsList/LHNEmptyState.tsx +++ b/src/components/LHNOptionsList/LHNEmptyState.tsx @@ -6,9 +6,11 @@ import Icon from '@components/Icon'; import TextBlock from '@components/TextBlock'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import {useSidebarOrderedReportsActions, useSidebarOrderedReportsState} from '@hooks/useSidebarOrderedReports'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import useEmptyLHNIllustration from './useEmptyLHNIllustration'; function LHNEmptyState() { @@ -17,6 +19,24 @@ function LHNEmptyState() { const {translate} = useLocalize(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass', 'Plus']); const emptyLHNIllustration = useEmptyLHNIllustration(); + const {activeTab} = useSidebarOrderedReportsState(); + const {setActiveTab} = useSidebarOrderedReportsActions(); + + if (activeTab === CONST.INBOX_TAB.UNREAD || activeTab === CONST.INBOX_TAB.TODO) { + const title = activeTab === CONST.INBOX_TAB.UNREAD ? translate('common.emptyLHN.noUnreadChats') : translate('common.emptyLHN.noTodos'); + + return ( + setActiveTab(CONST.INBOX_TAB.ALL)} + accessibilityLabel={title} + /> + ); + } const subtitle = ( diff --git a/src/languages/de.ts b/src/languages/de.ts index c243612f1896..5b495c9da38f 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -354,6 +354,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Finde einen Chat über die', subtitleText2: 'Schaltfläche oben oder erstellen Sie etwas mit der', subtitleText3: 'Schaltfläche unten.', + noUnreadChats: 'Keine ungelesenen Chats', + noTodos: 'Keine Aufgaben', + caughtUp: 'Du bist auf dem neuesten Stand. Gut gemacht!', + seeAllChats: 'Alle Chats anzeigen', }, businessName: 'Firmenname', clear: 'Löschen', diff --git a/src/languages/en.ts b/src/languages/en.ts index 5f4f51c6b94a..cf3ff8f64be4 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -369,6 +369,10 @@ const translations = { subtitleText1: 'Find a chat using the', subtitleText2: 'button above, or create something using the', subtitleText3: 'button below.', + noUnreadChats: 'No unread chats', + noTodos: 'No to-dos', + caughtUp: "You're all caught up. Well done!", + seeAllChats: 'See all chats', }, businessName: 'Business name', clear: 'Clear', diff --git a/src/languages/es.ts b/src/languages/es.ts index af8e6ae47cf3..aee1f08afd8b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -310,6 +310,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Encuentra un chat usando el botón', subtitleText2: 'o crea algo usando el botón', subtitleText3: '.', + noUnreadChats: 'No hay chats sin leer', + noTodos: 'No hay tareas pendientes', + caughtUp: '¡Estás al día. Bien hecho!', + seeAllChats: 'Ver todos los chats', }, businessName: 'Nombre de la empresa', clear: 'Borrar', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 76fe9e0e5260..453ec2defa1b 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -353,6 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Recherchez une discussion à l’aide de la', subtitleText2: 'bouton ci-dessus, ou créez quelque chose en utilisant le', subtitleText3: 'bouton ci-dessous.', + noUnreadChats: 'Aucun chat non lu', + noTodos: 'Aucune tâche', + caughtUp: 'Vous êtes à jour. Bien joué !', + seeAllChats: 'Voir tous les chats', }, businessName: 'Nom de l’entreprise', clear: 'Effacer', diff --git a/src/languages/it.ts b/src/languages/it.ts index edb1c6739271..000fb6e227a7 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -354,6 +354,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Trova una chat usando la', subtitleText2: 'pulsante sopra oppure crea qualcosa utilizzando il', subtitleText3: 'pulsante qui sotto.', + noUnreadChats: 'Nessuna chat non letta', + noTodos: 'Nessuna attività', + caughtUp: 'Sei aggiornato. Ben fatto!', + seeAllChats: 'Vedi tutte le chat', }, businessName: 'Nome azienda', clear: 'Pulisci', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 8569738fa3f3..b600c8079124 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -353,6 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'チャットを検索するには', subtitleText2: '上のボタン、または次を使って何かを作成する', subtitleText3: '下のボタンを押してください。', + noUnreadChats: '未読のチャットはありません', + noTodos: 'タスクはありません', + caughtUp: 'すべて確認済みです。お疲れさまでした!', + seeAllChats: 'すべてのチャットを表示', }, businessName: '会社名', clear: 'クリア', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index a9c54585631c..72bf0fafec9a 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -353,6 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Zoek een chat met de', subtitleText2: 'knop hierboven, of maak iets met de', subtitleText3: 'knop hieronder.', + noUnreadChats: 'Geen ongelezen chats', + noTodos: 'Geen taken', + caughtUp: 'Je bent helemaal bij. Goed gedaan!', + seeAllChats: 'Alle chats bekijken', }, businessName: 'Bedrijfsnaam', clear: 'Wissen', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index e49967481b7e..9606a22696bf 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -353,6 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Znajdź czat za pomocą', subtitleText2: 'przycisk powyżej lub utwórz coś za pomocą', subtitleText3: 'przycisk poniżej.', + noUnreadChats: 'Brak nieprzeczytanych czatów', + noTodos: 'Brak zadań', + caughtUp: 'Masz wszystko na bieżąco. Dobra robota!', + seeAllChats: 'Zobacz wszystkie czaty', }, businessName: 'Nazwa firmy', clear: 'Wyczyść', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 4c9c50eca9ab..380de6616a4f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -353,6 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Encontre um chat usando o', subtitleText2: 'botão acima ou crie algo usando o', subtitleText3: 'botão abaixo.', + noUnreadChats: 'Nenhum chat não lido', + noTodos: 'Nenhuma tarefa', + caughtUp: 'Você está em dia. Muito bem!', + seeAllChats: 'Ver todos os chats', }, businessName: 'Nome da empresa', clear: 'Limpar', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index f2ee9971e217..6189be08fff4 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -353,6 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: '使用以下方式查找聊天', subtitleText2: '上方的按钮,或使用以下内容创建', subtitleText3: '下方按钮。', + noUnreadChats: '没有未读聊天', + noTodos: '没有待办事项', + caughtUp: '你已全部处理完毕。干得好!', + seeAllChats: '查看所有聊天', }, businessName: '公司名称', clear: '清除', From fcfddc7cfa2fd1cf226be11a754ec24a4628d80e Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Mon, 8 Jun 2026 23:54:29 +0000 Subject: [PATCH 23/28] Translations --- src/languages/de.ts | 10 +++++----- src/languages/es.ts | 6 +++--- src/languages/fr.ts | 12 ++++++------ src/languages/it.ts | 8 ++++---- src/languages/ja.ts | 4 ++-- src/languages/nl.ts | 6 +++--- src/languages/pl.ts | 6 +++--- src/languages/pt-BR.ts | 8 ++++---- src/languages/zh-hans.ts | 6 +++--- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 5b495c9da38f..2f83da0fa036 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -355,8 +355,8 @@ const translations: TranslationDeepObject = { subtitleText2: 'Schaltfläche oben oder erstellen Sie etwas mit der', subtitleText3: 'Schaltfläche unten.', noUnreadChats: 'Keine ungelesenen Chats', - noTodos: 'Keine Aufgaben', - caughtUp: 'Du bist auf dem neuesten Stand. Gut gemacht!', + noTodos: 'Keine To-dos', + caughtUp: 'Sie sind auf dem neuesten Stand. Gut gemacht!', seeAllChats: 'Alle Chats anzeigen', }, businessName: 'Firmenname', @@ -2900,11 +2900,11 @@ ${amount} für ${merchant} – ${date}`, }, }, focusModeUpdateModal: { - title: 'Willkommen im #Fokusmodus!', + title: 'Willkommen im #Focus-Modus!', prompt: (priorityModePageUrl: string) => - `Behalten Sie den Überblick, indem Sie nur ungelesene Chats oder Chats anzeigen, die Ihre Aufmerksamkeit erfordern. Keine Sorge, Sie können dies jederzeit in den Einstellungen ändern.`, + `Behalten Sie den Überblick, indem Sie nur ungelesene Chats oder Chats sehen, die Ihre Aufmerksamkeit erfordern. Keine Sorge, Sie können dies jederzeit in den Einstellungen ändern.`, }, - inboxTabs: {all: 'Alle', todo: 'To-dos', unread: 'Ungelesen'}, + inboxTabs: {all: 'Alle', todo: 'Aufgaben', unread: 'Ungelesen'}, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, generatingPDF: 'PDF erstellen', diff --git a/src/languages/es.ts b/src/languages/es.ts index aee1f08afd8b..be3f37b58651 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -312,7 +312,7 @@ const translations: TranslationDeepObject = { subtitleText3: '.', noUnreadChats: 'No hay chats sin leer', noTodos: 'No hay tareas pendientes', - caughtUp: '¡Estás al día. Bien hecho!', + caughtUp: 'Te has puesto al día. ¡Bien hecho!', seeAllChats: 'Ver todos los chats', }, businessName: 'Nombre de la empresa', @@ -2777,9 +2777,9 @@ ${amount} para ${merchant} - ${date}`, focusModeUpdateModal: { title: '¡Bienvenido al modo #focus!', prompt: (priorityModePageUrl: string) => - `Mantente al tanto de todo viendo solo los chats sin leer o los chats que necesitan tu atención. No te preocupes, puedes cambiar esto en cualquier momento en los ajustes.`, + `Mantente al tanto de todo viendo solo los chats no leídos o los chats que necesitan tu atención. No te preocupes, puedes cambiarlo en cualquier momento en los ajustes.`, }, - inboxTabs: {all: 'Todo', todo: 'Tareas pendientes', unread: 'Sin leer'}, + inboxTabs: {all: 'Todo', todo: 'Tareas pendientes', unread: 'No leído'}, reportDetailsPage: { inWorkspace: (policyName) => `en ${policyName}`, generatingPDF: 'Generar PDF', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 453ec2defa1b..46c45edaeecb 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -353,10 +353,10 @@ const translations: TranslationDeepObject = { subtitleText1: 'Recherchez une discussion à l’aide de la', subtitleText2: 'bouton ci-dessus, ou créez quelque chose en utilisant le', subtitleText3: 'bouton ci-dessous.', - noUnreadChats: 'Aucun chat non lu', - noTodos: 'Aucune tâche', - caughtUp: 'Vous êtes à jour. Bien joué !', - seeAllChats: 'Voir tous les chats', + noUnreadChats: 'Aucune discussion non lue', + noTodos: 'Aucune tâche à faire', + caughtUp: 'Vous êtes à jour. Bravo !', + seeAllChats: 'Voir toutes les discussions', }, businessName: 'Nom de l’entreprise', clear: 'Effacer', @@ -2908,11 +2908,11 @@ ${amount} pour ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: 'Bienvenue dans le mode #focus !', + title: 'Bienvenue dans le mode #focus !', prompt: (priorityModePageUrl: string) => `Gardez le contrôle en n’affichant que les discussions non lues ou celles qui nécessitent votre attention. Ne vous inquiétez pas, vous pouvez modifier ce réglage à tout moment dans les paramètres.`, }, - inboxTabs: {all: 'Tout', todo: 'Tâches à faire', unread: 'Non lu'}, + inboxTabs: {all: 'Tout', todo: 'Tâches', unread: 'Non lu'}, reportDetailsPage: { inWorkspace: (policyName: string) => `dans ${policyName}`, generatingPDF: 'Générer le PDF', diff --git a/src/languages/it.ts b/src/languages/it.ts index 000fb6e227a7..bd835f31debe 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -355,8 +355,8 @@ const translations: TranslationDeepObject = { subtitleText2: 'pulsante sopra oppure crea qualcosa utilizzando il', subtitleText3: 'pulsante qui sotto.', noUnreadChats: 'Nessuna chat non letta', - noTodos: 'Nessuna attività', - caughtUp: 'Sei aggiornato. Ben fatto!', + noTodos: 'Nessuna attività da fare', + caughtUp: 'Hai gestito tutto. Ben fatto!', seeAllChats: 'Vedi tutte le chat', }, businessName: 'Nome azienda', @@ -2898,9 +2898,9 @@ ${amount} per ${merchant} - ${date}`, focusModeUpdateModal: { title: 'Benvenuto nella modalità #focus!', prompt: (priorityModePageUrl: string) => - `Tieniti sempre aggiornato vedendo solo le chat non lette o quelle che richiedono la tua attenzione. Non preoccuparti, puoi cambiare questa impostazione in qualsiasi momento nelle impostazioni.`, + `Tieniti al passo vedendo solo le chat non lette o quelle che richiedono la tua attenzione. Non preoccuparti, puoi cambiare questa impostazione in qualsiasi momento nelle impostazioni.`, }, - inboxTabs: {all: 'Tutto', todo: 'Attività da fare', unread: 'Non letti'}, + inboxTabs: {all: 'Tutti', todo: 'Attività da fare', unread: 'Non letti'}, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, generatingPDF: 'Genera PDF', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index b600c8079124..32fc6d4ac21e 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -354,7 +354,7 @@ const translations: TranslationDeepObject = { subtitleText2: '上のボタン、または次を使って何かを作成する', subtitleText3: '下のボタンを押してください。', noUnreadChats: '未読のチャットはありません', - noTodos: 'タスクはありません', + noTodos: 'To-do はありません', caughtUp: 'すべて確認済みです。お疲れさまでした!', seeAllChats: 'すべてのチャットを表示', }, @@ -2870,7 +2870,7 @@ ${date} の ${merchant} への ${amount}`, focusModeUpdateModal: { title: '#focus モードへようこそ!', prompt: (priorityModePageUrl: string) => - `未読のチャットや対応が必要なチャットだけを表示して、状況を常に把握しましょう。設定から、いつでもこの設定を変更できます。`, + `未読のチャットや対応が必要なチャットだけを表示して、状況を常に把握できるようにしましょう。いつでも設定で変更できます。`, }, inboxTabs: {all: 'すべて', todo: 'To-do リスト', unread: '未読'}, reportDetailsPage: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 72bf0fafec9a..27cfa6b13a83 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2893,11 +2893,11 @@ ${amount} voor ${merchant} - ${date}`, }, }, focusModeUpdateModal: { - title: 'Welkom in de #focus-modus!', + title: 'Welkom in #focus-modus!', prompt: (priorityModePageUrl: string) => - `Blijf alles bij door alleen ongelezen chats of chats te zien die je aandacht nodig hebben. Geen zorgen, je kunt dit op elk moment wijzigen in de instellingen.`, + `Blijf op de hoogte door alleen ongelezen chats of chats te zien die je aandacht nodig hebben. Geen zorgen, je kunt dit op elk moment wijzigen in de instellingen.`, }, - inboxTabs: {all: 'Alles', todo: 'To-do’s', unread: 'Ongelezen'}, + inboxTabs: {all: 'Alles', todo: 'Te doen', unread: 'Ongelezen'}, reportDetailsPage: { inWorkspace: (policyName: string) => `in ${policyName}`, generatingPDF: 'PDF genereren', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 9606a22696bf..15420e19e372 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -355,7 +355,7 @@ const translations: TranslationDeepObject = { subtitleText3: 'przycisk poniżej.', noUnreadChats: 'Brak nieprzeczytanych czatów', noTodos: 'Brak zadań', - caughtUp: 'Masz wszystko na bieżąco. Dobra robota!', + caughtUp: 'Ze wszystkim już się uporałeś. Dobra robota!', seeAllChats: 'Zobacz wszystkie czaty', }, businessName: 'Nazwa firmy', @@ -2889,9 +2889,9 @@ ${amount} dla ${merchant} - ${date}`, focusModeUpdateModal: { title: 'Witaj w trybie #focus!', prompt: (priorityModePageUrl: string) => - `Bądź na bieżąco, pokazując tylko nieprzeczytane czaty lub czaty wymagające twojej uwagi. Spokojnie, możesz to zmienić w każdej chwili w ustawieniach.`, + `Bądź na bieżąco, widząc tylko nieprzeczytane czaty lub czaty wymagające twojej uwagi. Spokojnie, możesz to zmienić w dowolnym momencie w ustawieniach.`, }, - inboxTabs: {all: 'Wszystkie', todo: 'Zadania', unread: 'Nieprzeczytane'}, + inboxTabs: {all: 'Wszystko', todo: 'Zadania do wykonania', unread: 'Nieprzeczytane'}, reportDetailsPage: { inWorkspace: (policyName: string) => `w ${policyName}`, generatingPDF: 'Wygeneruj PDF', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 380de6616a4f..ac2d21d694b1 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -354,9 +354,9 @@ const translations: TranslationDeepObject = { subtitleText2: 'botão acima ou crie algo usando o', subtitleText3: 'botão abaixo.', noUnreadChats: 'Nenhum chat não lido', - noTodos: 'Nenhuma tarefa', + noTodos: 'Nenhuma tarefa pendente', caughtUp: 'Você está em dia. Muito bem!', - seeAllChats: 'Ver todos os chats', + seeAllChats: 'Ver todas as conversas', }, businessName: 'Nome da empresa', clear: 'Limpar', @@ -2889,9 +2889,9 @@ ${amount} para ${merchant} - ${date}`, focusModeUpdateModal: { title: 'Bem-vindo ao modo #focus!', prompt: (priorityModePageUrl: string) => - `Fique por dentro de tudo vendo apenas os chats não lidos ou que precisam da sua atenção. Não se preocupe, você pode mudar isso a qualquer momento em configurações.`, + `Fique no controle vendo apenas chats não lidos ou que precisam da sua atenção. Não se preocupe, você pode mudar isso a qualquer momento em configurações.`, }, - inboxTabs: {all: 'Tudo', todo: 'Tarefas', unread: 'Não lidas'}, + inboxTabs: {all: 'Todos', todo: 'Pendências', unread: 'Não lidas'}, reportDetailsPage: { inWorkspace: (policyName: string) => `em ${policyName}`, generatingPDF: 'Gerar PDF', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 6189be08fff4..144782a3d77b 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -355,7 +355,7 @@ const translations: TranslationDeepObject = { subtitleText3: '下方按钮。', noUnreadChats: '没有未读聊天', noTodos: '没有待办事项', - caughtUp: '你已全部处理完毕。干得好!', + caughtUp: '你已经全部处理完了。干得好!', seeAllChats: '查看所有聊天', }, businessName: '公司名称', @@ -2811,8 +2811,8 @@ ${amount},商户:${merchant} - 日期:${date}`, }, }, focusModeUpdateModal: { - title: '欢迎进入 #focus 模式!', - prompt: (priorityModePageUrl: string) => `只查看未读或需要你关注的聊天,时刻掌握最新进展。别担心,你随时可以在设置中更改此项。`, + title: '欢迎使用 #focus 模式!', + prompt: (priorityModePageUrl: string) => `通过只查看未读聊天或需要你关注的聊天,随时掌握最新进展。别担心,你可以随时在设置中更改此项。`, }, inboxTabs: {all: '全部', todo: '待办事项', unread: '未读'}, reportDetailsPage: { From 6f9c5b36be017c16aa6aa1ea10a6c491cac66293 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 9 Jun 2026 01:07:04 +0000 Subject: [PATCH 24/28] Allow parent chat to show up in LHN when clicked from subtitle --- src/hooks/useSidebarOrderedReports.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index c15e52fe29ae..f3b8ec770abf 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -45,7 +45,14 @@ type SidebarOrderedReportsActionsContextValue = { setStickyReportID: (reportID: string) => void; }; -type ReportsToDisplayInLHN = Record; +type ReportsToDisplayInLHN = Record< + string, + OnyxTypes.Report & { + hasErrorsOtherThanFailedReceipt?: boolean; + requiresAttention?: boolean; + isUnreadReport?: boolean; + } +>; const SidebarOrderedReportsStateContext = createContext({ filteredReports: [], @@ -358,6 +365,17 @@ function SidebarOrderedReportsContextProvider({ [activeTab], ); + // Navigating to a report from somewhere other than the LHN (e.g. opening the parent chat from a + // report's header subtitle) should also keep that report visible in the active To-do/Unread tab, + // just like opening one from the LHN does. We only react to the focused report changing, not to a + // tab switch, so switching tabs while a report is open doesn't pin that report to the new tab. + useEffect(() => { + if (activeTab === CONST.INBOX_TAB.ALL || !derivedCurrentReportID || derivedCurrentReportID === '-1' || derivedCurrentReportID === prevDerivedCurrentReportID) { + return; + } + setStickyReport({reportID: derivedCurrentReportID, tab: activeTab}); + }, [activeTab, derivedCurrentReportID, prevDerivedCurrentReportID]); + const stateValue: SidebarOrderedReportsStateContextValue = useMemo(() => { // 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 From ec5ee165729a733ba7b2f3c94b76bd21cc963adb Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 9 Jun 2026 01:19:13 +0000 Subject: [PATCH 25/28] Revert parent chat fix --- src/hooks/useSidebarOrderedReports.tsx | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index f3b8ec770abf..c15e52fe29ae 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -45,14 +45,7 @@ type SidebarOrderedReportsActionsContextValue = { setStickyReportID: (reportID: string) => void; }; -type ReportsToDisplayInLHN = Record< - string, - OnyxTypes.Report & { - hasErrorsOtherThanFailedReceipt?: boolean; - requiresAttention?: boolean; - isUnreadReport?: boolean; - } ->; +type ReportsToDisplayInLHN = Record; const SidebarOrderedReportsStateContext = createContext({ filteredReports: [], @@ -365,17 +358,6 @@ function SidebarOrderedReportsContextProvider({ [activeTab], ); - // Navigating to a report from somewhere other than the LHN (e.g. opening the parent chat from a - // report's header subtitle) should also keep that report visible in the active To-do/Unread tab, - // just like opening one from the LHN does. We only react to the focused report changing, not to a - // tab switch, so switching tabs while a report is open doesn't pin that report to the new tab. - useEffect(() => { - if (activeTab === CONST.INBOX_TAB.ALL || !derivedCurrentReportID || derivedCurrentReportID === '-1' || derivedCurrentReportID === prevDerivedCurrentReportID) { - return; - } - setStickyReport({reportID: derivedCurrentReportID, tab: activeTab}); - }, [activeTab, derivedCurrentReportID, prevDerivedCurrentReportID]); - const stateValue: SidebarOrderedReportsStateContextValue = useMemo(() => { // 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 From 2c1905c32cb2301f858568e8f602b5efe2f6a92e Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 9 Jun 2026 01:27:32 +0000 Subject: [PATCH 26/28] Adjust spacing to match the mock --- .../LHNOptionsList/LHNEmptyState.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/LHNOptionsList/LHNEmptyState.tsx b/src/components/LHNOptionsList/LHNEmptyState.tsx index b4681315fc39..b8498ac7af75 100644 --- a/src/components/LHNOptionsList/LHNEmptyState.tsx +++ b/src/components/LHNOptionsList/LHNEmptyState.tsx @@ -3,7 +3,9 @@ import {View} from 'react-native'; import type {BlockingViewProps} from '@components/BlockingViews/BlockingView'; import BlockingView from '@components/BlockingViews/BlockingView'; import Icon from '@components/Icon'; +import Text from '@components/Text'; import TextBlock from '@components/TextBlock'; +import TextLink from '@components/TextLink'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import {useSidebarOrderedReportsActions, useSidebarOrderedReportsState} from '@hooks/useSidebarOrderedReports'; @@ -24,15 +26,24 @@ function LHNEmptyState() { if (activeTab === CONST.INBOX_TAB.UNREAD || activeTab === CONST.INBOX_TAB.TODO) { const title = activeTab === CONST.INBOX_TAB.UNREAD ? translate('common.emptyLHN.noUnreadChats') : translate('common.emptyLHN.noTodos'); + const caughtUpSubtitle = ( + + {translate('common.emptyLHN.caughtUp')} + setActiveTab(CONST.INBOX_TAB.ALL)} + style={[styles.link, styles.mt5, styles.ph4, styles.textAlignCenter]} + > + {translate('common.emptyLHN.seeAllChats')} + + + ); return ( setActiveTab(CONST.INBOX_TAB.ALL)} + titleStyles={styles.mb2} + CustomSubtitle={caughtUpSubtitle} accessibilityLabel={title} /> ); From b6515d7ba5c201902b86d9492f4ebaae5e294d5a Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 9 Jun 2026 13:42:34 +0000 Subject: [PATCH 27/28] Lint --- src/components/LHNOptionsList/LHNEmptyState.tsx | 6 +++--- src/pages/inbox/sidebar/InboxTabSelector.tsx | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/LHNOptionsList/LHNEmptyState.tsx b/src/components/LHNOptionsList/LHNEmptyState.tsx index b8498ac7af75..f3626566a96b 100644 --- a/src/components/LHNOptionsList/LHNEmptyState.tsx +++ b/src/components/LHNOptionsList/LHNEmptyState.tsx @@ -20,7 +20,7 @@ function LHNEmptyState() { const styles = useThemeStyles(); const {translate} = useLocalize(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass', 'Plus']); - const emptyLHNIllustration = useEmptyLHNIllustration(); + const emptyLHNIllustration = useEmptyLHNIllustration() as BlockingViewProps; const {activeTab} = useSidebarOrderedReportsState(); const {setActiveTab} = useSidebarOrderedReportsActions(); @@ -40,7 +40,7 @@ function LHNEmptyState() { return ( setActiveTab(key as ValueOf)} + onTabPress={(key) => { + if (key !== CONST.INBOX_TAB.ALL && key !== CONST.INBOX_TAB.UNREAD && key !== CONST.INBOX_TAB.TODO) { + return; + } + setActiveTab(key); + }} equalWidth contentContainerStyles={styles.pb1} /> From 4e3c0f97cdbd89f8ebe1291fdffb9a8ff6d88cec Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Tue, 9 Jun 2026 19:34:04 +0000 Subject: [PATCH 28/28] Adjust link style --- src/components/LHNOptionsList/LHNEmptyState.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNEmptyState.tsx b/src/components/LHNOptionsList/LHNEmptyState.tsx index f3626566a96b..cb0f4552dbca 100644 --- a/src/components/LHNOptionsList/LHNEmptyState.tsx +++ b/src/components/LHNOptionsList/LHNEmptyState.tsx @@ -31,7 +31,7 @@ function LHNEmptyState() { {translate('common.emptyLHN.caughtUp')} setActiveTab(CONST.INBOX_TAB.ALL)} - style={[styles.link, styles.mt5, styles.ph4, styles.textAlignCenter]} + style={[styles.textStrong, styles.mt5, styles.ph4, styles.textAlignCenter]} > {translate('common.emptyLHN.seeAllChats')}