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
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { DeviceManagementSession, DeviceManagementPopulatedSession, Serialized } from '@rocket.chat/core-typings';
import { Box, Pagination, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
import type { PaginatedResult } from '@rocket.chat/rest-typings';
import type { UseQueryResult } from '@tanstack/react-query';
import type { ComponentProps, ReactElement } from 'react';
import { useTranslation } from 'react-i18next';

import { AsyncStatePhase } from '../../../lib/asyncState';
import GenericNoResults from '../../GenericNoResults/GenericNoResults';
import { GenericTable, GenericTableHeader, GenericTableBody, GenericTableLoadingTable } from '../../GenericTable';

type DeviceManagementTableProps<T> = {
data?: Serialized<PaginatedResult<{ sessions: T[] }>>;
phase?: Partial<AsyncStatePhase>;
error?: Error;
reload?: () => void;
// FIXME: this tight coupling with the query result is not ideal; it indicates visual components should not be tightly
// coupled with data fetching logic.
type DeviceManagementTableProps<T> = UseQueryResult<PaginatedResult<{ sessions: Serialized<T>[] }>> & {
headers: (ReactElement | false)[];
renderRow: (data: Serialized<T>) => ReactElement;
current?: ComponentProps<typeof Pagination>['current'];
Expand All @@ -24,10 +22,12 @@ type DeviceManagementTableProps<T> = {

// TODO: Missing error state
const DeviceManagementTable = <T extends DeviceManagementSession | DeviceManagementPopulatedSession>({
data,
phase,
isPending,
isError,
error,
reload,
isSuccess,
data,
refetch,
headers,
renderRow,
current,
Expand All @@ -38,16 +38,16 @@ const DeviceManagementTable = <T extends DeviceManagementSession | DeviceManagem
}: DeviceManagementTableProps<T>): ReactElement => {
const { t } = useTranslation();

if (!data && phase === AsyncStatePhase.REJECTED) {
if (isError) {
return (
<Box display='flex' justifyContent='center' alignItems='center' height='100%'>
<States>
<StatesIcon name='warning' variation='danger' />
<StatesTitle>{t('Something_went_wrong')}</StatesTitle>
<StatesSubtitle>{t('We_Could_not_retrive_any_data')}</StatesSubtitle>
<StatesSubtitle>{error?.message}</StatesSubtitle>
<StatesSubtitle>{error.message}</StatesSubtitle>
<StatesActions>
<StatesAction onClick={reload}>{t('Retry')}</StatesAction>
<StatesAction onClick={refetch}>{t('Retry')}</StatesAction>
</StatesActions>
</States>
</Box>
Expand All @@ -56,15 +56,15 @@ const DeviceManagementTable = <T extends DeviceManagementSession | DeviceManagem

return (
<>
{data?.sessions.length === 0 && phase === AsyncStatePhase.RESOLVED && <GenericNoResults />}
{data?.sessions.length === 0 && isSuccess && <GenericNoResults />}
<GenericTable>
{data?.sessions && data.sessions.length > 0 && headers && <GenericTableHeader>{headers}</GenericTableHeader>}
<GenericTableBody>
{phase === AsyncStatePhase.LOADING && <GenericTableLoadingTable headerCells={headers.filter(Boolean).length} />}
{phase === AsyncStatePhase.RESOLVED && data?.sessions && data.sessions.map(renderRow)}
{isPending && <GenericTableLoadingTable headerCells={headers.filter(Boolean).length} />}
{isSuccess && data?.sessions && data.sessions.map(renderRow)}
</GenericTableBody>
</GenericTable>
{phase === AsyncStatePhase.RESOLVED && (
{isSuccess && (
<Pagination
divider
current={current}
Expand Down
58 changes: 0 additions & 58 deletions apps/meteor/client/hooks/useEndpointData.ts

This file was deleted.

77 changes: 76 additions & 1 deletion apps/meteor/client/lib/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import type { ILivechatDepartment, IMessage, IRoom, ITeam, IUser } from '@rocket.chat/core-typings';

export const roomsQueryKeys = {
all: ['rooms'] as const,
Expand All @@ -25,3 +25,78 @@ export const rolesQueryKeys = {
all: ['roles'] as const,
userRoles: () => [...rolesQueryKeys.all, 'user-roles'] as const,
};

export const omnichannelQueryKeys = {
all: ['omnichannel'] as const,
department: (id: string) => [...omnichannelQueryKeys.all, 'department', id] as const,
extensions: (
params:
| {
userId: string;
type: 'free' | 'allocated' | 'available';
}
| {
username: string;
type: 'free' | 'allocated' | 'available';
},
) => [...omnichannelQueryKeys.all, 'extensions', params] as const,
livechat: {
appearance: () => [...omnichannelQueryKeys.all, 'livechat', 'appearance'] as const,
customFields: () => [...omnichannelQueryKeys.all, 'livechat', 'custom-fields'] as const,
},
visitorInfo: (uid: string) => [...omnichannelQueryKeys.all, 'visitor-info', uid] as const,
analytics: {
all: (departmentId: ILivechatDepartment['_id']) => [...omnichannelQueryKeys.all, 'analytics', departmentId] as const,
agentsStatus: (departmentId: ILivechatDepartment['_id']) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'agents-status'] as const,
timings: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'timings', dateRange] as const,
chats: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'chats', dateRange] as const,
chatsPerAgent: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'chats-per-agent', dateRange] as const,
chatsPerDepartment: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'chats-per-department', dateRange] as const,
agentsProductivityTotals: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'agents-productivity', dateRange] as const,
chatsTotals: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'chats-totals', dateRange] as const,
conversationTotals: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'conversation-totals', dateRange] as const,
productivityTotals: (departmentId: ILivechatDepartment['_id'], dateRange: { start: string; end: string }) =>
[...omnichannelQueryKeys.analytics.all(departmentId), 'productivity-totals', dateRange] as const,
},
};

export const deviceManagementQueryKeys = {
all: ['device-management'] as const,
userSessions: (params: { sort?: string; count?: number; offset?: number }) =>
[...deviceManagementQueryKeys.all, 'users-sessions', params] as const,
sessions: (params: { sort?: string; count?: number; offset?: number }) =>
[...deviceManagementQueryKeys.all, 'all-users-sessions', params] as const,
sessionInfo: (sessionId: string) => [...deviceManagementQueryKeys.all, 'session-info', sessionId] as const,
};

export const miscQueryKeys = {
personalAccessTokens: ['personal-access-tokens'] as const,
lookup: (endpoint: string) => ['lookup', endpoint] as const,
autotranslateSupportedLanguages: (targetLanguage: string) => ['autotranslate', 'supported-languages', targetLanguage] as const,
};

export const voipQueryKeys = {
all: ['voip'] as const,
room: (rid: IRoom['_id'], token: string) => [...voipQueryKeys.all, 'room', rid, token] as const,
};

export const usersQueryKeys = {
all: ['users'] as const,
userInfo: ({ uid, username }: { uid?: IUser['_id']; username?: IUser['username'] }) =>
[...usersQueryKeys.all, 'info', { uid, username }] as const,
};

export const teamsQueryKeys = {
all: ['teams'] as const,
team: (teamId: ITeam['_id']) => [...teamsQueryKeys.all, teamId] as const,
roomsOfUser: (teamId: ITeam['_id'], userId: IUser['_id'], options?: { canUserDelete: boolean }) =>
[...teamsQueryKeys.team(teamId), 'rooms-of-user', userId, options] as const,
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import type { IOmnichannelCannedResponse, Serialized } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import CannedResponseEdit from './CannedResponseEdit';
import { FormSkeleton } from '../../components/Skeleton';
import { AsyncStatePhase } from '../../hooks/useAsyncState';
import { useEndpointData } from '../../hooks/useEndpointData';
import { omnichannelQueryKeys } from '../../lib/queryKeys';

const CannedResponseEditWithDepartmentData = ({ cannedResponseData }: { cannedResponseData: Serialized<IOmnichannelCannedResponse> }) => {
const departmentId = useMemo(() => cannedResponseData?.departmentId, [cannedResponseData]) as string;
const { value: departmentData, phase: state, error } = useEndpointData('/v1/livechat/department/:_id', { keys: { _id: departmentId } });

const getDepartment = useEndpoint('GET', '/v1/livechat/department/:_id', { _id: departmentId });
const {
data: departmentData,
isPending,
isError,
} = useQuery({
queryKey: omnichannelQueryKeys.department(departmentId),
queryFn: async () => {
const { department } = await getDepartment({});
return department;
},
});

const { t } = useTranslation();

if (state === AsyncStatePhase.LOADING) {
if (isPending) {
return <FormSkeleton />;
}

if (error) {
if (isError) {
return (
<Callout m={16} type='danger'>
{t('Not_Available')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -8,7 +10,7 @@ import { GenericTableHeaderCell } from '../../../../components/GenericTable';
import { usePagination } from '../../../../components/GenericTable/hooks/usePagination';
import { useSort } from '../../../../components/GenericTable/hooks/useSort';
import DeviceManagementTable from '../../../../components/deviceManagement/DeviceManagementTable';
import { useEndpointData } from '../../../../hooks/useEndpointData';
import { deviceManagementQueryKeys } from '../../../../lib/queryKeys';

const sortMapping = {
client: 'device.name',
Expand All @@ -30,7 +32,11 @@ const DeviceManagementAccountTable = (): ReactElement => {
[itemsPerPage, current, sortBy, sortDirection],
);

const { value: data, phase, error, reload } = useEndpointData('/v1/sessions/list', { params: query });
const listSessions = useEndpoint('GET', '/v1/sessions/list');
const queryResult = useQuery({
queryKey: deviceManagementQueryKeys.userSessions(query),
queryFn: () => listSessions(query),
});

const mediaQuery = useMediaQuery('(min-width: 1024px)');

Expand All @@ -53,10 +59,7 @@ const DeviceManagementAccountTable = (): ReactElement => {

return (
<DeviceManagementTable
data={data}
phase={phase}
error={error}
reload={reload}
{...queryResult}
headers={headers}
renderRow={(session): ReactElement => (
<DeviceManagementAccountRow
Expand All @@ -66,7 +69,7 @@ const DeviceManagementAccountTable = (): ReactElement => {
deviceType={session.device?.type}
deviceOSName={session.device?.os.name}
loginAt={session.loginAt}
onReload={reload}
onReload={queryResult.refetch}
/>
)}
current={current}
Expand Down
Loading
Loading