From ce7ad35e10a8544c2655cb215d6a27d82355ec92 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 13 Dec 2023 17:11:19 +0100 Subject: [PATCH 01/29] Create WorkspaceSelectorPage --- src/pages/WorkspaceSelectorPage.js | 222 +++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 src/pages/WorkspaceSelectorPage.js diff --git a/src/pages/WorkspaceSelectorPage.js b/src/pages/WorkspaceSelectorPage.js new file mode 100644 index 000000000000..6f6bc5519414 --- /dev/null +++ b/src/pages/WorkspaceSelectorPage.js @@ -0,0 +1,222 @@ +import PropTypes from 'prop-types'; +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import HeaderPageLayout from '@components/HeaderPageLayout'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import colors from '@styles/colors'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; +import * as Policy from '@userActions/Policy'; +import Icon from '@src/components/Icon'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; + +const propTypes = { + /** The list of this user's policies */ + policies: PropTypes.objectOf( + PropTypes.shape({ + /** The ID of the policy */ + id: PropTypes.string, + + /** The name of the policy */ + name: PropTypes.string, + + /** The type of the policy */ + type: PropTypes.string, + + /** The user's role in the policy */ + role: PropTypes.string, + + /** The current action that is waiting to happen on the policy */ + pendingAction: PropTypes.oneOf(_.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), + }), + ), + activeWorkspaceID: PropTypes.arrayOf([PropTypes.undefined, PropTypes.string]), +}; + +const defaultProps = { + policies: {}, + activeWorkspaceID: undefined, +}; + +const INDICATOR_STYLES = { + checkmark: { + icon: Expensicons.Checkmark, + fill: colors.green, + }, + greenIndicator: { + icon: Expensicons.DotIndicator, + fill: colors.green, + }, + redIndicator: { + icon: Expensicons.DotIndicator, + fill: colors.red, + }, +}; + +function WorkspacesSelectorPage({policies, activeWorkspaceID}) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + + const getIndicatorTypeForPolicy = useCallback( + // TO DO: Wait for missing logic to be implemented in other PR + (policyId) => { + if (policyId === activeWorkspaceID) { + return 'checkmark'; + } + + return undefined; + }, + [activeWorkspaceID], + ); + + const workspaceStatusComponent = useCallback( + (iconType) => { + const indicatorStyle = INDICATOR_STYLES[iconType]; + return ( + + + + ); + }, + [styles.alignItemsCenter, styles.justifyContentCenter], + ); + + const getMenuItem = useCallback( + (item) => { + const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; + + const shouldShowRightComponent = _.keys(INDICATOR_STYLES).includes(item.indicatorType); + const rightComponent = shouldShowRightComponent ? workspaceStatusComponent(item.indicatorType) : undefined; + + return ( + + ); + }, + [translate, workspaceStatusComponent], + ); + + const usersWorkspaces = useMemo( + () => + _.chain(policies) + .filter((policy) => PolicyUtils.shouldShowPolicy(policy, isOffline)) + .map((policy) => ({ + title: policy.name, + icon: policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name), + iconType: CONST.ICON_TYPE_WORKSPACE, + action: () => { + Policy.selectWorkspace(policy.id); + }, + fallbackIcon: Expensicons.FallbackWorkspaceAvatar, + pendingAction: policy.pendingAction, + errors: policy.errors, + disabled: policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + indicatorType: getIndicatorTypeForPolicy(policy.id), + })) + .sortBy((policy) => policy.title.toLowerCase()) + .value(), + [policies, isOffline, getIndicatorTypeForPolicy], + ); + + const allWorkspaces = useMemo( + () => [ + { + title: 'Expensify', + icon: Expensicons.ExpensifyAppIcon, + iconType: CONST.ICON_TYPE_AVATAR, + action: () => { + Policy.selectWorkspace(undefined); + }, + indicatorType: getIndicatorTypeForPolicy(undefined), + }, + ], + [getIndicatorTypeForPolicy], + ); + + const getWorkspacesSection = useCallback( + (workspaces, section, renderSearchBar, showAddWorkspaceButton) => { + const xd = renderSearchBar; + + return ( + + + + {section} + + {showAddWorkspaceButton && + {({hovered}) => ( + + )} + } + + + {_.map(workspaces, (item, index) => getMenuItem(item, index))} + + + ); + }, + [getMenuItem, styles.alignItemsCenter, styles.borderRadiusNormal, styles.bordersBG, styles.flexRow, styles.highlightBG, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, styles.p2, theme.textSupporting], + ); + + const allWorkspacesSection = useMemo(() => getWorkspacesSection(allWorkspaces, 'Everything', false, false), [allWorkspaces, getWorkspacesSection]); + const usersWorkspacesSection = useMemo(() => getWorkspacesSection(usersWorkspaces, 'Workspaces', true, true), [getWorkspacesSection, usersWorkspaces]); + + return ( + + {allWorkspacesSection} + {usersWorkspacesSection} + + ); +} + +WorkspacesSelectorPage.propTypes = propTypes; +WorkspacesSelectorPage.defaultProps = defaultProps; +WorkspacesSelectorPage.displayName = 'WorkspacesSelectorPage'; + +export default withOnyx({ + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + activeWorkspaceID: { + key: ONYXKEYS.ACTIVE_WORKSPACE_ID, + }, +})(WorkspacesSelectorPage); From 8bff47a72621c4ac502c8b9f19e113a10d9587df Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 14 Dec 2023 10:24:57 +0100 Subject: [PATCH 02/29] Update design and implement page in RHP temporarily --- src/ONYXKEYS.ts | 4 ++++ src/ROUTES.ts | 2 +- src/SCREENS.ts | 4 ++++ src/components/EnvironmentBadge.tsx | 6 ++++++ src/components/Icon/Expensicons.ts | 1 + .../Navigation/AppNavigator/ModalStackNavigators.tsx | 5 +++++ .../AppNavigator/Navigators/RightModalNavigator.tsx | 4 ++++ src/libs/Navigation/linkingConfig.ts | 7 +++++++ src/libs/Navigation/types.ts | 6 ++++++ src/libs/actions/Policy.js | 9 +++++++++ ...spaceSelectorPage.js => WorkspaceSwitcherPage.js} | 12 ++++++------ src/styles/theme/themes/dark.ts | 4 ++++ src/styles/theme/themes/light.ts | 4 ++++ 13 files changed, 61 insertions(+), 7 deletions(-) rename src/pages/{WorkspaceSelectorPage.js => WorkspaceSwitcherPage.js} (96%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4282cd8b842..f10aaf0f7c52 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -21,6 +21,9 @@ const ONYXKEYS = { * which tab is the leader, and which ones are the followers */ ACTIVE_CLIENTS: 'activeClients', + /** Holds active policyID (if any is active) */ + ACTIVE_WORKSPACE_ID: 'activeWorkspaceID', + /** A unique ID for the device */ DEVICE_ID: 'deviceID', @@ -363,6 +366,7 @@ type OnyxValues = { [ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string; [ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER]: boolean; [ONYXKEYS.ACTIVE_CLIENTS]: string[]; + [ONYXKEYS.ACTIVE_WORKSPACE_ID]: string | undefined; [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 873f6d0c0f16..e74530850efd 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -57,7 +57,7 @@ const ROUTES = { route: 'bank-account/:stepToOpen?', getRoute: (stepToOpen = '', policyID = '', backTo?: string) => getUrlWithBackToParam(`bank-account/${stepToOpen}?policyID=${policyID}`, backTo), }, - + WORKSPACE_SWITCHER: 'workspaceSwitcher', SETTINGS: 'settings', SETTINGS_PROFILE: 'settings/profile', SETTINGS_SHARE_CODE: 'settings/shareCode', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 7d0b66d441fc..067d3728663f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -86,6 +86,9 @@ const SCREENS = { LEFT_MODAL: { SEARCH: 'Search', }, + WORKSPACE_SWITCHER: { + ROOT: 'WorkspaceSwitcher_Root', + }, RIGHT_MODAL: { SETTINGS: 'Settings', NEW_CHAT: 'NewChat', @@ -110,6 +113,7 @@ const SCREENS = { ROOM_MEMBERS: 'RoomMembers', ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', + WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', diff --git a/src/components/EnvironmentBadge.tsx b/src/components/EnvironmentBadge.tsx index 3a8445f62880..d2d4b0ccfa0f 100644 --- a/src/components/EnvironmentBadge.tsx +++ b/src/components/EnvironmentBadge.tsx @@ -2,7 +2,9 @@ import React from 'react'; import useEnvironment from '@hooks/useEnvironment'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Environment from '@libs/Environment/Environment'; +import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import pkg from '../../package.json'; import Badge from './Badge'; @@ -32,6 +34,10 @@ function EnvironmentBadge() { badgeStyles={[styles.alignSelfStart, styles.headerEnvBadge]} textStyles={[styles.headerEnvBadgeText]} environment={environment} + pressable + onPress={() => { + Navigation.navigate(ROUTES.WORKSPACE_SWITCHER); + }} /> ); } diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 164f0b6d2454..9c2d57416db8 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -220,6 +220,7 @@ export { Location, Lock, LoungeAccess, + ExpensifyAppIcon, Luggage, MagnifyingGlass, Mail, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 90bb88fc100a..028451010552 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -28,6 +28,7 @@ import type { TaskDetailsNavigatorParamList, TeachersUniteNavigatorParamList, WalletStatementNavigatorParamList, + WorkspaceSwitcherNavigatorParamList, } from '@navigation/types'; import {ThemeStyles} from '@styles/index'; import SCREENS from '@src/SCREENS'; @@ -191,6 +192,9 @@ const AccountSettingsModalStackNavigator = createModalStackNavigator( }, (styles) => ({cardStyle: styles.navigationScreenCardStyle, headerShown: false}), ); +const WorkspaceSwitcherModalStackNavigator = createModalStackNavigator({ + [SCREENS.WORKSPACE_SWITCHER.ROOT]: () => require('../../../pages/WorkspaceSwitcherPage').default as React.ComponentType, +}); const SettingsModalStackNavigator = createModalStackNavigator({ [SCREENS.SETTINGS.SHARE_CODE]: () => require('../../../pages/ShareCodePage').default as React.ComponentType, @@ -315,4 +319,5 @@ export { RoomMembersModalStackNavigator, RoomInviteModalStackNavigator, ReferralModalStackNavigator, + WorkspaceSwitcherModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index d7c31bcae7d9..7a58a4d29c22 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -29,6 +29,10 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.SETTINGS} component={ModalStackNavigators.SettingsModalStackNavigator} /> + = { [SCREENS.WORKSPACE.NAME]: ROUTES.WORKSPACE_NAME.route, }, }, + [SCREENS.RIGHT_MODAL.WORKSPACE_SWITCHER]: { + screens: { + [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + path: ROUTES.WORKSPACE_SWITCHER, + }, + }, + }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { screens: { [SCREENS.PRIVATE_NOTES.VIEW]: ROUTES.PRIVATE_NOTES_VIEW.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index de360340a64f..ef1f538fb9e5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -79,6 +79,10 @@ type CentralPaneNavigatorParamList = { }; }; +type WorkspaceSwitcherNavigatorParamList = { + [SCREENS.WORKSPACE_SWITCHER.ROOT]: undefined; +}; + type SettingsNavigatorParamList = { [SCREENS.SETTINGS.ROOT]: undefined; [SCREENS.SETTINGS.SHARE_CODE]: undefined; @@ -377,6 +381,7 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.SIGN_IN]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.REFERRAL]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.WORKSPACE_SWITCHER]: NavigatorScreenParams; }; type SettingsCentralPaneNavigatorParamList = { @@ -504,4 +509,5 @@ export type { ReferralDetailsNavigatorParamList, ReimbursementAccountNavigatorParamList, State, + WorkspaceSwitcherNavigatorParamList, }; diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index f33e6637e2de..3bb9ee10d58e 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -1510,6 +1510,14 @@ function buildOptimisticPolicyRecentlyUsedTags(policyID, tag) { }; } +/** + * This function saves the selected workspace to onyx to filter data depending on this ID + * @param {String | undefined} policyID + */ +function selectWorkspace(policyID) { + Onyx.set(ONYXKEYS.ACTIVE_WORKSPACE_ID, policyID); +} + /** * This flow is used for bottom up flow converting IOU report to an expense report. When user takes this action, * we create a Collect type workspace when the person taking the action becomes an owner and an admin, while we @@ -1942,4 +1950,5 @@ export { buildOptimisticPolicyRecentlyUsedTags, createDraftInitialWorkspace, setWorkspaceInviteMessageDraft, + selectWorkspace, }; diff --git a/src/pages/WorkspaceSelectorPage.js b/src/pages/WorkspaceSwitcherPage.js similarity index 96% rename from src/pages/WorkspaceSelectorPage.js rename to src/pages/WorkspaceSwitcherPage.js index 6f6bc5519414..ed1e45b9b900 100644 --- a/src/pages/WorkspaceSelectorPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -64,7 +64,7 @@ const INDICATOR_STYLES = { }, }; -function WorkspacesSelectorPage({policies, activeWorkspaceID}) { +function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -185,7 +185,7 @@ function WorkspacesSelectorPage({policies, activeWorkspaceID}) { )} } - + {_.map(workspaces, (item, index) => getMenuItem(item, index))} @@ -208,9 +208,9 @@ function WorkspacesSelectorPage({policies, activeWorkspaceID}) { ); } -WorkspacesSelectorPage.propTypes = propTypes; -WorkspacesSelectorPage.defaultProps = defaultProps; -WorkspacesSelectorPage.displayName = 'WorkspacesSelectorPage'; +WorkspaceSwitcherPage.propTypes = propTypes; +WorkspaceSwitcherPage.defaultProps = defaultProps; +WorkspaceSwitcherPage.displayName = 'WorkspaceSwitcherPage'; export default withOnyx({ policies: { @@ -219,4 +219,4 @@ export default withOnyx({ activeWorkspaceID: { key: ONYXKEYS.ACTIVE_WORKSPACE_ID, }, -})(WorkspacesSelectorPage); +})(WorkspaceSwitcherPage); diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts index 7a94988eb019..a48ac85e0b10 100644 --- a/src/styles/theme/themes/dark.ts +++ b/src/styles/theme/themes/dark.ts @@ -127,6 +127,10 @@ const darkTheme = { backgroundColor: colors.productDark100, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, }, + [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + backgroundColor: colors.productDark100, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, [SCREENS.RIGHT_MODAL.REFERRAL]: { backgroundColor: colors.pink800, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts index 228917d73d26..3189ce591da6 100644 --- a/src/styles/theme/themes/light.ts +++ b/src/styles/theme/themes/light.ts @@ -127,6 +127,10 @@ const lightTheme = { backgroundColor: colors.productLight100, statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, }, + [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + backgroundColor: colors.productLight100, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + }, [SCREENS.RIGHT_MODAL.REFERRAL]: { backgroundColor: colors.pink800, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, From 01c740c3461230ed3e6010040ac0ff6f998d9189 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 14 Dec 2023 15:22:41 +0100 Subject: [PATCH 03/29] Adapt OptionRow for Workspace Switcher --- src/components/MultipleAvatars.tsx | 2 +- src/components/OptionRow.js | 8 ++ .../OptionsSelector/BaseOptionsSelector.js | 3 +- .../TextInput/BaseTextInput/index.js | 10 ++ src/pages/WorkspaceSwitcherPage.js | 136 +++++++++--------- src/styles/index.ts | 5 + src/types/onyx/OnyxCommon.ts | 3 + 7 files changed, 101 insertions(+), 66 deletions(-) diff --git a/src/components/MultipleAvatars.tsx b/src/components/MultipleAvatars.tsx index 997e63cb022f..de0aaaf30578 100644 --- a/src/components/MultipleAvatars.tsx +++ b/src/components/MultipleAvatars.tsx @@ -152,7 +152,7 @@ function MultipleAvatars({ )} + {props.option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO && ( + + + + )} {props.showSelectedState && ( <> {props.shouldShowSelectedStateAsButton && !props.isSelected ? ( diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 79482c11d6b0..3361718db31e 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -8,7 +8,7 @@ import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import FormHelpMessage from '@components/FormHelpMessage'; import Icon from '@components/Icon'; -import {Info} from '@components/Icon/Expensicons'; +import {Info, MagnifyingGlass} from '@components/Icon/Expensicons'; import OptionsList from '@components/OptionsList'; import {PressableWithoutFeedback} from '@components/Pressable'; import ShowMoreButton from '@components/ShowMoreButton'; @@ -491,6 +491,7 @@ class BaseOptionsSelector extends Component { spellCheck={false} shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} isLoading={this.props.isLoadingNewOptions} + iconLeft={MagnifyingGlass} /> ); const optionsList = ( diff --git a/src/components/TextInput/BaseTextInput/index.js b/src/components/TextInput/BaseTextInput/index.js index 67776d6c7e91..fe544c03e1b1 100644 --- a/src/components/TextInput/BaseTextInput/index.js +++ b/src/components/TextInput/BaseTextInput/index.js @@ -291,6 +291,16 @@ function BaseTextInput(props) { ) : null} + {!props.secureTextEntry && Boolean(props.iconLeft) && ( + + + + )} {Boolean(props.prefixCharacter) && ( { - if (policyId === activeWorkspaceID) { - return 'checkmark'; - } - - return undefined; - }, - [activeWorkspaceID], - ); - - const workspaceStatusComponent = useCallback( - (iconType) => { - const indicatorStyle = INDICATOR_STYLES[iconType]; - return ( - - - - ); - }, - [styles.alignItemsCenter, styles.justifyContentCenter], + // CONST.BRICK_ROAD_INDICATOR_STATUS.INFO or CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR + (policyId) => undefined, + [], ); const getMenuItem = useCallback( (item) => { const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; - const shouldShowRightComponent = _.keys(INDICATOR_STYLES).includes(item.indicatorType); - const rightComponent = shouldShowRightComponent ? workspaceStatusComponent(item.indicatorType) : undefined; + + const option = { + text: keyTitle, + brickRoadIndicator: getIndicatorTypeForPolicy(item.policyId), + icons: [{ + source: item.icon, + type: item.iconType, + fill: item.iconFill, + name: keyTitle, + fallbackIcon: item.fallbackIcon, + }], + } + + // return ( + // + // ); return ( - ); + }, - [translate, workspaceStatusComponent], + [activeWorkspaceID, getIndicatorTypeForPolicy, translate], ); const usersWorkspaces = useMemo( @@ -130,6 +115,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { .filter((policy) => PolicyUtils.shouldShowPolicy(policy, isOffline)) .map((policy) => ({ title: policy.name, + policyId: policy.id, icon: policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name), iconType: CONST.ICON_TYPE_WORKSPACE, action: () => { @@ -139,11 +125,20 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { pendingAction: policy.pendingAction, errors: policy.errors, disabled: policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - indicatorType: getIndicatorTypeForPolicy(policy.id), })) .sortBy((policy) => policy.title.toLowerCase()) .value(), - [policies, isOffline, getIndicatorTypeForPolicy], + [policies, isOffline], + ); + + const usersWorkspacesSectionData = useMemo( + () => + [{ + data: usersWorkspaces, + shouldShow: true, + indexOffset: 0, + }], + [usersWorkspaces], ); const allWorkspaces = useMemo( @@ -162,10 +157,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { ); const getWorkspacesSection = useCallback( - (workspaces, section, renderSearchBar, showAddWorkspaceButton) => { - const xd = renderSearchBar; - - return ( + (workspaces, section, showAddWorkspaceButton) => ( getMenuItem(item, index))} - ); - }, + ), [getMenuItem, styles.alignItemsCenter, styles.borderRadiusNormal, styles.bordersBG, styles.flexRow, styles.highlightBG, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, styles.p2, theme.textSupporting], ); const allWorkspacesSection = useMemo(() => getWorkspacesSection(allWorkspaces, 'Everything', false, false), [allWorkspaces, getWorkspacesSection]); const usersWorkspacesSection = useMemo(() => getWorkspacesSection(usersWorkspaces, 'Workspaces', true, true), [getWorkspacesSection, usersWorkspaces]); + // const {inputCallbackRef} = useAutoFocusInput(); + + return ( {allWorkspacesSection} {usersWorkspacesSection} + {/* {}} + sections={usersWorkspacesSectionData} + selectedOptions={[]} + value="Find" + onSelectRow={(option) => {}} + onChangeText + headerMessage + boldStyle + shouldPreventDefaultFocusOnSelectRow + shouldShowOptions + autoFocus={false} + /> */} ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 5c7743ecb01b..3a56fbed8a48 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1036,6 +1036,11 @@ const styles = (theme: ThemeColors) => margin: 1, }, + textInputLeftIconContainer: { + justifyContent: "center", + paddingRight: 8, + }, + secureInput: { borderTopRightRadius: 0, borderBottomRightRadius: 0, diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 956e9ff36b24..b55820f078ea 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -27,6 +27,9 @@ type Icon = { /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ fallbackIcon?: AvatarSource; + + /** Fill color of the icon */ + fill?: string; }; export type {Icon, PendingAction, PendingFields, ErrorFields, Errors, AvatarType}; From 2b0cb18f2e806b3055d297204c1eedebcdb57a8e Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 14 Dec 2023 15:24:14 +0100 Subject: [PATCH 04/29] Comment unused variable --- src/pages/WorkspaceSwitcherPage.js | 94 +++++++++++++++++------------- src/styles/index.ts | 2 +- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index 9b54ecf2992a..ffb955b14e20 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -5,7 +5,12 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import HeaderPageLayout from '@components/HeaderPageLayout'; import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import OptionRow from '@components/OptionRow'; +import OptionsSelector from '@components/OptionsSelector'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -17,8 +22,6 @@ import Icon from '@src/components/Icon'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import OptionRow from '@components/OptionRow'; const propTypes = { /** The list of this user's policies */ @@ -65,18 +68,19 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { (item) => { const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; - const option = { text: keyTitle, brickRoadIndicator: getIndicatorTypeForPolicy(item.policyId), - icons: [{ - source: item.icon, - type: item.iconType, - fill: item.iconFill, - name: keyTitle, - fallbackIcon: item.fallbackIcon, - }], - } + icons: [ + { + source: item.icon, + type: item.iconType, + fill: item.iconFill, + name: keyTitle, + fallbackIcon: item.fallbackIcon, + }, + ], + }; // return ( // ); - }, [activeWorkspaceID, getIndicatorTypeForPolicy, translate], ); @@ -132,12 +135,13 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { ); const usersWorkspacesSectionData = useMemo( - () => - [{ + () => [ + { data: usersWorkspaces, shouldShow: true, indexOffset: 0, - }], + }, + ], [usersWorkspaces], ); @@ -158,31 +162,44 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const getWorkspacesSection = useCallback( (workspaces, section, showAddWorkspaceButton) => ( - - - - {section} - - {showAddWorkspaceButton && - {({hovered}) => ( - + + + + {section} + + {showAddWorkspaceButton && ( + + {({hovered}) => ( + + )} + )} - } - - - {_.map(workspaces, (item, index) => getMenuItem(item, index))} - - ), - [getMenuItem, styles.alignItemsCenter, styles.borderRadiusNormal, styles.bordersBG, styles.flexRow, styles.highlightBG, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, styles.p2, theme.textSupporting], + {_.map(workspaces, (item, index) => getMenuItem(item, index))} + + ), + [ + getMenuItem, + styles.alignItemsCenter, + styles.borderRadiusNormal, + styles.bordersBG, + styles.flexRow, + styles.highlightBG, + styles.justifyContentBetween, + styles.label, + styles.mb3, + styles.mh4, + styles.p2, + theme.textSupporting, + ], ); const allWorkspacesSection = useMemo(() => getWorkspacesSection(allWorkspaces, 'Everything', false, false), [allWorkspaces, getWorkspacesSection]); @@ -190,7 +207,6 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { // const {inputCallbackRef} = useAutoFocusInput(); - return ( }, textInputLeftIconContainer: { - justifyContent: "center", + justifyContent: 'center', paddingRight: 8, }, From f5a0d7b04aa56d3ac7541f277258016e03d7c4d9 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 20 Dec 2023 12:33:52 +0100 Subject: [PATCH 05/29] Integrate search bar --- src/components/OptionsList/BaseOptionsList.js | 8 +- .../OptionsSelector/BaseOptionsSelector.js | 1 + .../optionsSelectorPropTypes.js | 4 + src/pages/WorkspaceSwitcherPage.js | 248 ++++++++---------- 4 files changed, 125 insertions(+), 136 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index d22df00bd0b3..9aa9e7d796b6 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -192,6 +192,10 @@ function BaseOptionsList({ return true; } + if (option.policyID && option.policyID === item.policyID) { + return true; + } + if (_.isEmpty(option.name)) { return false; } @@ -201,7 +205,7 @@ function BaseOptionsList({ return ( 0 && shouldHaveOptionSeparator} shouldDisableRowInnerPadding={shouldDisableRowInnerPadding} diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 3361718db31e..808031b320c2 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -501,6 +501,7 @@ class BaseOptionsSelector extends Component { onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} sections={this.state.sections} focusedIndex={this.state.focusedIndex} + disableFocusOptions={this.props.disableFocusOptions} selectedOptions={this.props.selectedOptions} canSelectMultipleOptions={this.props.canSelectMultipleOptions} shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} diff --git a/src/components/OptionsSelector/optionsSelectorPropTypes.js b/src/components/OptionsSelector/optionsSelectorPropTypes.js index e52187fa76d7..847409c70269 100644 --- a/src/components/OptionsSelector/optionsSelectorPropTypes.js +++ b/src/components/OptionsSelector/optionsSelectorPropTypes.js @@ -72,6 +72,9 @@ const propTypes = { /** Whether to disable interactivity of option rows */ isDisabled: PropTypes.bool, + /** Whether to disable focus options of rows */ + disableFocusOptions: PropTypes.bool, + /** Display the text of the option in bold font style */ boldStyle: PropTypes.bool, @@ -163,6 +166,7 @@ const defaultProps = { shouldShowOptions: true, disableArrowKeysActions: false, isDisabled: false, + disableFocusOptions: false, shouldHaveOptionSeparator: false, initiallyFocusedOptionKey: undefined, maxLength: CONST.SEARCH_MAX_LENGTH, diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index ffb955b14e20..dc4c2b682319 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -1,24 +1,20 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import HeaderPageLayout from '@components/HeaderPageLayout'; import * as Expensicons from '@components/Icon/Expensicons'; -import MenuItem from '@components/MenuItem'; import OptionRow from '@components/OptionRow'; import OptionsSelector from '@components/OptionsSelector'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; -import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import * as Policy from '@userActions/Policy'; -import Icon from '@src/components/Icon'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; @@ -54,180 +50,164 @@ const defaultProps = { function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const theme = useTheme(); const styles = useThemeStyles(); - const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const [selectedOption, setSelectedOption] = useState(); + const [searchTerm, setSearchTerm] = useState(''); const getIndicatorTypeForPolicy = useCallback( // TO DO: Wait for missing logic to be implemented in other PR // CONST.BRICK_ROAD_INDICATOR_STATUS.INFO or CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR + // eslint-disable-next-line no-unused-vars (policyId) => undefined, [], ); - const getMenuItem = useCallback( - (item) => { - const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; - - const option = { - text: keyTitle, - brickRoadIndicator: getIndicatorTypeForPolicy(item.policyId), - icons: [ - { - source: item.icon, - type: item.iconType, - fill: item.iconFill, - name: keyTitle, - fallbackIcon: item.fallbackIcon, - }, - ], - }; - - // return ( - // - // ); - - return ( - - ); - }, - [activeWorkspaceID, getIndicatorTypeForPolicy, translate], + const hasUnreadData = useCallback( + // TO DO: Implement checking if policy has some unread data + // eslint-disable-next-line no-unused-vars + (policyId) => false, + [] ); + const selectPolicy = useCallback((option) => { + const policyID = option.policyID; + Policy.selectWorkspace(policyID); + + if (policyID) { + setSelectedOption(option); + } else { + setSelectedOption(undefined); + } + }, []); + + const onChangeText = useCallback((newSearchTerm) => { + // TO DO: Handle searching logic + setSearchTerm(newSearchTerm); + }, []) + const usersWorkspaces = useMemo( () => _.chain(policies) .filter((policy) => PolicyUtils.shouldShowPolicy(policy, isOffline)) .map((policy) => ({ - title: policy.name, - policyId: policy.id, - icon: policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name), - iconType: CONST.ICON_TYPE_WORKSPACE, - action: () => { - Policy.selectWorkspace(policy.id); - }, - fallbackIcon: Expensicons.FallbackWorkspaceAvatar, - pendingAction: policy.pendingAction, - errors: policy.errors, - disabled: policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + text: policy.name, + policyID: policy.id, + brickRoadIndicator: getIndicatorTypeForPolicy(policy.id), + icons: [ + { + source: policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policy.name), + fallbackIcon: Expensicons.FallbackWorkspaceAvatar, + name: policy.name, + type: CONST.ICON_TYPE_WORKSPACE, + }, + ], + boldStyle: hasUnreadData(policy.id), })) - .sortBy((policy) => policy.title.toLowerCase()) + .sortBy((policy) => policy.text.toLowerCase()) .value(), - [policies, isOffline], + [policies, isOffline, getIndicatorTypeForPolicy, hasUnreadData], ); const usersWorkspacesSectionData = useMemo( - () => [ - { + () => ({ data: usersWorkspaces, shouldShow: true, indexOffset: 0, - }, - ], + }), [usersWorkspaces], ); - const allWorkspaces = useMemo( - () => [ - { - title: 'Expensify', - icon: Expensicons.ExpensifyAppIcon, - iconType: CONST.ICON_TYPE_AVATAR, - action: () => { - Policy.selectWorkspace(undefined); + const everythingSection = useMemo(() => { + const option = { + text: 'Expensify', + icons: [ + { + source: Expensicons.ExpensifyAppIcon, + name: 'Expensify', + type: CONST.ICON_TYPE_AVATAR, }, - indicatorType: getIndicatorTypeForPolicy(undefined), - }, - ], - [getIndicatorTypeForPolicy], - ); + ], + brickRoadIndicator: getIndicatorTypeForPolicy(undefined), + boldStyle: hasUnreadData(undefined), + }; - const getWorkspacesSection = useCallback( - (workspaces, section, showAddWorkspaceButton) => ( - + return ( + <> - {section} + Everything - {showAddWorkspaceButton && ( - - {({hovered}) => ( - - )} - - )} - {_.map(workspaces, (item, index) => getMenuItem(item, index))} - + + + + + ); + }, [activeWorkspaceID, getIndicatorTypeForPolicy, hasUnreadData, selectPolicy, styles.alignItemsCenter, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, theme.textSupporting]); + + const inputCallbackRef = useAutoFocusInput(); + const workspacesSection = useMemo( + () => ( + <> + + + Workspaces + + + {/* TO DO: Display breadcrumb */} + {usersWorkspacesSectionData.data.length === 0 && } + 8} + onChangeText={onChangeText} + selectedOptions={selectedOption ? [selectedOption] : []} + onSelectRow={selectPolicy} + boldStyle + shouldPreventDefaultFocusOnSelectRow + highlightSelectedOptions + shouldShowOptions + autoFocus={false} + disableFocusOptions + canSelectMultipleOptions={false} + shouldShowSubscript={false} + showTitleTooltip={false} + contentContainerStyles={[styles.pt0, styles.mt0]} + /> + ), - [ - getMenuItem, - styles.alignItemsCenter, - styles.borderRadiusNormal, - styles.bordersBG, - styles.flexRow, - styles.highlightBG, - styles.justifyContentBetween, - styles.label, - styles.mb3, - styles.mh4, - styles.p2, - theme.textSupporting, - ], + [inputCallbackRef, onChangeText, searchTerm, selectPolicy, selectedOption, styles.alignItemsCenter, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb1, styles.mh4, styles.mt0, styles.mt3, styles.pt0, theme.textSupporting, usersWorkspacesSectionData], ); - const allWorkspacesSection = useMemo(() => getWorkspacesSection(allWorkspaces, 'Everything', false, false), [allWorkspaces, getWorkspacesSection]); - const usersWorkspacesSection = useMemo(() => getWorkspacesSection(usersWorkspaces, 'Workspaces', true, true), [getWorkspacesSection, usersWorkspaces]); - - // const {inputCallbackRef} = useAutoFocusInput(); + useEffect(() => { + if (!activeWorkspaceID) { + return; + } + const optionToSet = _.find(usersWorkspaces, (option) => option.policyID === activeWorkspaceID); + setSelectedOption(optionToSet); + }, [activeWorkspaceID, usersWorkspaces]); return ( - {allWorkspacesSection} - {usersWorkspacesSection} - {/* {}} - sections={usersWorkspacesSectionData} - selectedOptions={[]} - value="Find" - onSelectRow={(option) => {}} - onChangeText - headerMessage - boldStyle - shouldPreventDefaultFocusOnSelectRow - shouldShowOptions - autoFocus={false} - /> */} + {everythingSection} + {workspacesSection} ); } From daa8597ec9a7ba2f943da52cbc2fb08f1f308411 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 21 Dec 2023 22:05:10 +0100 Subject: [PATCH 06/29] Fix typos --- src/components/Icon/Expensicons.ts | 1 - src/pages/WorkspaceSwitcherPage.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 9c2d57416db8..164f0b6d2454 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -220,7 +220,6 @@ export { Location, Lock, LoungeAccess, - ExpensifyAppIcon, Luggage, MagnifyingGlass, Mail, diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index dc4c2b682319..3fd52455eb69 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -12,8 +12,8 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useNetwork from '@hooks/useNetwork'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -204,7 +204,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { return ( {everythingSection} {workspacesSection} From 476eeda40cb165d6fa6b7f38dfaebbf9255442b1 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 21 Dec 2023 22:18:14 +0100 Subject: [PATCH 07/29] Move switcher to LHP --- src/SCREENS.ts | 2 +- src/components/EnvironmentBadge.tsx | 5 - .../Navigators/LeftModalNavigator.tsx | 4 + .../Navigators/RightModalNavigator.tsx | 4 - .../createCustomBottomTabNavigator/TopBar.js | 7 +- src/libs/Navigation/linkingConfig.ts | 16 ++-- src/libs/Navigation/types.ts | 2 +- src/pages/WorkspaceSwitcherPage.js | 95 ++++++++++++------- 8 files changed, 82 insertions(+), 53 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 067d3728663f..e3b5aae0d752 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -85,6 +85,7 @@ const SCREENS = { }, LEFT_MODAL: { SEARCH: 'Search', + WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, WORKSPACE_SWITCHER: { ROOT: 'WorkspaceSwitcher_Root', @@ -113,7 +114,6 @@ const SCREENS = { ROOM_MEMBERS: 'RoomMembers', ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', - WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', diff --git a/src/components/EnvironmentBadge.tsx b/src/components/EnvironmentBadge.tsx index d2d4b0ccfa0f..782144414688 100644 --- a/src/components/EnvironmentBadge.tsx +++ b/src/components/EnvironmentBadge.tsx @@ -2,9 +2,7 @@ import React from 'react'; import useEnvironment from '@hooks/useEnvironment'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Environment from '@libs/Environment/Environment'; -import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import pkg from '../../package.json'; import Badge from './Badge'; @@ -35,9 +33,6 @@ function EnvironmentBadge() { textStyles={[styles.headerEnvBadgeText]} environment={environment} pressable - onPress={() => { - Navigation.navigate(ROUTES.WORKSPACE_SWITCHER); - }} /> ); } diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index b7385c930e2c..11fef951a28b 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -34,6 +34,10 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { name={SCREENS.LEFT_MODAL.SEARCH} component={ModalStackNavigators.SearchModalStackNavigator} /> + diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 7a58a4d29c22..d7c31bcae7d9 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -29,10 +29,6 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.SETTINGS} component={ModalStackNavigators.SettingsModalStackNavigator} /> - - + { + Navigation.navigate(ROUTES.WORKSPACE_SWITCHER); + }} + > = { [SCREENS.SEARCH_ROOT]: ROUTES.SEARCH, }, }, + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { + screens: { + [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + path: ROUTES.WORKSPACE_SWITCHER, + }, + }, + }, }, }, [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: { @@ -256,14 +263,7 @@ const linkingConfig: LinkingOptions = { }, [SCREENS.WORKSPACE.NAME]: ROUTES.WORKSPACE_NAME.route, }, - }, - [SCREENS.RIGHT_MODAL.WORKSPACE_SWITCHER]: { - screens: { - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { - path: ROUTES.WORKSPACE_SWITCHER, - }, - }, - }, + }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { screens: { [SCREENS.PRIVATE_NOTES.VIEW]: ROUTES.PRIVATE_NOTES_VIEW.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ef1f538fb9e5..c7fd33f904f7 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -355,6 +355,7 @@ type PrivateNotesNavigatorParamList = { type LeftModalNavigatorParamList = { [SCREENS.LEFT_MODAL.SEARCH]: NavigatorScreenParams; + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: NavigatorScreenParams; }; type RightModalNavigatorParamList = { @@ -381,7 +382,6 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.SIGN_IN]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.REFERRAL]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: NavigatorScreenParams; - [SCREENS.RIGHT_MODAL.WORKSPACE_SWITCHER]: NavigatorScreenParams; }; type SettingsCentralPaneNavigatorParamList = { diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index 3fd52455eb69..28386617eca5 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -10,10 +10,10 @@ import OptionsSelector from '@components/OptionsSelector'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useNetwork from '@hooks/useNetwork'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -65,8 +65,8 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const hasUnreadData = useCallback( // TO DO: Implement checking if policy has some unread data // eslint-disable-next-line no-unused-vars - (policyId) => false, - [] + (policyId) => false, + [], ); const selectPolicy = useCallback((option) => { @@ -83,7 +83,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const onChangeText = useCallback((newSearchTerm) => { // TO DO: Handle searching logic setSearchTerm(newSearchTerm); - }, []) + }, []); const usersWorkspaces = useMemo( () => @@ -110,10 +110,10 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const usersWorkspacesSectionData = useMemo( () => ({ - data: usersWorkspaces, - shouldShow: true, - indexOffset: 0, - }), + data: usersWorkspaces, + shouldShow: true, + indexOffset: 0, + }), [usersWorkspaces], ); @@ -153,13 +153,25 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { ); - }, [activeWorkspaceID, getIndicatorTypeForPolicy, hasUnreadData, selectPolicy, styles.alignItemsCenter, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, theme.textSupporting]); + }, [ + activeWorkspaceID, + getIndicatorTypeForPolicy, + hasUnreadData, + selectPolicy, + styles.alignItemsCenter, + styles.flexRow, + styles.justifyContentBetween, + styles.label, + styles.mb3, + styles.mh4, + theme.textSupporting, + ]); const inputCallbackRef = useAutoFocusInput(); const workspacesSection = useMemo( () => ( <> - + - {/* TO DO: Display breadcrumb */} - {usersWorkspacesSectionData.data.length === 0 && } - 8} - onChangeText={onChangeText} - selectedOptions={selectedOption ? [selectedOption] : []} - onSelectRow={selectPolicy} - boldStyle - shouldPreventDefaultFocusOnSelectRow - highlightSelectedOptions - shouldShowOptions - autoFocus={false} - disableFocusOptions - canSelectMultipleOptions={false} - shouldShowSubscript={false} - showTitleTooltip={false} - contentContainerStyles={[styles.pt0, styles.mt0]} + {/* TO DO: Display breadcrumb */} + {usersWorkspacesSectionData.data.length === 0 && } + 8} + onChangeText={onChangeText} + selectedOptions={selectedOption ? [selectedOption] : []} + onSelectRow={selectPolicy} + boldStyle + shouldPreventDefaultFocusOnSelectRow + highlightSelectedOptions + shouldShowOptions + autoFocus={false} + disableFocusOptions + canSelectMultipleOptions={false} + shouldShowSubscript={false} + showTitleTooltip={false} + contentContainerStyles={[styles.pt0, styles.mt0]} /> - + ), - [inputCallbackRef, onChangeText, searchTerm, selectPolicy, selectedOption, styles.alignItemsCenter, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb1, styles.mh4, styles.mt0, styles.mt3, styles.pt0, theme.textSupporting, usersWorkspacesSectionData], + [ + inputCallbackRef, + onChangeText, + searchTerm, + selectPolicy, + selectedOption, + styles.alignItemsCenter, + styles.flexRow, + styles.justifyContentBetween, + styles.label, + styles.mb1, + styles.mh4, + styles.mt0, + styles.mt3, + styles.pt0, + theme.textSupporting, + usersWorkspacesSectionData, + ], ); useEffect(() => { From 62b122a439b915ba278908936affa8fc5de836ee Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Wed, 20 Dec 2023 08:41:23 +0100 Subject: [PATCH 08/29] Cherry pick workspace card --- .../simple-illustration__hotdogstand.svg | 1 + .../DatePicker/CalendarPicker/index.js | 2 +- src/components/Icon/Illustrations.ts | 2 + src/components/Section/IconSection.js | 40 +++++++++++++ .../{Section.js => Section/index.js} | 57 ++++++++++++------- src/languages/en.ts | 5 +- src/languages/es.ts | 5 +- .../card/WorkspaceCardCreateAWorkspace.tsx | 33 +++++++++++ src/styles/index.ts | 10 ++-- 9 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 assets/images/simple-illustrations/simple-illustration__hotdogstand.svg create mode 100644 src/components/Section/IconSection.js rename src/components/{Section.js => Section/index.js} (61%) create mode 100644 src/pages/workspace/card/WorkspaceCardCreateAWorkspace.tsx diff --git a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg new file mode 100644 index 000000000000..732c16a75a2b --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/DatePicker/CalendarPicker/index.js b/src/components/DatePicker/CalendarPicker/index.js index a404c4746397..bbdeda6ef84f 100644 --- a/src/components/DatePicker/CalendarPicker/index.js +++ b/src/components/DatePicker/CalendarPicker/index.js @@ -236,7 +236,7 @@ class CalendarPicker extends React.PureComponent { diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 440350895826..c8a14c7aba03 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -35,6 +35,7 @@ import ConciergeNew from '@assets/images/simple-illustrations/simple-illustratio import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg'; import HandEarth from '@assets/images/simple-illustrations/simple-illustration__handearth.svg'; +import HotDogStand from '@assets/images/simple-illustrations/simple-illustration__hotdogstand.svg'; import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg'; import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg'; import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg'; @@ -60,6 +61,7 @@ export { ConciergeExclamation, CreditCardsBlue, EmailAddress, + HotDogStand, InvoiceOrange, JewelBoxBlue, JewelBoxGreen, diff --git a/src/components/Section/IconSection.js b/src/components/Section/IconSection.js new file mode 100644 index 000000000000..21dc458f2c76 --- /dev/null +++ b/src/components/Section/IconSection.js @@ -0,0 +1,40 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import useThemeStyles from '@hooks/useThemeStyles'; + +const iconSectionPropTypes = { + icon: PropTypes.icon, + IconComponent: PropTypes.IconComponent, + iconContainerStyles: PropTypes.iconContainerStyles, +}; + +const defaultIconSectionPropTypes = { + icon: null, + IconComponent: null, + iconContainerStyles: [], +}; + +function IconSection({icon, IconComponent, iconContainerStyles}) { + const styles = useThemeStyles(); + + return ( + + {Boolean(icon) && ( + + )} + {Boolean(IconComponent) && } + + ); +} + +IconSection.displayName = 'IconSection'; +IconSection.propTypes = iconSectionPropTypes; +IconSection.defaultProps = defaultIconSectionPropTypes; + +export default IconSection; diff --git a/src/components/Section.js b/src/components/Section/index.js similarity index 61% rename from src/components/Section.js rename to src/components/Section/index.js index a59476fb0c05..cadadfe94b39 100644 --- a/src/components/Section.js +++ b/src/components/Section/index.js @@ -1,11 +1,16 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; +import MenuItemList from '@components/MenuItemList'; +import menuItemPropTypes from '@components/menuItemPropTypes'; +import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; -import Icon from './Icon'; -import MenuItemList from './MenuItemList'; -import menuItemPropTypes from './menuItemPropTypes'; -import Text from './Text'; +import IconSection from './IconSection'; + +const CARD_LAYOUT = { + ICON_ON_TOP: 'iconOnTop', + ICON_ON_RIGHT: 'iconOnRight', +}; const propTypes = { /** An array of props that are pass to individual MenuItem components */ @@ -23,6 +28,10 @@ const propTypes = { /** Icon component */ IconComponent: PropTypes.func, + /** Card layout that affects icon positioning, margins, sizes. */ + // eslint-disable-next-line rulesdir/prefer-underscore-method + cardLayout: PropTypes.oneOf(Object.values(CARD_LAYOUT)), + /** Contents to display inside the section */ children: PropTypes.node, @@ -38,6 +47,9 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types subtitleStyles: PropTypes.arrayOf(PropTypes.object), + /** Whether the subtitle should have a muted style */ + subtitleMuted: PropTypes.bool, + /** Customize the Section container */ // eslint-disable-next-line react/forbid-prop-types childrenStyles: PropTypes.arrayOf(PropTypes.object), @@ -52,38 +64,45 @@ const defaultProps = { children: null, icon: null, IconComponent: null, + cardLayout: CARD_LAYOUT.ICON_ON_RIGHT, containerStyles: [], iconContainerStyles: [], titleStyles: [], subtitleStyles: [], + subtitleMuted: false, childrenStyles: [], subtitle: null, }; -function Section({children, childrenStyles, containerStyles, icon, IconComponent, iconContainerStyles, menuItems, subtitle, subtitleStyles, title, titleStyles}) { +function Section({children, childrenStyles, containerStyles, icon, IconComponent, cardLayout, iconContainerStyles, menuItems, subtitle, subtitleStyles, subtitleMuted, title, titleStyles}) { const styles = useThemeStyles(); + return ( <> - + {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( + + )} + {title} - - {Boolean(icon) && ( - - )} - {Boolean(IconComponent) && } - + {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && ( + + )} {Boolean(subtitle) && ( - - {subtitle} + + {subtitle} )} @@ -94,9 +113,9 @@ function Section({children, childrenStyles, containerStyles, icon, IconComponent ); } - Section.displayName = 'Section'; Section.propTypes = propTypes; Section.defaultProps = defaultProps; +export {CARD_LAYOUT}; export default Section; diff --git a/src/languages/en.ts b/src/languages/en.ts index 949ad64d7e6c..1c568c3c325b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1496,8 +1496,9 @@ export default { mustBeOnlineToViewMembers: 'You must be online in order to view members of this workspace.', }, emptyWorkspace: { - title: 'Create a new workspace', - subtitle: "Workspaces are where you'll chat with your team, reimburse expenses, issue cards, send invoices, pay bills, and more — all in one place.", + title: 'Create a workspace', + subtitle: 'Manage business expenses, issue cards, send invoices, and more.', + createAWorkspaceCTA: 'Get Started', features: { trackAndCollect: 'Track and collect receipts', companyCards: 'Company credit cards', diff --git a/src/languages/es.ts b/src/languages/es.ts index 70bcfa69f176..06f88d61ab6d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1519,8 +1519,9 @@ export default { mustBeOnlineToViewMembers: 'Debes estar en lĂ­nea para poder ver los miembros de este espacio de trabajo.', }, emptyWorkspace: { - title: 'Crear un nuevo espacio de trabajo', - subtitle: 'En los espacios de trabajo es donde puedes chatear con tu equipo, reembolsar gastos, emitir tarjetas, enviar y pagar facturas y mas — todo en un mismo lugar', + title: 'Crea un espacio de trabajo', + subtitle: 'Administra gastos de empresa, emite tarjetas, envĂ­a facturas y mucho mĂĄs.', + createAWorkspaceCTA: 'Comenzar', features: { trackAndCollect: 'Organiza recibos', companyCards: 'Tarjetas de crĂ©dito corporativas', diff --git a/src/pages/workspace/card/WorkspaceCardCreateAWorkspace.tsx b/src/pages/workspace/card/WorkspaceCardCreateAWorkspace.tsx new file mode 100644 index 000000000000..9e45bd143e7e --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardCreateAWorkspace.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import Button from '@components/Button'; +import * as Illustrations from '@components/Icon/Illustrations'; +import Section, {CARD_LAYOUT} from '@components/Section'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function WorkspaceCardCreateAWorkspace() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( +
+
+ ); +} + +WorkspaceCardCreateAWorkspace.displayName = 'WorkspaceCardNoVBAView'; + +export default WorkspaceCardCreateAWorkspace; diff --git a/src/styles/index.ts b/src/styles/index.ts index aba3bfaf84da..0400a0231c7a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -948,10 +948,14 @@ const styles = (theme: ThemeColors) => overflow: 'hidden', }, - calendarDayContainerSelected: { + buttonDefaultBG: { backgroundColor: theme.buttonDefaultBG, }, + buttonHoveredBG: { + backgroundColor: theme.buttonHoveredBG, + }, + autoGrowHeightInputContainer: (textInputHeight: number, minHeight: number, maxHeight: number) => ({ height: lodashClamp(textInputHeight, minHeight, maxHeight), @@ -1964,10 +1968,6 @@ const styles = (theme: ThemeColors) => alignSelf: 'flex-end', }, - hoveredButton: { - backgroundColor: theme.buttonHoveredBG, - }, - composerSizeButton: { alignSelf: 'center', height: 32, From 0db1612ed66e9f97f874d3f595ef5e61b55c1e00 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 22 Dec 2023 00:41:57 +0100 Subject: [PATCH 09/29] Implement simplified search --- src/pages/WorkspaceSwitcherPage.js | 70 +++++++++++++++--------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index 28386617eca5..a34ccfdf72d6 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -18,6 +18,9 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import Icon from '@components/Icon'; +import WorkspaceCardCreateAWorkspace from './workspace/card/WorkspaceCardCreateAWorkspace'; const propTypes = { /** The list of this user's policies */ @@ -39,7 +42,7 @@ const propTypes = { pendingAction: PropTypes.oneOf(_.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), }), ), - activeWorkspaceID: PropTypes.arrayOf([PropTypes.undefined, PropTypes.string]), + activeWorkspaceID: PropTypes.string, }; const defaultProps = { @@ -47,12 +50,16 @@ const defaultProps = { activeWorkspaceID: undefined, }; +const MINIMUM_WORKSPACES_TO_SHOW_SEARCH = 8; +const EXPENSIFY_TITLE = 'Expensify' + function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); const [selectedOption, setSelectedOption] = useState(); const [searchTerm, setSearchTerm] = useState(''); + const {inputCallbackRef} = useAutoFocusInput(); const getIndicatorTypeForPolicy = useCallback( // TO DO: Wait for missing logic to be implemented in other PR @@ -81,7 +88,6 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { }, []); const onChangeText = useCallback((newSearchTerm) => { - // TO DO: Handle searching logic setSearchTerm(newSearchTerm); }, []); @@ -102,28 +108,31 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { }, ], boldStyle: hasUnreadData(policy.id), + keyForList: policy.id, })) .sortBy((policy) => policy.text.toLowerCase()) .value(), [policies, isOffline, getIndicatorTypeForPolicy, hasUnreadData], ); + const filteredUserWorkspaces = useMemo(() => _.filter(usersWorkspaces, (policy) => policy.text.toLowerCase().startsWith(searchTerm.toLowerCase())), [searchTerm, usersWorkspaces]); + const usersWorkspacesSectionData = useMemo( () => ({ - data: usersWorkspaces, + data: filteredUserWorkspaces, shouldShow: true, indexOffset: 0, }), - [usersWorkspaces], + [filteredUserWorkspaces], ); const everythingSection = useMemo(() => { const option = { - text: 'Expensify', + text: EXPENSIFY_TITLE, icons: [ { source: Expensicons.ExpensifyAppIcon, - name: 'Expensify', + name: EXPENSIFY_TITLE, type: CONST.ICON_TYPE_AVATAR, }, ], @@ -166,30 +175,40 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { styles.mh4, theme.textSupporting, ]); - - const inputCallbackRef = useAutoFocusInput(); + const workspacesSection = useMemo( () => ( <> - + 0 ? [styles.mb1] : [styles.mb3])]}> + + + > Workspaces + + + {({hovered}) => ( + + )} + - {/* TO DO: Display breadcrumb */} - {usersWorkspacesSectionData.data.length === 0 && } - 0 ? 8} + shouldShowTextInput={usersWorkspaces.length < MINIMUM_WORKSPACES_TO_SHOW_SEARCH} onChangeText={onChangeText} selectedOptions={selectedOption ? [selectedOption] : []} onSelectRow={selectPolicy} - boldStyle shouldPreventDefaultFocusOnSelectRow highlightSelectedOptions shouldShowOptions @@ -199,27 +218,10 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { shouldShowSubscript={false} showTitleTooltip={false} contentContainerStyles={[styles.pt0, styles.mt0]} - /> + /> : } ), - [ - inputCallbackRef, - onChangeText, - searchTerm, - selectPolicy, - selectedOption, - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentBetween, - styles.label, - styles.mb1, - styles.mh4, - styles.mt0, - styles.mt3, - styles.pt0, - theme.textSupporting, - usersWorkspacesSectionData, - ], + [inputCallbackRef, onChangeText, searchTerm, selectPolicy, selectedOption, styles.alignItemsEnd, styles.borderRadiusNormal, styles.buttonDefaultBG, styles.buttonHoveredBG, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb1, styles.mb3, styles.mh4, styles.mt0, styles.mt2, styles.mt3, styles.p2, styles.pt0, theme.textSupporting, usersWorkspaces.length, usersWorkspacesSectionData], ); useEffect(() => { From 792190c907db7dda830958d5b40bb08cd57d293f Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 22 Dec 2023 11:28:31 +0100 Subject: [PATCH 10/29] Add translations --- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ src/pages/WorkspaceSwitcherPage.js | 24 +++++++----------------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 1c568c3c325b..51b1bb05479b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1507,6 +1507,10 @@ export default { notFound: 'No workspace found', description: 'Rooms are a great place to discuss and work with multiple people. To begin collaborating, create or join a workspace', }, + switcher: { + headerTitle: 'Choose a workspace', + everythingSection: 'Everything', + }, new: { newWorkspace: 'New workspace', getTheExpensifyCardAndMore: 'Get the Expensify Card and more', diff --git a/src/languages/es.ts b/src/languages/es.ts index 06f88d61ab6d..e6df03bcff27 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1530,6 +1530,10 @@ export default { notFound: 'No se encontrĂł ningĂșn espacio de trabajo', description: 'Las salas son un gran lugar para discutir y trabajar con varias personas. Para comenzar a colaborar, cree o Ășnase a un espacio de trabajo', }, + switcher: { + headerTitle: 'Elegir un espacio de trabajo', + everythingSection: 'Todo', + }, new: { newWorkspace: 'Nuevo espacio de trabajo', getTheExpensifyCardAndMore: 'Consigue la Tarjeta Expensify y mĂĄs', diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index a34ccfdf72d6..9ea0f9380a05 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -20,6 +20,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Icon from '@components/Icon'; +import useLocalize from '@hooks/useLocalize'; import WorkspaceCardCreateAWorkspace from './workspace/card/WorkspaceCardCreateAWorkspace'; const propTypes = { @@ -60,6 +61,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const [selectedOption, setSelectedOption] = useState(); const [searchTerm, setSearchTerm] = useState(''); const {inputCallbackRef} = useAutoFocusInput(); + const {translate} = useLocalize(); const getIndicatorTypeForPolicy = useCallback( // TO DO: Wait for missing logic to be implemented in other PR @@ -147,7 +149,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { style={styles.label} color={theme.textSupporting} > - Everything + {translate('workspace.switcher.everythingSection')} @@ -162,19 +164,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { ); - }, [ - activeWorkspaceID, - getIndicatorTypeForPolicy, - hasUnreadData, - selectPolicy, - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentBetween, - styles.label, - styles.mb3, - styles.mh4, - theme.textSupporting, - ]); + }, [activeWorkspaceID, getIndicatorTypeForPolicy, hasUnreadData, selectPolicy, styles.alignItemsCenter, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, theme.textSupporting, translate]); const workspacesSection = useMemo( () => ( @@ -186,7 +176,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { style={[styles.mt3, styles.label]} color={theme.textSupporting} > - Workspaces + {translate('common.workspaces')} @@ -221,7 +211,7 @@ additionalStyles={[styles.buttonDefaultBG, styles.borderRadiusNormal, styles.p2, /> : } ), - [inputCallbackRef, onChangeText, searchTerm, selectPolicy, selectedOption, styles.alignItemsEnd, styles.borderRadiusNormal, styles.buttonDefaultBG, styles.buttonHoveredBG, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb1, styles.mb3, styles.mh4, styles.mt0, styles.mt2, styles.mt3, styles.p2, styles.pt0, theme.textSupporting, usersWorkspaces.length, usersWorkspacesSectionData], + [inputCallbackRef, onChangeText, searchTerm, selectPolicy, selectedOption, styles.alignItemsEnd, styles.borderRadiusNormal, styles.buttonDefaultBG, styles.buttonHoveredBG, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb1, styles.mb3, styles.mh4, styles.mt0, styles.mt2, styles.mt3, styles.p2, styles.pt0, theme.textSupporting, translate, usersWorkspaces.length, usersWorkspacesSectionData], ); useEffect(() => { @@ -234,7 +224,7 @@ additionalStyles={[styles.buttonDefaultBG, styles.borderRadiusNormal, styles.p2, return ( {everythingSection} From 4a1735e853505128319ed9db36126f65b88a0681 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 18 Dec 2023 16:21:58 +0100 Subject: [PATCH 11/29] Add BrickRoadsUtils --- src/libs/BrickRoadsUtils.ts | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/libs/BrickRoadsUtils.ts diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts new file mode 100644 index 000000000000..63a03924bbf2 --- /dev/null +++ b/src/libs/BrickRoadsUtils.ts @@ -0,0 +1,65 @@ +import Onyx, {OnyxCollection} from 'react-native-onyx'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {Report} from '@src/types/onyx'; +import * as OptionsListUtils from './OptionsListUtils'; +import * as ReportActionsUtils from './ReportActionsUtils'; +import * as ReportUtils from './ReportUtils'; + +let allReports: OnyxCollection; + +type BrickRoad = 'GBR' | 'RBR' | undefined; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + +const getBrickRoadForPolicy = (policyReport: Report): BrickRoad | undefined => { + const policyReportAction = ReportActionsUtils.getAllReportActions(policyReport.reportID); + const reportErrors = OptionsListUtils.getAllReportErrors(policyReport, policyReportAction); + const brickRoadIndicator = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; + if (brickRoadIndicator) { + return 'RBR'; + } + let itemParentReportAction = {}; + if (policyReport.parentReportID) { + const itemParentReportActions = ReportActionsUtils.getAllReportActions(policyReport.parentReportID); + itemParentReportAction = policyReport.parentReportActionID ? itemParentReportActions[policyReport.parentReportActionID] : {}; + } + const optionFromPolicyReport = {...policyReport, isUnread: ReportUtils.isUnread(policyReport), isUnreadWithMention: ReportUtils.isUnreadWithMention(policyReport)}; + const shouldShowGreenDotIndicator = ReportUtils.requiresAttentionFromCurrentUser(optionFromPolicyReport, itemParentReportAction); + return shouldShowGreenDotIndicator ? 'GBR' : undefined; +}; + +function getWorkspacesBrickRoads(): Record { + if (!allReports) { + return {}; + } + + const brickRoadsMap: Record = {}; + + if (!allReports) { + return brickRoadsMap; + } + + Object.keys(allReports).forEach((report) => { + const policyID = allReports?.[report]?.policyID; + const policyReport = allReports ? allReports[report] : null; + if (!policyID || !policyReport || brickRoadsMap[policyID] === 'RBR') { + return; + } + const policyBrickRoad = getBrickRoadForPolicy(policyReport); + + if (!policyBrickRoad && !!brickRoadsMap[policyID]) { + return; + } + + brickRoadsMap[policyID] = policyBrickRoad; + }); + + return brickRoadsMap; +} + +export {getBrickRoadForPolicy, getWorkspacesBrickRoads}; From 72f72d392797634274f6e70427fbfb37924957ba Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 18 Dec 2023 16:27:56 +0100 Subject: [PATCH 12/29] Refactor getBrickRoadForPolicy return type --- src/libs/BrickRoadsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index 63a03924bbf2..03c11e8fcfd6 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -16,7 +16,7 @@ Onyx.connect({ callback: (value) => (allReports = value), }); -const getBrickRoadForPolicy = (policyReport: Report): BrickRoad | undefined => { +const getBrickRoadForPolicy = (policyReport: Report): BrickRoad => { const policyReportAction = ReportActionsUtils.getAllReportActions(policyReport.reportID); const reportErrors = OptionsListUtils.getAllReportErrors(policyReport, policyReportAction); const brickRoadIndicator = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; From ad55ac83e6c135a347d321b8cfbed5009fdf0179 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 18 Dec 2023 16:33:51 +0100 Subject: [PATCH 13/29] Refactor getBrickRoadForPolicy function --- src/libs/BrickRoadsUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index 03c11e8fcfd6..9fd8712c695e 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -19,8 +19,8 @@ Onyx.connect({ const getBrickRoadForPolicy = (policyReport: Report): BrickRoad => { const policyReportAction = ReportActionsUtils.getAllReportActions(policyReport.reportID); const reportErrors = OptionsListUtils.getAllReportErrors(policyReport, policyReportAction); - const brickRoadIndicator = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - if (brickRoadIndicator) { + const redBrickRoadIndicator = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; + if (redBrickRoadIndicator) { return 'RBR'; } let itemParentReportAction = {}; From 7a3caa4d1ff44f6c9ef9eb6402791ed0f936c35b Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 18 Dec 2023 17:25:44 +0100 Subject: [PATCH 14/29] Refactor getWorkspacesBrickRoads function --- src/libs/BrickRoadsUtils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index 9fd8712c695e..e4180e5044b8 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -40,10 +40,6 @@ function getWorkspacesBrickRoads(): Record { const brickRoadsMap: Record = {}; - if (!allReports) { - return brickRoadsMap; - } - Object.keys(allReports).forEach((report) => { const policyID = allReports?.[report]?.policyID; const policyReport = allReports ? allReports[report] : null; From 7043a9a5211909e5401b9a0dd15660e91dc411a3 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 18 Dec 2023 17:28:56 +0100 Subject: [PATCH 15/29] Add export type BrickRoad --- src/libs/BrickRoadsUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index e4180e5044b8..0d292fd808cc 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -59,3 +59,4 @@ function getWorkspacesBrickRoads(): Record { } export {getBrickRoadForPolicy, getWorkspacesBrickRoads}; +export type {BrickRoad}; From 2023eaa9e44daacf680ffc19565c81fce780a397 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 19 Dec 2023 17:05:04 +0100 Subject: [PATCH 16/29] Refactor BrickRoad type, add commments in BrickRoadsUtils --- src/CONST.ts | 4 ++++ src/libs/BrickRoadsUtils.ts | 29 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 14c8d8a81e70..2ef0d6c9a155 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3037,6 +3037,10 @@ const CONST = { DEFAULT: 5, CAROUSEL: 3, }, + BRICK_ROAD: { + GBR: 'GBR', + RBR: 'RBR', + }, } as const; export default CONST; diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index 0d292fd808cc..8bbf1d4c437b 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -1,4 +1,5 @@ import Onyx, {OnyxCollection} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {Report} from '@src/types/onyx'; @@ -8,7 +9,7 @@ import * as ReportUtils from './ReportUtils'; let allReports: OnyxCollection; -type BrickRoad = 'GBR' | 'RBR' | undefined; +type BrickRoad = ValueOf | undefined; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, @@ -16,12 +17,16 @@ Onyx.connect({ callback: (value) => (allReports = value), }); +/** + * @param policyReport + * @returns BrickRoad for the policy passed as a param + */ const getBrickRoadForPolicy = (policyReport: Report): BrickRoad => { const policyReportAction = ReportActionsUtils.getAllReportActions(policyReport.reportID); const reportErrors = OptionsListUtils.getAllReportErrors(policyReport, policyReportAction); - const redBrickRoadIndicator = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; - if (redBrickRoadIndicator) { - return 'RBR'; + const doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; + if (doesReportContainErrors) { + return CONST.BRICK_ROAD.RBR; } let itemParentReportAction = {}; if (policyReport.parentReportID) { @@ -30,32 +35,36 @@ const getBrickRoadForPolicy = (policyReport: Report): BrickRoad => { } const optionFromPolicyReport = {...policyReport, isUnread: ReportUtils.isUnread(policyReport), isUnreadWithMention: ReportUtils.isUnreadWithMention(policyReport)}; const shouldShowGreenDotIndicator = ReportUtils.requiresAttentionFromCurrentUser(optionFromPolicyReport, itemParentReportAction); - return shouldShowGreenDotIndicator ? 'GBR' : undefined; + return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD.GBR : undefined; }; +/** + * @returns a map where the keys are policyIDs and the values are BrickRoads for each policy + */ function getWorkspacesBrickRoads(): Record { if (!allReports) { return {}; } - const brickRoadsMap: Record = {}; + //The key in this map is the workspace id + const workspacesBrickRoadsMap: Record = {}; Object.keys(allReports).forEach((report) => { const policyID = allReports?.[report]?.policyID; const policyReport = allReports ? allReports[report] : null; - if (!policyID || !policyReport || brickRoadsMap[policyID] === 'RBR') { + if (!policyID || !policyReport || workspacesBrickRoadsMap[policyID] === CONST.BRICK_ROAD.RBR) { return; } const policyBrickRoad = getBrickRoadForPolicy(policyReport); - if (!policyBrickRoad && !!brickRoadsMap[policyID]) { + if (!policyBrickRoad && !!workspacesBrickRoadsMap[policyID]) { return; } - brickRoadsMap[policyID] = policyBrickRoad; + workspacesBrickRoadsMap[policyID] = policyBrickRoad; }); - return brickRoadsMap; + return workspacesBrickRoadsMap; } export {getBrickRoadForPolicy, getWorkspacesBrickRoads}; From 6cc04371acddc83d592a97cddbda36dc7d7dad3d Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 19 Dec 2023 17:10:03 +0100 Subject: [PATCH 17/29] Fix lint in BrickRoadsUtils --- src/libs/BrickRoadsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index 8bbf1d4c437b..ceee9b570268 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -46,7 +46,7 @@ function getWorkspacesBrickRoads(): Record { return {}; } - //The key in this map is the workspace id + // The key in this map is the workspace id const workspacesBrickRoadsMap: Record = {}; Object.keys(allReports).forEach((report) => { From f618fcc256e5b3fff3e3120527b5cae3abaf0ff3 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 21 Dec 2023 09:44:16 +0100 Subject: [PATCH 18/29] Refactor BrickRoadsUtils --- src/libs/BrickRoadsUtils.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/BrickRoadsUtils.ts index ceee9b570268..5116ce4f2e92 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/BrickRoadsUtils.ts @@ -18,23 +18,25 @@ Onyx.connect({ }); /** - * @param policyReport + * @param report * @returns BrickRoad for the policy passed as a param */ -const getBrickRoadForPolicy = (policyReport: Report): BrickRoad => { - const policyReportAction = ReportActionsUtils.getAllReportActions(policyReport.reportID); - const reportErrors = OptionsListUtils.getAllReportErrors(policyReport, policyReportAction); +const getBrickRoadForPolicy = (report: Report): BrickRoad => { + const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); + const reportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); const doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; if (doesReportContainErrors) { return CONST.BRICK_ROAD.RBR; } + + // To determine if the report requires attention from the current user, we need to load the parent report action let itemParentReportAction = {}; - if (policyReport.parentReportID) { - const itemParentReportActions = ReportActionsUtils.getAllReportActions(policyReport.parentReportID); - itemParentReportAction = policyReport.parentReportActionID ? itemParentReportActions[policyReport.parentReportActionID] : {}; + if (report.parentReportID) { + const itemParentReportActions = ReportActionsUtils.getAllReportActions(report.parentReportID); + itemParentReportAction = report.parentReportActionID ? itemParentReportActions[report.parentReportActionID] : {}; } - const optionFromPolicyReport = {...policyReport, isUnread: ReportUtils.isUnread(policyReport), isUnreadWithMention: ReportUtils.isUnreadWithMention(policyReport)}; - const shouldShowGreenDotIndicator = ReportUtils.requiresAttentionFromCurrentUser(optionFromPolicyReport, itemParentReportAction); + const reportOption = {...report, isUnread: ReportUtils.isUnread(report), isUnreadWithMention: ReportUtils.isUnreadWithMention(report)}; + const shouldShowGreenDotIndicator = ReportUtils.requiresAttentionFromCurrentUser(reportOption, itemParentReportAction); return shouldShowGreenDotIndicator ? CONST.BRICK_ROAD.GBR : undefined; }; @@ -55,13 +57,13 @@ function getWorkspacesBrickRoads(): Record { if (!policyID || !policyReport || workspacesBrickRoadsMap[policyID] === CONST.BRICK_ROAD.RBR) { return; } - const policyBrickRoad = getBrickRoadForPolicy(policyReport); + const workspaceBrickRoad = getBrickRoadForPolicy(policyReport); - if (!policyBrickRoad && !!workspacesBrickRoadsMap[policyID]) { + if (!workspaceBrickRoad && !!workspacesBrickRoadsMap[policyID]) { return; } - workspacesBrickRoadsMap[policyID] = policyBrickRoad; + workspacesBrickRoadsMap[policyID] = workspaceBrickRoad; }); return workspacesBrickRoadsMap; From bc79188272bd54569c98f1a6dd67baf69ea750a0 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 22 Dec 2023 12:25:57 +0100 Subject: [PATCH 19/29] Add logic for gbr/rbr and unread chats --- src/CONST.ts | 4 +- src/libs/Navigation/linkingConfig.ts | 2 +- ...{BrickRoadsUtils.ts => WorkspacesUtils.ts} | 32 +++- src/pages/WorkspaceSwitcherPage.js | 159 ++++++++++++------ 4 files changed, 143 insertions(+), 54 deletions(-) rename src/libs/{BrickRoadsUtils.ts => WorkspacesUtils.ts} (75%) diff --git a/src/CONST.ts b/src/CONST.ts index 2ef0d6c9a155..ed4901415dc3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3038,8 +3038,8 @@ const CONST = { CAROUSEL: 3, }, BRICK_ROAD: { - GBR: 'GBR', - RBR: 'RBR', + GBR: 'info', + RBR: 'error', }, } as const; diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index eb3877516b3b..bbdc1319c154 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -263,7 +263,7 @@ const linkingConfig: LinkingOptions = { }, [SCREENS.WORKSPACE.NAME]: ROUTES.WORKSPACE_NAME.route, }, - }, + }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { screens: { [SCREENS.PRIVATE_NOTES.VIEW]: ROUTES.PRIVATE_NOTES_VIEW.route, diff --git a/src/libs/BrickRoadsUtils.ts b/src/libs/WorkspacesUtils.ts similarity index 75% rename from src/libs/BrickRoadsUtils.ts rename to src/libs/WorkspacesUtils.ts index 5116ce4f2e92..caba82d9ca70 100644 --- a/src/libs/BrickRoadsUtils.ts +++ b/src/libs/WorkspacesUtils.ts @@ -69,5 +69,35 @@ function getWorkspacesBrickRoads(): Record { return workspacesBrickRoadsMap; } -export {getBrickRoadForPolicy, getWorkspacesBrickRoads}; +/** + * @returns a map where the keys are policyIDs and the values are truthy booleans if policy has unread content + */ +function getWorkspacesUnreadStatuses(): Record { + if(!allReports) { + return {}; + } + + const workspacesUnreadStatuses: Record = {} + + Object.keys(allReports).forEach((report) => { + const policyID = allReports?.[report]?.policyID; + const policyReport = allReports ? allReports[report] : null; + if (!policyID || !policyReport) { + return; + } + + const unreadStatus = ReportUtils.isUnread(policyReport); + + if(unreadStatus) { + workspacesUnreadStatuses[policyID] = true; + } + else { + workspacesUnreadStatuses[policyID] = false; + } + }) + + return workspacesUnreadStatuses; +} + +export {getBrickRoadForPolicy, getWorkspacesBrickRoads, getWorkspacesUnreadStatuses}; export type {BrickRoad}; diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index 9ea0f9380a05..f670af604b8f 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -4,23 +4,24 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import HeaderPageLayout from '@components/HeaderPageLayout'; +import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import OptionRow from '@components/OptionRow'; import OptionsSelector from '@components/OptionsSelector'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {getWorkspacesBrickRoads, getWorkspacesUnreadStatuses} from '@libs/WorkspacesUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import Icon from '@components/Icon'; -import useLocalize from '@hooks/useLocalize'; import WorkspaceCardCreateAWorkspace from './workspace/card/WorkspaceCardCreateAWorkspace'; const propTypes = { @@ -52,7 +53,7 @@ const defaultProps = { }; const MINIMUM_WORKSPACES_TO_SHOW_SEARCH = 8; -const EXPENSIFY_TITLE = 'Expensify' +const EXPENSIFY_TITLE = 'Expensify'; function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const theme = useTheme(); @@ -63,19 +64,36 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); - const getIndicatorTypeForPolicy = useCallback( - // TO DO: Wait for missing logic to be implemented in other PR - // CONST.BRICK_ROAD_INDICATOR_STATUS.INFO or CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR - // eslint-disable-next-line no-unused-vars - (policyId) => undefined, - [], - ); + const brickRoadsForPolicies = getWorkspacesBrickRoads(); + const unreadStatusesForPolicies = getWorkspacesUnreadStatuses(); + + const getIndicatorTypeForPolicy = useCallback((policyId) => { + if (policyId && policyId !== activeWorkspaceID) { + return brickRoadsForPolicies[policyId]; + } + + if(_.values(brickRoadsForPolicies).includes(CONST.BRICK_ROAD.RBR)) { + return CONST.BRICK_ROAD.RBR; + } + + if(_.values(brickRoadsForPolicies).includes(CONST.BRICK_ROAD.GBR)) { + return CONST.BRICK_ROAD.GBR; + } + + return undefined + }, [activeWorkspaceID, brickRoadsForPolicies]); const hasUnreadData = useCallback( // TO DO: Implement checking if policy has some unread data // eslint-disable-next-line no-unused-vars - (policyId) => false, - [], + (policyId) => { + if(policyId) { + return unreadStatusesForPolicies[policyId]; + } + + return _.some(_.values(unreadStatusesForPolicies), (status) => status) + }, + [unreadStatusesForPolicies], ); const selectPolicy = useCallback((option) => { @@ -164,54 +182,95 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) {
); - }, [activeWorkspaceID, getIndicatorTypeForPolicy, hasUnreadData, selectPolicy, styles.alignItemsCenter, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb3, styles.mh4, theme.textSupporting, translate]); - + }, [ + activeWorkspaceID, + getIndicatorTypeForPolicy, + hasUnreadData, + selectPolicy, + styles.alignItemsCenter, + styles.flexRow, + styles.justifyContentBetween, + styles.label, + styles.mb3, + styles.mh4, + theme.textSupporting, + translate, + ]); + const workspacesSection = useMemo( () => ( <> 0 ? [styles.mb1] : [styles.mb3])]}> - - - {translate('common.workspaces')} - - + {translate('common.workspaces')} + + - {({hovered}) => ( - - )} - + {({hovered}) => ( + + )} +
- {usersWorkspaces.length > 0 ? : } + {usersWorkspaces.length > 0 ? ( + + ) : ( + + )} ), - [inputCallbackRef, onChangeText, searchTerm, selectPolicy, selectedOption, styles.alignItemsEnd, styles.borderRadiusNormal, styles.buttonDefaultBG, styles.buttonHoveredBG, styles.flexRow, styles.justifyContentBetween, styles.label, styles.mb1, styles.mb3, styles.mh4, styles.mt0, styles.mt2, styles.mt3, styles.p2, styles.pt0, theme.textSupporting, translate, usersWorkspaces.length, usersWorkspacesSectionData], + [ + inputCallbackRef, + onChangeText, + searchTerm, + selectPolicy, + selectedOption, + styles.alignItemsEnd, + styles.borderRadiusNormal, + styles.buttonDefaultBG, + styles.buttonHoveredBG, + styles.flexRow, + styles.justifyContentBetween, + styles.label, + styles.mb1, + styles.mb3, + styles.mh4, + styles.mt0, + styles.mt2, + styles.mt3, + styles.p2, + styles.pt0, + theme.textSupporting, + translate, + usersWorkspaces.length, + usersWorkspacesSectionData, + ], ); useEffect(() => { From a11528614c0ee8d6a33f960deb1452b60d527c67 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Fri, 22 Dec 2023 12:41:03 +0100 Subject: [PATCH 20/29] Add navigation to workspace editor --- src/pages/WorkspaceSwitcherPage.js | 7 ++++--- src/pages/workspace/card/WorkspaceCardCreateAWorkspace.tsx | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage.js b/src/pages/WorkspaceSwitcherPage.js index f670af604b8f..d11c938229b6 100644 --- a/src/pages/WorkspaceSwitcherPage.js +++ b/src/pages/WorkspaceSwitcherPage.js @@ -22,6 +22,7 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; +import * as App from '@userActions/App'; import WorkspaceCardCreateAWorkspace from './workspace/card/WorkspaceCardCreateAWorkspace'; const propTypes = { @@ -64,8 +65,8 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { const {inputCallbackRef} = useAutoFocusInput(); const {translate} = useLocalize(); - const brickRoadsForPolicies = getWorkspacesBrickRoads(); - const unreadStatusesForPolicies = getWorkspacesUnreadStatuses(); + const brickRoadsForPolicies = useMemo(() => getWorkspacesBrickRoads(), []); + const unreadStatusesForPolicies = useMemo(() => getWorkspacesUnreadStatuses(), []); const getIndicatorTypeForPolicy = useCallback((policyId) => { if (policyId && policyId !== activeWorkspaceID) { @@ -209,7 +210,7 @@ function WorkspaceSwitcherPage({policies, activeWorkspaceID}) { {translate('common.workspaces')}
- + {App.createWorkspaceWithPolicyDraftAndNavigateToIt()}}> {({hovered}) => (