Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Broken Omnichannel>Agents page #25731

Merged
merged 11 commits into from
Jun 7, 2022
7 changes: 3 additions & 4 deletions apps/meteor/client/views/omnichannel/agents/AddAgent.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { Button, Box, Field } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useState, FC } from 'react';
import React, { useState, ReactElement } from 'react';

import UserAutoComplete from '../../../components/UserAutoComplete';
import { useEndpointAction } from '../../../hooks/useEndpointAction';

type AddAgentProps = {
reload: () => void;
pi?: 'x24';
};

const AddAgent: FC<AddAgentProps> = ({ reload, ...props }) => {
const AddAgent = ({ reload }: AddAgentProps): ReactElement => {
const t = useTranslation();
const [username, setUsername] = useState('');

Expand All @@ -29,7 +28,7 @@ const AddAgent: FC<AddAgentProps> = ({ reload, ...props }) => {
setUsername(username);
});
return (
<Box display='flex' alignItems='center' {...props}>
<Box display='flex' alignItems='center' pi='24px'>
<Field>
<Field.Label>{t('Username')}</Field.Label>
<Field.Row>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { FC } from 'react';
import React, { ReactElement } from 'react';

import { FormSkeleton } from '../../../components/Skeleton';
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
Expand All @@ -12,7 +12,7 @@ type AgentEditWithDataProps = {
reload: () => void;
};

const AgentEditWithData: FC<AgentEditWithDataProps> = ({ uid, reload }) => {
const AgentEditWithData = ({ uid, reload }: AgentEditWithDataProps): ReactElement => {
const t = useTranslation();
const { value: data, phase: state, error } = useEndpointData(`livechat/users/agent/${uid}`);
const {
Expand All @@ -35,7 +35,7 @@ const AgentEditWithData: FC<AgentEditWithDataProps> = ({ uid, reload }) => {
}

if (error || userDepartmentsError || availableDepartmentsError || !data || !data.user) {
return <Box mbs='x16'>{t('User_not_found')}</Box>;
return <Box p='x16'>{t('User_not_found')}</Box>;
}

return <AgentEdit uid={uid} data={data} userDepartments={userDepartments} availableDepartments={availableDepartments} reset={reload} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useRouteParameter, useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import React, { VFC } from 'react';
import React, { ReactElement } from 'react';

import GenericModal from '../../../components/GenericModal';
import { useEndpointAction } from '../../../hooks/useEndpointAction';
import AgentInfo from './AgentInfo';

const AgentInfoActions: VFC<{
reload: () => void;
}> = ({ reload }) => {
const AgentInfoActions = ({ reload }: { reload: () => void }): ReactElement => {
const t = useTranslation();
const _id = useRouteParameter('id');
const agentsRoute = useRoute('omnichannel-agents');
Expand Down Expand Up @@ -48,8 +46,8 @@ const AgentInfoActions: VFC<{

return (
<>
<AgentInfo.Action key={t('Remove')} title={t('Remove')} label={t('Remove')} onClick={handleDelete} icon={'trash'} />,
<AgentInfo.Action key={t('Edit')} title={t('Edit')} label={t('Edit')} onClick={handleEditClick} icon={'edit'} />,
<AgentInfo.Action key={t('Remove')} title={t('Remove')} label={t('Remove')} onClick={handleDelete} icon={'trash'} />
<AgentInfo.Action key={t('Edit')} title={t('Edit')} label={t('Edit')} onClick={handleEditClick} icon={'edit'} />
</>
);
};
Expand Down
134 changes: 104 additions & 30 deletions apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,113 @@
import React, { FC, Key, ReactNode, ReactElement } from 'react';
import { Box, Pagination } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission, useRouteParameter, useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, useMemo, useState } from 'react';

import FilterByText from '../../../components/FilterByText';
import GenericTable from '../../../components/GenericTable';
import { GenericTableBody, GenericTableHeader, GenericTableHeaderCell, GenericTableLoadingTable } from '../../../components/GenericTable';
import { GenericTable } from '../../../components/GenericTable/V2/GenericTable';
import { usePagination } from '../../../components/GenericTable/hooks/usePagination';
import { useSort } from '../../../components/GenericTable/hooks/useSort';
import Page from '../../../components/Page';
import { useEndpointData } from '../../../hooks/useEndpointData';
import { AsyncStatePhase } from '../../../lib/asyncState';
import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage';
import AddAgent from './AddAgent';
import AgentsPageRow from './AgentsPageRow';
import AgentsTab from './AgentsTab';
import { useQuery } from './hooks/useQuery';

type AgentPageProps = {
reload: () => void;
data: any;
header: ReactNode;
setParams: (params: any) => void;
params: any;
title: string;
renderRow: (props: { _id?: Key }) => ReactElement;
};
const AgentsPage = (): ReactElement => {
const t = useTranslation();
const canViewAgents = usePermission('manage-livechat-agents');
const mediaQuery = useMediaQuery('(min-width: 1024px)');

const context = useRouteParameter('context');
const id = useRouteParameter('id');

const { sortBy, sortDirection, setSort } = useSort<'name' | 'username' | 'emails.address' | 'statusLivechat'>('name');
const [filter, setFilter] = useState('');
const debouncedFilter = useDebouncedValue(filter, 500);
const debouncedSort = useDebouncedValue(
useMemo(() => [sortBy, sortDirection], [sortBy, sortDirection]),
500,
) as ['name' | 'username' | 'emails.address' | 'statusLivechat', 'asc' | 'desc'];

const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination();

const AgentsPage: FC<AgentPageProps> = ({ data, reload, header, setParams, params, title, renderRow, children }) => (
<Page flexDirection='row'>
<Page>
<Page.Header title={title} />
<AddAgent reload={reload} pi='x24' />
<Page.Content>
<GenericTable
header={header}
renderRow={renderRow}
results={data?.users}
total={data?.total}
setParams={setParams}
params={params}
renderFilter={({ onChange, ...props }: any): any => <FilterByText setFilter={onChange} {...props} />}
/>
</Page.Content>
const query = useQuery({ text: debouncedFilter, current, itemsPerPage }, debouncedSort);
const { reload, ...result } = useEndpointData('livechat/users/agent', query);

const onHeaderClick = useMutableCallback((id) => {
if (sortBy === id) {
setSort(id, sortDirection === 'asc' ? 'desc' : 'asc');
return;
}
setSort(id, 'asc');
});

if (!canViewAgents) {
return <NotAuthorizedPage />;
}

return (
<Page flexDirection='row'>
<Page>
<Page.Header title={t('Agents')} />
<AddAgent reload={reload} />
<Box pi='24px'>
<FilterByText onChange={({ text }: { text: string }): void => setFilter(text)} />
</Box>
<Page.Content>
<GenericTable>
<GenericTableHeader>
<GenericTableHeaderCell direction={sortDirection} sort='name' active={sortBy === 'name'} onClick={onHeaderClick}>
{t('Name')}
</GenericTableHeaderCell>
{mediaQuery && (
<GenericTableHeaderCell direction={sortDirection} sort='username' active={sortBy === 'username'} onClick={onHeaderClick}>
{t('Username')}
</GenericTableHeaderCell>
)}
<GenericTableHeaderCell
direction={sortDirection}
sort='emails.address'
active={sortBy === 'emails.address'}
onClick={onHeaderClick}
>
{t('Email')}
</GenericTableHeaderCell>
<GenericTableHeaderCell
direction={sortDirection}
sort='statusLivechat'
active={sortBy === 'statusLivechat'}
onClick={onHeaderClick}
>
{t('Livechat_status')}
</GenericTableHeaderCell>
<GenericTableHeaderCell w='x60'>{t('Remove')}</GenericTableHeaderCell>
</GenericTableHeader>
<GenericTableBody>
{result.phase === AsyncStatePhase.LOADING && <GenericTableLoadingTable headerCells={4} />}
{result.phase === AsyncStatePhase.RESOLVED &&
result.value.users.map((user) => <AgentsPageRow key={user._id} user={user} mediaQuery={mediaQuery} reload={reload} />)}
</GenericTableBody>
</GenericTable>
{result.phase === AsyncStatePhase.RESOLVED && (
<Pagination
current={current}
itemsPerPage={itemsPerPage}
count={result.value.count}
onSetItemsPerPage={setItemsPerPage}
onSetCurrent={setCurrent}
{...paginationProps}
/>
)}
</Page.Content>
</Page>
{context && id && <AgentsTab reload={reload} context={context} id={id} />}
</Page>
{children}
</Page>
);
);
};

export default AgentsPage;
62 changes: 62 additions & 0 deletions apps/meteor/client/views/omnichannel/agents/AgentsPageRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Box } from '@rocket.chat/fuselage';
import { useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, useCallback } from 'react';

import { GenericTableRow, GenericTableCell } from '../../../components/GenericTable';
import UserAvatar from '../../../components/avatar/UserAvatar';
import RemoveAgentButton from './RemoveAgentButton';

const AgentsPageRow = ({
user: { _id, name, username, avatarETag, emails, statusLivechat },
mediaQuery,
reload,
}: {
user: { _id: string; name?: string; username?: string; avatarETag?: string; emails?: { address: string }[]; statusLivechat: string };
mediaQuery: boolean;
reload: () => void;
}): ReactElement => {
const t = useTranslation();
const agentsRoute = useRoute('omnichannel-agents');

const onRowClick = useCallback(() => {
agentsRoute.push({
context: 'info',
id: _id,
});
}, [_id, agentsRoute]);

return (
<GenericTableRow action onClick={onRowClick}>
<GenericTableCell>
<Box display='flex' alignItems='center'>
{username && <UserAvatar size={mediaQuery ? 'x28' : 'x40'} title={username} username={username} etag={avatarETag} />}
<Box display='flex' withTruncatedText mi='x8'>
<Box display='flex' flexDirection='column' alignSelf='center' withTruncatedText>
<Box fontScale='p2m' withTruncatedText color='default'>
{name || username}
</Box>
{!mediaQuery && name && (
<Box fontScale='p2' color='hint' withTruncatedText>
{`@${username}`}
</Box>
)}
</Box>
</Box>
</Box>
</GenericTableCell>
{mediaQuery && (
<GenericTableCell>
<Box fontScale='p2m' withTruncatedText color='hint'>
{username}
</Box>
<Box mi='x4' />
</GenericTableCell>
)}
<GenericTableCell withTruncatedText>{emails?.length && emails[0].address}</GenericTableCell>
<GenericTableCell withTruncatedText>{statusLivechat === 'available' ? t('Available') : t('Not_Available')}</GenericTableCell>
<RemoveAgentButton _id={_id} reload={reload} />
</GenericTableRow>
);
};

export default AgentsPageRow;
Loading