Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/meteor/client/NavBarV2/NavBarPagesGroup/NavBarItemSort.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SidebarV2Action } from '@rocket.chat/fuselage';
import { GenericMenu } from '@rocket.chat/ui-client';
import type { HTMLAttributes } from 'react';
import { useTranslation } from 'react-i18next';

import { useSortMenu } from './hooks/useSortMenu';

type NavBarItemSortProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarItemSort = (props: NavBarItemSortProps) => {
const { t } = useTranslation();

const sections = useSortMenu();

return <GenericMenu icon='sort' sections={sections} title={t('Display')} selectionMode='multiple' is={SidebarV2Action} {...props} />;
};

export default NavBarItemSort;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import NavBarItemCreateNew from './NavBarItemCreateNew';
import NavBarItemDirectoryPage from './NavBarItemDirectoryPage';
import NavBarItemHomePage from './NavBarItemHomePage';
import NavBarItemMarketPlaceMenu from './NavBarItemMarketPlaceMenu';
import NavBarItemSort from './NavBarItemSort';
import NavBarPagesStackMenu from './NavBarPagesStackMenu';

const NavBarPagesGroup = () => {
Expand All @@ -26,6 +27,7 @@ const NavBarPagesGroup = () => {
</>
)}
{showMarketplace && !isMobile && <NavBarItemMarketPlaceMenu />}
{!isMobile && <NavBarItemSort />}
<NavBarItemCreateNew />
</NavBarGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { CheckBox } from '@rocket.chat/fuselage';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useEndpoint, useUserPreference } from '@rocket.chat/ui-contexts';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';

