chore: Refactor RoomsNavigationContext and related components to improve type safety and enhance sidebar functionality

pull/37686/head
Guilherme Gazzo 5 months ago committed by Guilherme Gazzo
parent a70e67c762
commit d4df5e9dd5
  1. 17
      apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts
  2. 22
      apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx
  3. 71
      apps/meteor/client/views/navigation/sidepanel/SidePanel.tsx
  4. 8
      apps/meteor/client/views/navigation/sidepanel/SidePanelInquiry.tsx
  5. 85
      apps/meteor/client/views/navigation/sidepanel/SidePanelInternal.tsx
  6. 4
      apps/meteor/client/views/navigation/sidepanel/omnichannel/tabs/SidePanelQueue.tsx

@ -72,8 +72,15 @@ export type AllGroupsKeys = SidePanelFiltersKeys | SideBarFiltersKeys;
export type AllGroupsKeysWithUnread = SidePanelFilters | SideBarFiltersKeys | SideBarFiltersUnreadKeys;
export type RecordTypeBySidebarKey<K extends AllGroupsKeysWithUnread> = K extends 'queue' ? ILivechatInquiryRecord : SubscriptionWithRoom;
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface RoomsNavigationGroup extends Map<AllGroupsKeysWithUnread, Set<RecordTypeBySidebarKey<AllGroupsKeysWithUnread>>> {
get<K extends AllGroupsKeysWithUnread>(key: K): Set<RecordTypeBySidebarKey<K>> | undefined;
}
export type RoomsNavigationContextValue = {
groups: Map<AllGroupsKeysWithUnread, Set<SubscriptionWithRoom | ILivechatInquiryRecord>>;
groups: RoomsNavigationGroup;
currentFilter: AllGroupsKeysWithUnread;
setFilter: (filter: AllGroupsKeys, unread: boolean, parentRid?: IRoom['_id']) => void;
unreadGroupData: Map<AllGroupsKeys, GroupedUnreadInfoData>;
@ -164,7 +171,7 @@ export const useSideBarRoomsList = (): {
};
};
export const isUnreadSubscription = (subscription: Partial<ISubscription>) => {
export const isUnreadSubscription = (subscription: Partial<ISubscription>): boolean => {
if (subscription.hideUnreadStatus) {
return false;
}
@ -179,7 +186,7 @@ export const isUnreadSubscription = (subscription: Partial<ISubscription>) => {
);
};
export const useSidePanelRoomsListTab = (tab: AllGroupsKeys) => {
export const useSidePanelRoomsListTab = <K extends AllGroupsKeys>(tab: K): Array<RecordTypeBySidebarKey<K>> => {
const [, unread] = useSidePanelFilter();
const roomSet = useRoomsListContext().groups.get(tab);
@ -203,11 +210,11 @@ export const useSidePanelRoomsListTab = (tab: AllGroupsKeys) => {
result[1].push(room);
return result;
},
[[], []] as [Array<SubscriptionWithRoom | ILivechatInquiryRecord>, Array<SubscriptionWithRoom | ILivechatInquiryRecord>],
[[], []] as [Array<RecordTypeBySidebarKey<K>>, Array<RecordTypeBySidebarKey<K>>],
)
.flat();
}, [roomSet, unread]);
return roomsList;
return roomsList as Array<RecordTypeBySidebarKey<K>>;
};
export const useSidePanelFilter = (): [AllGroupsKeys, boolean, AllGroupsKeysWithUnread, (filter: AllGroupsKeysWithUnread) => void] => {

@ -1,12 +1,4 @@
import {
isDirectMessageRoom,
isDiscussion,
isLivechatInquiryRecord,
isOmnichannelRoom,
isPrivateRoom,
isPublicRoom,
isTeamRoom,
} from '@rocket.chat/core-typings';
import { isDirectMessageRoom, isDiscussion, isOmnichannelRoom, isPrivateRoom, isPublicRoom, isTeamRoom } from '@rocket.chat/core-typings';
import type { ILivechatInquiryRecord, IRoom } from '@rocket.chat/core-typings';
import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts';
@ -19,7 +11,12 @@ import { RoomManager } from '../../../lib/RoomManager';
import { Rooms } from '../../../stores';
import { useOmnichannelEnabled } from '../../omnichannel/hooks/useOmnichannelEnabled';
import { useQueuedInquiries } from '../../omnichannel/hooks/useQueuedInquiries';
import type { GroupedUnreadInfoData, AllGroupsKeys, AllGroupsKeysWithUnread } from '../contexts/RoomsNavigationContext';
import type {
GroupedUnreadInfoData,
AllGroupsKeys,
AllGroupsKeysWithUnread,
RoomsNavigationGroup,
} from '../contexts/RoomsNavigationContext';
import {
RoomsNavigationContext,
getEmptyUnreadInfo,
@ -56,10 +53,9 @@ const updateGroupUnreadInfo = (room: SubscriptionWithRoom, current: GroupedUnrea
const hasMention = (room: SubscriptionWithRoom) =>
room.userMentions || room.groupMentions || room.tunreadUser?.length || room.tunreadGroup?.length;
type GroupMap = Map<AllGroupsKeysWithUnread, Set<SubscriptionWithRoom | ILivechatInquiryRecord>>;
type UnreadGroupDataMap = Map<AllGroupsKeys, GroupedUnreadInfoData>;
const useRoomsGroups = (): [GroupMap, UnreadGroupDataMap] => {
const useRoomsGroups = (): [RoomsNavigationGroup, UnreadGroupDataMap] => {
const showOmnichannel = useOmnichannelEnabled();
const sidebarShowUnread = useUserPreference('sidebarShowUnread');
const sidebarGroupByType = useUserPreference('sidebarGroupByType');
@ -73,7 +69,7 @@ const useRoomsGroups = (): [GroupMap, UnreadGroupDataMap] => {
return useDebouncedValue(
useMemo(() => {
const groups: GroupMap = new Map();
const groups: RoomsNavigationGroup = new Map();
showOmnichannel && groups.set('queue', new Set(queue));
const unreadGroupData: UnreadGroupDataMap = new Map();

@ -1,73 +1,8 @@
import { isLivechatInquiryRecord, type ILivechatInquiryRecord } from '@rocket.chat/core-typings';
import { Box, IconButton, Sidepanel, SidepanelHeader, SidepanelHeaderTitle, SidepanelListItem, ToggleSwitch } from '@rocket.chat/fuselage';
import { VirtualizedScrollbars } from '@rocket.chat/ui-client';
import { useLayout, type SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { memo, useId, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Virtuoso } from 'react-virtuoso';
import { memo } from 'react';
import SidePanelNoResults from './SidePanelNoResults';
import { createSidePanel } from './SidePanelInternal';
import RoomSidePanelItem from './SidepanelItem/RoomSidePanelItem';
import SidepanelListWrapper from './SidepanelListWrapper';
import { useOpenedRoom } from '../../../lib/RoomManager';
import { usePreventDefault } from '../../../sidebarv2/hooks/usePreventDefault';
import { useIsRoomFilter, type AllGroupsKeys } from '../contexts/RoomsNavigationContext';
import InquireSidePanelItem from './omnichannel/InquireSidePanelItem';
type SidePanelProps = {
title: string;
currentTab: AllGroupsKeys;
unreadOnly: boolean;
toggleUnreadOnly: () => void;
rooms: (SubscriptionWithRoom | ILivechatInquiryRecord)[];
};
const SidePanel = ({ title, currentTab, unreadOnly, toggleUnreadOnly, rooms }: SidePanelProps) => {
const { t } = useTranslation();
const ref = useRef(null);
const unreadFieldId = useId();
const openedRoom = useOpenedRoom();
const {
isTablet,
sidePanel: { closeSidePanel },
} = useLayout();
const isRoomFilter = useIsRoomFilter();
usePreventDefault(ref);
return (
<Sidepanel role='tabpanel' aria-label={t('Side_panel')}>
<SidepanelHeader role='heading' aria-label={title}>
<Box withTruncatedText display='flex' alignItems='center'>
{isTablet && <IconButton mie={8} icon='arrow-back' title={t('Back')} small onClick={closeSidePanel} />}
<SidepanelHeaderTitle>{title}</SidepanelHeaderTitle>
</Box>
<Box display='flex' alignItems='center'>
<Box htmlFor={unreadFieldId} is='label' fontScale='c1' mie={8}>
{t('Unread')}
</Box>
<ToggleSwitch id={unreadFieldId} checked={unreadOnly} onChange={toggleUnreadOnly} />
</Box>
</SidepanelHeader>
<Box h='full' ref={ref}>
{rooms && rooms.length === 0 && <SidePanelNoResults currentTab={currentTab} />}
<VirtualizedScrollbars>
<Virtuoso
totalCount={rooms.length}
data={rooms}
components={{ Item: SidepanelListItem, List: SidepanelListWrapper }}
itemContent={(_, room) => {
if (isLivechatInquiryRecord(room)) {
return <InquireSidePanelItem openedRoom={openedRoom} room={room} />;
}
return <RoomSidePanelItem openedRoom={openedRoom} room={room} isRoomFilter={isRoomFilter} />;
}}
/>
</VirtualizedScrollbars>
</Box>
</Sidepanel>
);
};
const SidePanel = createSidePanel(RoomSidePanelItem);
export default memo(SidePanel);

@ -0,0 +1,8 @@
import { memo } from 'react';
import { createSidePanel } from './SidePanelInternal';
import InquireSidePanelItem from './omnichannel/InquireSidePanelItem';
const SidePanelInquiry = createSidePanel(InquireSidePanelItem);
export default memo(SidePanelInquiry);

@ -0,0 +1,85 @@
import { Box, IconButton, Sidepanel, SidepanelHeader, SidepanelHeaderTitle, SidepanelListItem, ToggleSwitch } from '@rocket.chat/fuselage';
import { VirtualizedScrollbars } from '@rocket.chat/ui-client';
import { useLayout } from '@rocket.chat/ui-contexts';
import { useId, useRef, type ComponentType } from 'react';
import { useTranslation } from 'react-i18next';
import { Virtuoso } from 'react-virtuoso';
import SidePanelNoResults from './SidePanelNoResults';
import SidepanelListWrapper from './SidepanelListWrapper';
import { useOpenedRoom } from '../../../lib/RoomManager';
import { usePreventDefault } from '../../../sidebarv2/hooks/usePreventDefault';
import { useIsRoomFilter, type AllGroupsKeys } from '../contexts/RoomsNavigationContext';
type SidePanelProps<R = any> = {
title: string;
currentTab: AllGroupsKeys;
unreadOnly: boolean;
toggleUnreadOnly: () => void;
rooms: R[];
ItemContentComponent: ComponentType<{
room: R;
openedRoom: ReturnType<typeof useOpenedRoom>;
isRoomFilter: boolean;
}>;
};
const SidePanelInternal = ({ title, currentTab, unreadOnly, toggleUnreadOnly, rooms, ItemContentComponent }: SidePanelProps) => {
const { t } = useTranslation();
const ref = useRef(null);
const unreadFieldId = useId();
const openedRoom = useOpenedRoom();
const {
isTablet,
sidePanel: { closeSidePanel },
} = useLayout();
const isRoomFilter = useIsRoomFilter();
usePreventDefault(ref);
return (
<Sidepanel role='tabpanel' aria-label={t('Side_panel')}>
<SidepanelHeader role='heading' aria-label={title}>
<Box withTruncatedText display='flex' alignItems='center'>
{isTablet && <IconButton mie={8} icon='arrow-back' title={t('Back')} small onClick={closeSidePanel} />}
<SidepanelHeaderTitle>{title}</SidepanelHeaderTitle>
</Box>
<Box display='flex' alignItems='center'>
<Box htmlFor={unreadFieldId} is='label' fontScale='c1' mie={8}>
{t('Unread')}
</Box>
<ToggleSwitch id={unreadFieldId} checked={unreadOnly} onChange={toggleUnreadOnly} />
</Box>
</SidepanelHeader>
<Box h='full' ref={ref}>
{rooms && rooms.length === 0 && <SidePanelNoResults currentTab={currentTab} />}
<VirtualizedScrollbars>
<Virtuoso
totalCount={rooms.length}
data={rooms}
components={{ Item: SidepanelListItem, List: SidepanelListWrapper }}
itemContent={(_, room) => {
return <ItemContentComponent room={room} openedRoom={openedRoom} isRoomFilter={isRoomFilter} />;
}}
/>
</VirtualizedScrollbars>
</Box>
</Sidepanel>
);
};
export const createSidePanel =
<R extends object>(
ItemContentComponent: ComponentType<{ room: R; openedRoom: ReturnType<typeof useOpenedRoom>; isRoomFilter: boolean }>,
) =>
// eslint-disable-next-line react/no-multi-comp, react/display-name
({ title, currentTab, unreadOnly, toggleUnreadOnly, rooms }: Omit<SidePanelProps<R>, 'ItemContentComponent'>) => (
<SidePanelInternal
title={title}
currentTab={currentTab}
unreadOnly={unreadOnly}
toggleUnreadOnly={toggleUnreadOnly}
rooms={rooms}
ItemContentComponent={ItemContentComponent}
/>
);

@ -7,7 +7,7 @@ import {
useSidePanelRoomsListTab,
useUnreadOnlyToggle,
} from '../../../contexts/RoomsNavigationContext';
import SidePanel from '../../SidePanel';
import SidePanelInquiry from '../../SidePanelInquiry';
const SidePanelQueue = () => {
const { t } = useTranslation();
@ -23,7 +23,7 @@ const SidePanelQueue = () => {
}
return (
<SidePanel
<SidePanelInquiry
title={t(sidePanelFiltersConfig.queue.title)}
currentTab='queue'
unreadOnly={unreadOnly}

Loading…
Cancel
Save