diff --git a/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx b/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx index 518a78033e28e..48d6a6a411b7c 100644 --- a/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx +++ b/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx @@ -1,20 +1,21 @@ import { Box } from '@rocket.chat/fuselage'; import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { useUserPreference, useUserId } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { GroupedVirtuoso } from 'react-virtuoso'; import RoomListCollapser from './RoomListCollapser'; +import RoomsListFilters from './RoomListFilters'; import RoomListRow from './RoomListRow'; import RoomListRowWrapper from './RoomListRowWrapper'; import RoomListWrapper from './RoomListWrapper'; import { VirtualizedScrollbars } from '../../components/CustomScrollbars'; import { useOpenedRoom } from '../../lib/RoomManager'; +import { useSideBarRoomsList } from '../../views/navigation/contexts/RoomsNavigationContext'; import { useAvatarTemplate } from '../hooks/useAvatarTemplate'; -import { useCollapsedGroups } from '../hooks/useCollapsedGroups'; import { usePreventDefault } from '../hooks/usePreventDefault'; -import { useRoomList } from '../hooks/useRoomList'; import { useShortcutOpenMenu } from '../hooks/useShortcutOpenMenu'; import { useTemplateByViewMode } from '../hooks/useTemplateByViewMode'; @@ -22,10 +23,9 @@ const RoomList = () => { const { t } = useTranslation(); const isAnonymous = !useUserId(); - const { collapsedGroups, handleClick, handleKeyDown } = useCollapsedGroups(); - const { groupsCount, groupsList, roomList, groupedUnreadInfo } = useRoomList({ collapsedGroups }); - const avatarTemplate = useAvatarTemplate(); - const sideBarItemTemplate = useTemplateByViewMode(); + const { roomListGroups, groupCounts, collapsedGroups, handleClick, handleKeyDown, totalCount } = useSideBarRoomsList(); + const avatarTemplate = useAvatarTemplate('condensed'); + const sideBarItemTemplate = useTemplateByViewMode('condensed'); const { ref } = useResizeObserver({ debounceDelay: 100 }); const openedRoom = useOpenedRoom() ?? ''; const sidebarViewMode = useUserPreference<'extended' | 'medium' | 'condensed'>('sidebarViewMode') || 'extended'; @@ -51,20 +51,30 @@ const RoomList = () => { ( - handleClick(groupsList[index])} - onKeyDown={(e) => handleKeyDown(e, groupsList[index])} - groupTitle={groupsList[index]} - unreadCount={groupedUnreadInfo[index]} - /> - )} - {...(roomList.length > 0 && { - itemContent: (index) => roomList[index] && , + groupCounts={groupCounts} + groupContent={(index) => { + const { group, unreadInfo } = roomListGroups[index]; + return ( + handleClick(group)} + onKeyDown={(e) => handleKeyDown(e, group)} + groupTitle={group} + unreadCount={unreadInfo} + /> + ); + }} + {...(totalCount > 0 && { + itemContent: (index, groupIndex) => { + const { rooms } = roomListGroups[groupIndex]; + // Grouped virtuoso index increases linearly, but we're indexing the list by group. + // Either we go back to providing a single list, or we do this. + const correctedIndex = index - groupCounts.slice(0, groupIndex).reduce((acc, count) => acc + count, 0); + // TODO: ILivechatInquiryRecord + return rooms[correctedIndex] && ; + }, })} - components={{ Item: RoomListRowWrapper, List: RoomListWrapper }} + components={{ Header: RoomsListFilters, Item: RoomListRowWrapper, List: RoomListWrapper }} /> diff --git a/apps/meteor/client/sidebarv2/RoomList/RoomListFilters.tsx b/apps/meteor/client/sidebarv2/RoomList/RoomListFilters.tsx new file mode 100644 index 0000000000000..8bc73d0afc3b4 --- /dev/null +++ b/apps/meteor/client/sidebarv2/RoomList/RoomListFilters.tsx @@ -0,0 +1,72 @@ +import { Divider, Box } from '@rocket.chat/fuselage'; +import type { Keys as IconName } from '@rocket.chat/icons'; +import { forwardRef } from 'react'; +import type { Components } from 'react-virtuoso'; + +import RoomListFiltersItem from './RoomListFiltersItem'; +import { useOmnichannelEnabled } from '../../hooks/omnichannel/useOmnichannelEnabled'; +import type { SidePanelFiltersKeys } from '../../views/navigation/contexts/RoomsNavigationContext'; +import { + OMNICHANNEL_GROUPS, + SIDE_PANEL_GROUPS, + TEAM_COLLAB_GROUPS, + useSwitchSidePanelTab, + useSidePanelFilter, +} from '../../views/navigation/contexts/RoomsNavigationContext'; + +const filtersIcons: { [Key in SidePanelFiltersKeys]: IconName } = { + All: 'inbox', + Mentions: 'at', + Starred: 'star', + Discussions: 'baloons', + In_progress: 'user-arrow-right', + Queue: 'burger-arrow-left', + On_Hold: 'pause-unfilled', +}; + +const RoomListFilters: Components['Header'] = forwardRef(function RoomListWrapper(_, ref) { + // const favoritesEnabled = useUserPreference('sidebarShowFavorites', true); + const showOmnichannel = useOmnichannelEnabled(); + + const switchSidePanelTab = useSwitchSidePanelTab(); + const [currentTab] = useSidePanelFilter(); + + if (Object.values(SIDE_PANEL_GROUPS).length === 0) { + return null; + } + + return ( + + + {Object.values(TEAM_COLLAB_GROUPS).map((group) => ( + switchSidePanelTab(group)} + /> + ))} + + + {showOmnichannel && ( + <> + + {Object.values(OMNICHANNEL_GROUPS).map((group) => ( + switchSidePanelTab(group)} + /> + ))} + + + + )} + + ); +}); + +export default RoomListFilters; diff --git a/apps/meteor/client/sidebarv2/RoomList/RoomListFiltersItem.tsx b/apps/meteor/client/sidebarv2/RoomList/RoomListFiltersItem.tsx new file mode 100644 index 0000000000000..f584543dddace --- /dev/null +++ b/apps/meteor/client/sidebarv2/RoomList/RoomListFiltersItem.tsx @@ -0,0 +1,40 @@ +import { Icon, SidebarV2Item, SidebarV2ItemTitle } from '@rocket.chat/fuselage'; +import type { Keys as IconName } from '@rocket.chat/icons'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +// import { useUnreadDisplay } from './hooks/useUnreadDisplay'; +import type { SidePanelFiltersKeys } from '../../views/navigation/contexts/RoomsNavigationContext'; +// import { useUnreadGroupData } from '../views/navigation/contexts/RoomsNavigationContext'; + +type SidebarFiltersItemProps = { + title: SidePanelFiltersKeys; + selected: boolean; + icon: IconName; + onClick: () => void; +}; + +const RoomListFiltersItem = ({ title, selected, icon, onClick }: SidebarFiltersItemProps) => { + const { t } = useTranslation(); + // const unreadGroupCount = useUnreadGroupData(title); + // const { unreadTitle, unreadVariant, showUnread, unreadCount } = useUnreadDisplay(unreadGroupCount); + + return ( + + + {t(title)} + {/* {showUnread && ( + + {unreadCount.total} + + )} */} + + ); +}; + +export default memo(RoomListFiltersItem); diff --git a/apps/meteor/client/sidebarv2/hooks/useTemplateByViewMode.ts b/apps/meteor/client/sidebarv2/hooks/useTemplateByViewMode.ts index 2362669f3ebdf..a2c2bacf9ad73 100644 --- a/apps/meteor/client/sidebarv2/hooks/useTemplateByViewMode.ts +++ b/apps/meteor/client/sidebarv2/hooks/useTemplateByViewMode.ts @@ -6,10 +6,10 @@ import Condensed from '../Item/Condensed'; import Extended from '../Item/Extended'; import Medium from '../Item/Medium'; -export const useTemplateByViewMode = (): ComponentType => { +export const useTemplateByViewMode = (viewMode?: string): ComponentType => { const sidebarViewMode = useUserPreference('sidebarViewMode'); return useMemo(() => { - switch (sidebarViewMode) { + switch (viewMode || sidebarViewMode) { case 'extended': return Extended; case 'medium': @@ -18,5 +18,5 @@ export const useTemplateByViewMode = (): ComponentType => { default: return Condensed; } - }, [sidebarViewMode]); + }, [sidebarViewMode, viewMode]); }; diff --git a/apps/meteor/client/sidepanel/SidePanel.tsx b/apps/meteor/client/sidepanel/SidePanel.tsx new file mode 100644 index 0000000000000..8e1b375d47dcd --- /dev/null +++ b/apps/meteor/client/sidepanel/SidePanel.tsx @@ -0,0 +1,57 @@ +import { Box, Sidepanel, SidepanelHeader, SidepanelHeaderTitle, SidepanelListItem, ToggleSwitch } from '@rocket.chat/fuselage'; +import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts'; +import { memo, useId, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Virtuoso } from 'react-virtuoso'; + +import { VirtualizedScrollbars } from '../components/CustomScrollbars'; +import GenericNoResults from '../components/GenericNoResults'; +import { useOpenedRoom, useSecondLevelOpenedRoom } from '../lib/RoomManager'; +import { usePreventDefault } from '../sidebarv2/hooks/usePreventDefault'; +import RoomSidepanelListWrapper from '../views/room/Sidepanel/RoomSidepanelListWrapper'; +import RoomSidepanelItem from '../views/room/Sidepanel/SidepanelItem'; + +type SidePanelProps = { + headerTitle: TranslationKey; + onlyUnreads: boolean; + toggleOnlyUnreads: () => void; + // TODO: This can also be of type ILivechatInquiryRecord[] + rooms: SubscriptionWithRoom[]; +}; + +const SidePanel = ({ headerTitle, onlyUnreads, toggleOnlyUnreads, rooms }: SidePanelProps) => { + const { t } = useTranslation(); + const ref = useRef(null); + const unreadFieldId = useId(); + const parentRid = useOpenedRoom(); + const secondLevelOpenedRoom = useSecondLevelOpenedRoom() ?? parentRid; + + usePreventDefault(ref); + + return ( + + + {t(headerTitle)} + + + {t('Unread')} + + + + + + {rooms && rooms.length === 0 && } + + } + /> + + + + ); +}; + +export default memo(SidePanel); diff --git a/apps/meteor/client/sidepanel/SidePanelRouter.tsx b/apps/meteor/client/sidepanel/SidePanelRouter.tsx new file mode 100644 index 0000000000000..8bbd43bc6e138 --- /dev/null +++ b/apps/meteor/client/sidepanel/SidePanelRouter.tsx @@ -0,0 +1,33 @@ +import SidePanel from './SidePanel'; +// import SidePanelAll from './tabs/SidePanelAll'; +// import SidePanelStarred from './tabs/SidePanelStarred'; +// import { useSidePanelFilter } from '../views/navigation/contexts/RoomsNavigationContext'; + +const SidePanelRouter = () => { + // const [currentTab] = useSidePanelFilter(); + + return undefined} />; + // return ; + + // TODO: figure out if we need this switch + // switch (currentTab) { + // case SIDE_PANEL_GROUPS.ALL: + // return ; + // case SIDE_PANEL_GROUPS.STARRED: + // return ; + // case SIDE_PANEL_GROUPS.MENTIONS: + // return null; // TODO implement tab + // case SIDE_PANEL_GROUPS.DISCUSSIONS: + // return null; // TODO implement tab + // case SIDE_PANEL_GROUPS.IN_PROGRESS: + // return null; // TODO implement tab + // case SIDE_PANEL_GROUPS.QUEUE: + // return null; // TODO implement tab + // case SIDE_PANEL_GROUPS.ON_HOLD: + // return null; // TODO implement tab + // default: + // return null; + // } +}; + +export default SidePanelRouter; diff --git a/apps/meteor/client/sidepanel/index.ts b/apps/meteor/client/sidepanel/index.ts new file mode 100644 index 0000000000000..c6a33a4bee0b4 --- /dev/null +++ b/apps/meteor/client/sidepanel/index.ts @@ -0,0 +1 @@ +export { default } from './SidePanelRouter'; diff --git a/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts b/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts new file mode 100644 index 0000000000000..aed6f7baa7f6e --- /dev/null +++ b/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts @@ -0,0 +1,159 @@ +import type { ILivechatInquiryRecord } from '@rocket.chat/core-typings'; +import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; +import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; +import { createContext, useContext, useMemo } from 'react'; + +import { useCollapsedGroups } from '../hooks/useCollapsedGroups'; + +export const TEAM_COLLAB_GROUPS = { + ALL: 'All', + STARRED: 'Starred', + MENTIONS: 'Mentions', + DISCUSSIONS: 'Discussions', +} as const; + +export const OMNICHANNEL_GROUPS = { + IN_PROGRESS: 'In_progress', + QUEUE: 'Queue', + ON_HOLD: 'On_Hold', +} as const; + +export const SIDE_PANEL_GROUPS = { + ...TEAM_COLLAB_GROUPS, + ...OMNICHANNEL_GROUPS, +} as const; + +export const SIDE_BAR_GROUPS = { + TEAMS: 'Teams', + CHANNELS: 'Channels', + DIRECT_MESSAGES: 'Direct_Messages', +} as const; + +export const SidebarOrder = [SIDE_BAR_GROUPS.TEAMS, SIDE_BAR_GROUPS.CHANNELS, SIDE_BAR_GROUPS.DIRECT_MESSAGES]; + +export type SidePanelFiltersKeys = (typeof SIDE_PANEL_GROUPS)[keyof typeof SIDE_PANEL_GROUPS]; +export type SidePanelFiltersUnreadKeys = `${SidePanelFiltersKeys}_unread`; +export type SidePanelFilters = SidePanelFiltersKeys | SidePanelFiltersUnreadKeys; + +export type SideBarGroupsKeys = (typeof SIDE_BAR_GROUPS)[keyof typeof SIDE_BAR_GROUPS]; +export type SideBarGroupsUnreadKeys = `${SideBarGroupsKeys}_unread`; + +export type AllGroupsKeys = SidePanelFiltersKeys | SideBarGroupsKeys; + +export type AllGroupsKeysWithUnread = SidePanelFilters | SideBarGroupsKeys | SideBarGroupsUnreadKeys; + +export type RoomsNavigationContextValue = { + groups: Map>; + currentFilter: AllGroupsKeysWithUnread; + setFilter: (filter: SidePanelFiltersKeys, unread: boolean) => void; + unreadGroupData: Map; +}; + +export type GroupedUnreadInfoData = { + userMentions: number; + groupMentions: number; + tunread: string[]; + tunreadUser: string[]; + unread: number; +}; + +export const RoomsNavigationContext = createContext(undefined); + +export const useRoomsListContext = () => { + const contextValue = useContext(RoomsNavigationContext); + + if (!contextValue) { + throw new Error('useRoomsListValue must be used within a RoomsNavigationContext'); + } + + return contextValue; +}; + +// Helper functions +const splitFilter = (currentFilter: AllGroupsKeysWithUnread): [SidePanelFiltersKeys, boolean] => { + const [currentTab, unread] = currentFilter.split('_'); + return [currentTab as SidePanelFiltersKeys, unread === 'unread']; +}; + +export const getFilterKey = (tab: AllGroupsKeys, unread: boolean): AllGroupsKeysWithUnread => { + return unread ? `${tab}_unread` : tab; +}; + +export const getEmptyUnreadInfo = (): GroupedUnreadInfoData => ({ + userMentions: 0, + groupMentions: 0, + tunread: [], + tunreadUser: [], + unread: 0, +}); + +// Hooks +type RoomListGroup = { + group: AllGroupsKeys; + rooms: Array; + unreadInfo: GroupedUnreadInfoData; +}; + +export const useSideBarRoomsList = (): { roomListGroups: RoomListGroup[]; groupCounts: number[]; totalCount: number } & ReturnType< + typeof useCollapsedGroups +> => { + const { collapsedGroups, handleClick, handleKeyDown } = useCollapsedGroups(); + const { groups, unreadGroupData } = useRoomsListContext(); + + const roomListGroups = SidebarOrder.map((group) => { + const roomSet = groups.get(group); + const rooms = roomSet ? Array.from(roomSet) : []; + const unreadInfo = unreadGroupData.get(group) || getEmptyUnreadInfo(); + return { group, rooms, unreadInfo }; + }); + + const groupCounts = roomListGroups.map((group) => { + if (collapsedGroups.includes(group.group)) { + return 0; + } + return group.rooms.length; + }); + + return { + collapsedGroups, + handleClick, + handleKeyDown, + roomListGroups, + groupCounts, + totalCount: groupCounts.reduce((acc, count) => acc + count, 0), + }; +}; + +export const useSidePanelRoomsListTab = (tab: AllGroupsKeys) => { + const [, unread] = useSidePanelFilter(); + const roomSet = useRoomsListContext().groups.get(getFilterKey(tab, unread)); + const roomsList = useMemo(() => { + if (!roomSet) { + return []; + } + + return Array.from(roomSet); + }, [roomSet]); + return roomsList; +}; + +export const useSidePanelFilter = (): [SidePanelFiltersKeys, boolean, AllGroupsKeysWithUnread] => { + const { currentFilter } = useRoomsListContext(); + return [...splitFilter(currentFilter), currentFilter]; +}; + +export const useUnreadOnlyToggle = (): [boolean, () => void] => { + const { setFilter } = useRoomsListContext(); + const [currentTab, unread] = useSidePanelFilter(); + + return [unread, useEffectEvent(() => setFilter(currentTab, !unread))]; +}; + +export const useSwitchSidePanelTab = () => { + const { setFilter } = useRoomsListContext(); + const [, unread] = useSidePanelFilter(); + + return (tab: SidePanelFiltersKeys) => setFilter(tab, unread); +}; + +export const useUnreadGroupData = (key: SidePanelFiltersKeys) => useRoomsListContext().unreadGroupData.get(key) || getEmptyUnreadInfo(); diff --git a/apps/meteor/client/views/navigation/hooks/useCollapsedGroups.ts b/apps/meteor/client/views/navigation/hooks/useCollapsedGroups.ts new file mode 100644 index 0000000000000..b178f5c5121c2 --- /dev/null +++ b/apps/meteor/client/views/navigation/hooks/useCollapsedGroups.ts @@ -0,0 +1,30 @@ +import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import type { KeyboardEvent } from 'react'; +import { useCallback } from 'react'; + +export const useCollapsedGroups = () => { + const [collapsedGroups, setCollapsedGroups] = useLocalStorage('sidebarGroups', []); + + const handleClick = useCallback( + (group: string) => { + if (collapsedGroups.includes(group)) { + setCollapsedGroups(collapsedGroups.filter((item) => item !== group)); + } else { + setCollapsedGroups([...collapsedGroups, group]); + } + }, + [collapsedGroups, setCollapsedGroups], + ); + + const handleKeyDown = useCallback( + (event: KeyboardEvent, group: string) => { + if (['Enter', 'Space'].includes(event.code)) { + event.preventDefault(); + handleClick(group); + } + }, + [handleClick], + ); + + return { collapsedGroups, handleClick, handleKeyDown }; +}; diff --git a/apps/meteor/client/views/navigation/hooks/useSidePanelFilters.ts b/apps/meteor/client/views/navigation/hooks/useSidePanelFilters.ts new file mode 100644 index 0000000000000..d695bd1c25ccd --- /dev/null +++ b/apps/meteor/client/views/navigation/hooks/useSidePanelFilters.ts @@ -0,0 +1,17 @@ +import { useEffectEvent, useLocalStorage } from '@rocket.chat/fuselage-hooks'; + +import type { SidePanelFiltersKeys, AllGroupsKeysWithUnread } from '../contexts/RoomsNavigationContext'; +import { SIDE_PANEL_GROUPS, getFilterKey } from '../contexts/RoomsNavigationContext'; + +export const useSidePanelFilters = () => { + const [currentFilter, setCurrentFilter] = useLocalStorage( + 'sidePanelFilters', + getFilterKey(SIDE_PANEL_GROUPS.ALL, false), + ); + + const setFilter = useEffectEvent((filter: SidePanelFiltersKeys, unread: boolean) => { + setCurrentFilter(getFilterKey(filter, unread)); + }); + + return { currentFilter, setFilter }; +}; diff --git a/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx b/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx new file mode 100644 index 0000000000000..2c89591cf52c9 --- /dev/null +++ b/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx @@ -0,0 +1,154 @@ +import { isOmnichannelRoom, type ILivechatInquiryRecord } from '@rocket.chat/core-typings'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts'; +import { useSetting, useUserPreference, useUserSubscriptions } from '@rocket.chat/ui-contexts'; +import type { ReactNode } from 'react'; +import { useMemo } from 'react'; +// import { useVideoConfIncomingCalls } from '@rocket.chat/ui-video-conf'; + +import { useOmnichannelEnabled } from '../../../hooks/omnichannel/useOmnichannelEnabled'; +import { useQueuedInquiries } from '../../../hooks/omnichannel/useQueuedInquiries'; +import { useSortQueryOptions } from '../../../hooks/useSortQueryOptions'; +import type { GroupedUnreadInfoData, AllGroupsKeys, AllGroupsKeysWithUnread } from '../contexts/RoomsNavigationContext'; +import { RoomsNavigationContext, SIDE_PANEL_GROUPS, SIDE_BAR_GROUPS, getEmptyUnreadInfo } from '../contexts/RoomsNavigationContext'; +import { useSidePanelFilters } from '../hooks/useSidePanelFilters'; + +const query = { open: { $ne: false } }; + +const emptyQueue: ILivechatInquiryRecord[] = []; + +export type useRoomsGroupsReturnType = { + sideBar: { + roomList: Array; + groupsCount: number[]; + groupsList: TranslationKey[]; + groupedUnreadInfo: GroupedUnreadInfoData[]; + }; +}; + +const updateGroupUnreadInfo = (room: SubscriptionWithRoom, current: GroupedUnreadInfoData): GroupedUnreadInfoData => { + return { + ...current, + userMentions: current.userMentions + (room.userMentions || 0), + groupMentions: current.groupMentions + (room.groupMentions || 0), + tunread: [...current.tunread, ...(room.tunread || [])], + tunreadUser: [...current.tunreadUser, ...(room.tunreadUser || [])], + unread: current.unread + (room.unread || 0), + }; +}; + +const isUnread = (room: SubscriptionWithRoom | ILivechatInquiryRecord) => + 'alert' in room && (room.alert || room.unread || room.tunread?.length) && !room.hideUnreadStatus; + +const hasMention = (room: SubscriptionWithRoom) => room.userMentions || room.groupMentions || room.tunreadUser || room.tunreadGroup; + +type GroupMap = Map>; +type UnreadGroupDataMap = Map; + +const useRoomsGroups = (): [GroupMap, UnreadGroupDataMap] => { + const showOmnichannel = useOmnichannelEnabled(); + // const sidebarGroupByType = useUserPreference('sidebarGroupByType'); + const favoritesEnabled = useUserPreference('sidebarShowFavorites'); + const isDiscussionEnabled = useSetting('Discussion_enabled'); + // const sidebarShowUnread = useUserPreference('sidebarShowUnread'); + + const options = useSortQueryOptions(); + + const rooms = useUserSubscriptions(query, options); + + const inquiries = useQueuedInquiries(); + + // const incomingCalls = useVideoConfIncomingCalls(); + + const queue = inquiries.enabled ? inquiries.queue : emptyQueue; + + return useDebouncedValue( + useMemo(() => { + const groups: GroupMap = new Map(); + showOmnichannel && groups.set(SIDE_PANEL_GROUPS.QUEUE, new Set(queue)); + + const unreadGroupData: UnreadGroupDataMap = new Map(); + + const setGroupRoom = (key: AllGroupsKeys, room: SubscriptionWithRoom | ILivechatInquiryRecord) => { + const getGroupSet = (key: AllGroupsKeysWithUnread) => { + const roomSet = groups.get(key) || new Set(); + if (!groups.has(key)) { + groups.set(key, roomSet); + } + return roomSet; + }; + + getGroupSet(key).add(room); + + if (isUnread(room)) { + getGroupSet(`${key}_unread`).add(room); + + const currentUnreadData = unreadGroupData.get(key) || getEmptyUnreadInfo(); + // TODO: Fix this type casting. We have to handle ILivechatInquiryRecord as well + const unreadInfo = updateGroupUnreadInfo(room as SubscriptionWithRoom, currentUnreadData); + unreadGroupData.set(key, unreadInfo); + } + }; + + rooms.forEach((room) => { + if (room.archived) { + return; + } + + if (favoritesEnabled && room.f) { + setGroupRoom(SIDE_PANEL_GROUPS.STARRED, room); + } + + if (room.teamMain) { + setGroupRoom(SIDE_PANEL_GROUPS.ALL, room); + return setGroupRoom(SIDE_BAR_GROUPS.TEAMS, room); + } + + if (isDiscussionEnabled && room.prid) { + setGroupRoom(SIDE_PANEL_GROUPS.DISCUSSIONS, room); + } + + if (hasMention(room)) { + setGroupRoom(SIDE_PANEL_GROUPS.MENTIONS, room); + } + + if ((room.t === 'c' || room.t === 'p') && !room.prid) { + setGroupRoom(SIDE_BAR_GROUPS.CHANNELS, room); + } + + if (isOmnichannelRoom(room) && showOmnichannel) { + room.onHold && setGroupRoom(SIDE_PANEL_GROUPS.ON_HOLD, room); + return setGroupRoom(SIDE_PANEL_GROUPS.IN_PROGRESS, room); + } + + if (room.t === 'd') { + setGroupRoom(SIDE_BAR_GROUPS.DIRECT_MESSAGES, room); + } + + setGroupRoom(SIDE_PANEL_GROUPS.ALL, room); + }); + + return [groups, unreadGroupData]; + }, [rooms, showOmnichannel, queue, favoritesEnabled, isDiscussionEnabled]), + 50, + ); +}; + +const RoomsNavigationContextProvider = ({ children }: { children: ReactNode }) => { + const { currentFilter, setFilter } = useSidePanelFilters(); + + const [groups, unreadGroupData] = useRoomsGroups(); + + const contextValue = useMemo(() => { + return { + currentFilter, + setFilter, + groups, + unreadGroupData, + }; + }, [currentFilter, setFilter, groups, unreadGroupData]); + + return {children}; +}; + +export default RoomsNavigationContextProvider; diff --git a/apps/meteor/client/views/room/RoomOpener.tsx b/apps/meteor/client/views/room/RoomOpener.tsx index 4fc72933022b2..842c5eb80e204 100644 --- a/apps/meteor/client/views/room/RoomOpener.tsx +++ b/apps/meteor/client/views/room/RoomOpener.tsx @@ -1,15 +1,15 @@ import type { RoomType } from '@rocket.chat/core-typings'; import { Box, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; -import { FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; +// import { FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import type { ReactElement } from 'react'; import { lazy, Suspense } from 'react'; import { useTranslation } from 'react-i18next'; import NotSubscribedRoom from './NotSubscribedRoom'; import RoomSkeleton from './RoomSkeleton'; -import RoomSidepanel from './Sidepanel/RoomSidepanel'; +// import RoomSidepanel from './Sidepanel/RoomSidepanel'; import { useOpenRoom } from './hooks/useOpenRoom'; -import { FeaturePreviewSidePanelNavigation } from '../../components/FeaturePreviewSidePanelNavigation'; +// import { FeaturePreviewSidePanelNavigation } from '../../components/FeaturePreviewSidePanelNavigation'; import { Header } from '../../components/Header'; import { getErrorMessage } from '../../lib/errorHandling'; import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError'; @@ -28,7 +28,7 @@ type RoomOpenerProps = { reference: string; }; -const isDirectOrOmnichannelRoom = (type: RoomType) => type === 'd' || type === 'l'; +// const isDirectOrOmnichannelRoom = (type: RoomType) => type === 'd' || type === 'l'; const RoomOpener = ({ type, reference }: RoomOpenerProps): ReactElement => { const { data, error, isSuccess, isError, isLoading } = useOpenRoom({ type, reference }); @@ -36,14 +36,14 @@ const RoomOpener = ({ type, reference }: RoomOpenerProps): ReactElement => { return ( - {!isDirectOrOmnichannelRoom(type) && ( + {/* {!isDirectOrOmnichannelRoom(type) && ( {null} - )} + )} */} }> {isLoading && } diff --git a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx index 7469c512f801d..633d06af9e7d7 100644 --- a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx +++ b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx @@ -8,7 +8,7 @@ import { useItemData } from '../hooks/useItemData'; type RoomSidepanelItemProps = { openedRoom?: string; room: SubscriptionWithRoom; - parentRid: string; + parentRid?: string; viewMode?: 'extended' | 'medium' | 'condensed'; }; diff --git a/apps/meteor/client/views/root/MainLayout/LayoutWithSidebarV2.tsx b/apps/meteor/client/views/root/MainLayout/LayoutWithSidebarV2.tsx index b7b5dc0ddbde2..b8469a072ea55 100644 --- a/apps/meteor/client/views/root/MainLayout/LayoutWithSidebarV2.tsx +++ b/apps/meteor/client/views/root/MainLayout/LayoutWithSidebarV2.tsx @@ -8,6 +8,8 @@ import AccessibilityShortcut from './AccessibilityShortcut'; import { MainLayoutStyleTags } from './MainLayoutStyleTags'; import NavBar from '../../../NavBarV2'; import Sidebar from '../../../sidebarv2'; +import SidePanel from '../../../sidepanel'; +import RoomsNavigationContextProvider from '../../navigation/providers/RoomsNavigationProvider'; const LayoutWithSidebarV2 = ({ children }: { children: ReactNode }): ReactElement => { const { isEmbedded: embeddedLayout } = useLayout(); @@ -50,7 +52,12 @@ const LayoutWithSidebarV2 = ({ children }: { children: ReactNode }): ReactElemen className={[embeddedLayout ? 'embedded-view' : undefined, 'menu-nav'].filter(Boolean).join(' ')} > - {!removeSidenav && } + {!removeSidenav && ( + + + + + )}
{ return ( <> - + {theme === 'dark' && } ); diff --git a/apps/meteor/tests/e2e/feature-preview.spec.ts b/apps/meteor/tests/e2e/feature-preview.spec.ts index bb2a3607fbe17..10fa648356adb 100644 --- a/apps/meteor/tests/e2e/feature-preview.spec.ts +++ b/apps/meteor/tests/e2e/feature-preview.spec.ts @@ -52,7 +52,7 @@ test.describe.serial('feature preview', () => { await expect(page.getByRole('button', { name: 'Navigation' })).toBeVisible(); }); - test.describe('Enhanced navigation', () => { + test.describe.skip('Enhanced navigation', () => { test.beforeAll(async ({ api }) => { await setUserPreferences(api, { featuresPreview: [ @@ -278,7 +278,7 @@ test.describe.serial('feature preview', () => { }); }); - test.describe('Sidepanel', () => { + test.describe.skip('Sidepanel', () => { test.beforeEach(async ({ api }) => { sidepanelTeam = await createTargetTeam(api, { sidepanel: { items: ['channels', 'discussions'] } });