export const useGroupingListItems = (): GenericMenuItemProps[] => {
const { t } = useTranslation();

const sidebarGroupByType = useUserPreference<boolean>('sidebarGroupByType');
const sidebarShowUnread = useUserPreference<boolean>('sidebarShowUnread');

const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');

const useHandleChange = (key: 'sidebarGroupByType' | 'sidebarShowUnread', value: boolean): (() => void) =>
useCallback(() => saveUserPreferences({ data: { [key]: value } }), [key, value]);

const handleChangeGroupByType = useHandleChange('sidebarGroupByType', !sidebarGroupByType);
const handleChangeShowUnread = useHandleChange('sidebarShowUnread', !sidebarShowUnread);

return [
{
id: 'unread',
content: t('Unread'),
icon: 'flag',
addon: <CheckBox onChange={handleChangeShowUnread} checked={sidebarShowUnread} />,
},
{
id: 'types',
content: t('Types'),
icon: 'group-by-type',
addon: <CheckBox onChange={handleChangeGroupByType} checked={sidebarGroupByType} />,
},
];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { renderHook } from '@testing-library/react';

import { useGroupingListItems } from './useGroupingListItems';

it('should render groupingList items', async () => {
const { result } = renderHook(() => useGroupingListItems());

expect(result.current[0]).toEqual(
expect.objectContaining({
id: 'unread',
}),
);

expect(result.current[1]).toEqual(
expect.objectContaining({
id: 'types',
}),
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useTranslation } from 'react-i18next';

import { useGroupingListItems } from './useGroupingListItems';
import { useSortModeItems } from './useSortModeItems';

export const useSortMenu = () => {
const { t } = useTranslation();

const sortModeItems = useSortModeItems();
const groupingListItems = useGroupingListItems();

const sections = [
{ title: t('Sort_By'), items: sortModeItems },
{ title: t('Group_by'), items: groupingListItems },
];

return sections;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { renderHook } from '@testing-library/react';

import { useSortModeItems } from './useSortModeItems';

it('should render sortMode items', async () => {
const { result } = renderHook(() => useSortModeItems());

expect(result.current[0]).toEqual(
expect.objectContaining({
id: 'activity',
}),
);

expect(result.current[1]).toEqual(
expect.objectContaining({
id: 'name',
}),
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { RadioButton } from '@rocket.chat/fuselage';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useEndpoint, useUserPreference } from '@rocket.chat/ui-contexts';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';

import {
OmnichannelSortingDisclaimer,
useOmnichannelSortingDisclaimer,
} from '../../../components/Omnichannel/OmnichannelSortingDisclaimer';

export const useSortModeItems = (): GenericMenuItemProps[] => {
const { t } = useTranslation();

const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');
const sidebarSortBy = useUserPreference<'activity' | 'alphabetical'>('sidebarSortby', 'activity');
const isOmnichannelEnabled = useOmnichannelSortingDisclaimer();

const useHandleChange = (value: 'alphabetical' | 'activity'): (() => void) =>
useCallback(() => saveUserPreferences({ data: { sidebarSortby: value } }), [value]);

const setToAlphabetical = useHandleChange('alphabetical');
const setToActivity = useHandleChange('activity');

return [
{
id: 'activity',
content: t('Activity'),
icon: 'clock',
addon: <RadioButton onChange={setToActivity} checked={sidebarSortBy === 'activity'} />,
description: sidebarSortBy === 'activity' && isOmnichannelEnabled && <OmnichannelSortingDisclaimer />,
},
{
id: 'name',
content: t('Name'),
icon: 'sort-az',
addon: <RadioButton onChange={setToAlphabetical} checked={sidebarSortBy === 'alphabetical'} />,
description: sidebarSortBy === 'alphabetical' && isOmnichannelEnabled && <OmnichannelSortingDisclaimer />,
},
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ type GenericNoResultsProps = {
icon?: IconName | null;
title?: string;
description?: string;
buttonPrimary?: boolean;
} & LinkProps &
ButtonProps;

Expand All @@ -19,7 +18,6 @@ const GenericNoResults = ({
description,
buttonTitle,
buttonAction,
buttonPrimary = true,
linkHref,
linkText,
}: GenericNoResultsProps) => {
Expand All @@ -33,9 +31,7 @@ const GenericNoResults = ({
{description && <StatesSubtitle>{description}</StatesSubtitle>}
{buttonTitle && buttonAction && (
<StatesActions>
<StatesAction primary={buttonPrimary} onClick={buttonAction}>
{buttonTitle}
</StatesAction>
<StatesAction onClick={buttonAction}>{buttonTitle}</StatesAction>
</StatesActions>
)}
{linkText && linkHref && (
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/sidebarv2/RoomList/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const SidebarItem = ({ icon, title, actions, unread, menu, badges, room, ...prop
const handlePointerEnter = () => setMenuVisibility(true);

return (
<SidebarV2Item {...props} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
<SidebarV2Item {...props} title={title} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
<SidebarV2ItemAvatarWrapper>
<RoomAvatar size='x20' room={{ ...room, _id: room.rid || room._id, type: room.t }} />
</SidebarV2ItemAvatarWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type RoomListRowProps = {

const SidebarItemWithData = ({ room, id, style, t, videoConfActions }: RoomListRowProps) => {
const title = roomCoordinator.getRoomName(room.t, room) || '';
const href = roomCoordinator.getRouteLink(room.t, room) || '';

const { unreadTitle, unreadVariant, showUnread, unreadCount, highlightUnread: highlighted } = useUnreadDisplay(room);

const icon = (
Expand Down Expand Up @@ -85,17 +87,15 @@ const SidebarItemWithData = ({ room, id, style, t, videoConfActions }: RoomListR
switchSidePanelTab('channels', { parentRid: room.rid });
}, [room, switchSidePanelTab]);

const buttonProps = useButtonPattern((e) => {
e.preventDefault();
handleClick();
});
const buttonProps = useButtonPattern(handleClick);

return (
<SidebarItem
id={id}
data-qa='sidebar-item'
data-unread={highlighted}
unread={highlighted}
href={href}
selected={selected}
aria-label={showUnread ? t('__unreadTitle__from__roomTitle__', { unreadTitle, roomTitle: title }) : title}
title={title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const TeamCollabFilters = () => {
return (
<Box role='tablist' aria-label={t('Team_collaboration_filters')} aria-orientation='vertical' mbs={8}>
<RoomListFiltersItem group='all' icon={sidePanelFiltersConfig.all.icon} />
<RoomListFiltersItem group='mentions' icon={sidePanelFiltersConfig.mentions.icon} />
<RoomListFiltersItem group='favorites' icon={sidePanelFiltersConfig.favorites.icon} />
{isDiscussionEnabled && <RoomListFiltersItem group='discussions' icon={sidePanelFiltersConfig.discussions.icon} />}
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ILivechatInquiryRecord, IRoom } from '@rocket.chat/core-typings';
import type { ISubscription, ILivechatInquiryRecord, IRoom } from '@rocket.chat/core-typings';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { Keys as IconName } from '@rocket.chat/icons';
import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts';
Expand Down Expand Up @@ -48,15 +48,23 @@ export const sidePanelFiltersConfig: { [Key in AllGroupsKeys]: { title: Translat
title: 'Direct_Messages',
icon: 'at',
},
unread: {
title: 'Unread',
icon: 'flag',
},
conversations: {
title: 'Conversations',
icon: 'chat',
},
};

export type SidePanelFiltersKeys = 'all' | 'mentions' | 'favorites' | 'discussions' | 'inProgress' | 'queue' | 'onHold';

export const collapsibleFilters: SideBarFiltersKeys[] = ['teams', 'channels', 'directMessages'];
export const collapsibleFilters: SideBarFiltersKeys[] = ['unread', 'conversations', 'teams', 'channels', 'directMessages'];
export type SidePanelFiltersUnreadKeys = `${SidePanelFiltersKeys}_unread`;
export type SidePanelFilters = SidePanelFiltersKeys | SidePanelFiltersUnreadKeys;

export type SideBarFiltersKeys = 'teams' | 'channels' | 'directMessages';
export type SideBarFiltersKeys = 'teams' | 'channels' | 'directMessages' | 'conversations' | 'unread';
export type SideBarFiltersUnreadKeys = `${SideBarFiltersKeys}_unread`;
export type SideBarFilters = SidePanelFiltersKeys | SidePanelFiltersUnreadKeys;

Expand Down Expand Up @@ -156,16 +164,49 @@ export const useSideBarRoomsList = (): {
};
};

export const isUnreadSubscription = (subscription: Partial<ISubscription>) => {
if (subscription.hideUnreadStatus) {
return false;
}

return Boolean(
subscription.userMentions ||
subscription.groupMentions ||
subscription.tunread?.length ||
subscription.tunreadUser?.length ||
subscription.unread ||
subscription.alert,
);
};

export const useSidePanelRoomsListTab = (tab: AllGroupsKeys) => {
const [, unread] = useSidePanelFilter();
const roomSet = useRoomsListContext().groups.get(getFilterKey(tab, unread));
const roomSet = useRoomsListContext().groups.get(tab);

const roomsList = useMemo(() => {
if (!roomSet) {
return [];
}

return Array.from(roomSet);
}, [roomSet]);
if (!unread) {
return Array.from(roomSet);
}

return Array.from(roomSet)
.reduce(
(result, room) => {
if (isUnreadSubscription(room)) {
result[0].push(room);
return result;
}

result[1].push(room);
return result;
},
[[], []] as [Array<SubscriptionWithRoom | ILivechatInquiryRecord>, Array<SubscriptionWithRoom | ILivechatInquiryRecord>],
)
.flat();
}, [roomSet, unread]);
return roomsList;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { renderHook } from '@testing-library/react';

import { useSortQueryOptions } from './useSortQueryOptions';

it("should return query option to sort by last message when user preference is 'activity'", () => {
const { result } = renderHook(() => useSortQueryOptions(), {
wrapper: mockAppRoot().withUserPreference('sidebarSortby', 'activity').build(),
});
expect(result.current.sort).toHaveProperty('lm', -1);
});

it("should return query option to sort by name when user preference is 'name'", () => {
const { result } = renderHook(() => useSortQueryOptions(), {
wrapper: mockAppRoot().withUserPreference('sidebarSortby', 'name').build(),
});
expect(result.current.sort).toHaveProperty('lowerCaseName', 1);
});

it("should return query option to sort by fname when user preference is 'name' and showRealName is true", () => {
const { result } = renderHook(() => useSortQueryOptions(), {
wrapper: mockAppRoot().withUserPreference('sidebarSortby', 'name').withSetting('UI_Use_Real_Name', true).build(),
});
expect(result.current.sort).toHaveProperty('lowerCaseFName', 1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';

export const useSortQueryOptions = (): {
sort:
| {
lm?: -1 | 1 | undefined;
}
| {
lowerCaseFName: -1 | 1;
lm?: -1 | 1 | undefined;
}
| {
lowerCaseName: -1 | 1;
lm?: -1 | 1 | undefined;
};
} => {
const sortBy = useUserPreference('sidebarSortby');
const showRealName = useSetting('UI_Use_Real_Name');

return useMemo(
() => ({
sort: {
...(sortBy === 'activity' && { lm: -1 }),
...(sortBy !== 'activity' && {
...(showRealName ? { lowerCaseFName: 1 } : { lowerCaseName: 1 }),
}),
},
}),
[sortBy, showRealName],
);
};
Loading
Loading