Skip to content

Commit

Permalink
[FEATURE] Omnichannel / Directory - Chats Tab (#19930)
Browse files Browse the repository at this point in the history
* [FEATURE] Directory page starting contacts/visitors tab.

* [IMPROVE] Removing unused variable.

* [IMPROVE] Directory link tooltip to just "Directory" instead "Omnichannel Directory"

* Creating omnichannel directory - chat tab page.

* Get logged user and passing to query.

* Navigate to conversation page on click at row.

* Missing translation of "Chat duration"

* Contact name column translation

* Sorting by "Closed At" column and adding tags below "Contact Name"

* Responsive tags in Chat tab

* Add permission to see chats tab and removing useless comment.

* changes after merge with develop

* after merge

* Revert "after merge"

This reverts commit 8e1860f.

* Fix blank line missing.

* showing chat tab.

Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>
  • Loading branch information
rafaelblink and renatobecker authored Dec 22, 2020
1 parent 27d990d commit a4221a4
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 0 deletions.
97 changes: 97 additions & 0 deletions client/omnichannel/directory/ChatTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useState, useMemo, useCallback } from 'react';
import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { Table, Tag, Box } from '@rocket.chat/fuselage';
import moment from 'moment';
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';

import { useTranslation } from '../../contexts/TranslationContext';
import { useEndpointData } from '../../hooks/useEndpointData';
import GenericTable from '../../components/GenericTable';
import FilterByText from '../../components/FilterByText';
import { usePermission } from '../../contexts/AuthorizationContext';
import NotAuthorizedPage from '../../components/NotAuthorizedPage';


const useQuery = ({ text, itemsPerPage, current }, [column, direction], userIdLoggedIn) => useMemo(() => ({
sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
open: false,
roomName: text,
agents: [userIdLoggedIn],
...itemsPerPage && { count: itemsPerPage },
...current && { offset: current },
}), [column, current, direction, itemsPerPage, userIdLoggedIn, text]);

const ChatTable = () => {
const [params, setParams] = useState({ text: '', current: 0, itemsPerPage: 25 });
const [sort, setSort] = useState(['closedAt', 'desc']);
const t = useTranslation();
const debouncedParams = useDebouncedValue(params, 500);
const debouncedSort = useDebouncedValue(sort, 500);
const userIdLoggedIn = Meteor.userId();
const query = useQuery(debouncedParams, debouncedSort, userIdLoggedIn);

const onHeaderClick = useMutableCallback((id) => {
const [sortBy, sortDirection] = sort;

if (sortBy === id) {
setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']);
return;
}
setSort([id, 'asc']);
});

const onRowClick = useMutableCallback((_id) => {
FlowRouter.go('live', { id: _id });
});

const { value: data } = useEndpointData('livechat/rooms', query) || {};

const header = useMemo(() => [
<GenericTable.HeaderCell key={'fname'} direction={sort[1]} active={sort[0] === 'fname'} onClick={onHeaderClick} sort='fname' w='x400'>{t('Contact_Name')}</GenericTable.HeaderCell>,
<GenericTable.HeaderCell key={'department'} direction={sort[1]} active={sort[0] === 'department'} onClick={onHeaderClick} sort='department' w='x200'>{t('Department')}</GenericTable.HeaderCell>,
<GenericTable.HeaderCell key={'ts'} direction={sort[1]} active={sort[0] === 'ts'} onClick={onHeaderClick} sort='ts' w='x200'>{t('Started_At')}</GenericTable.HeaderCell>,
<GenericTable.HeaderCell key={'chatDuration'} direction={sort[1]} active={sort[0] === 'chatDuration'} onClick={onHeaderClick} sort='chatDuration' w='x120'>{t('Chat_Duration')}</GenericTable.HeaderCell>,
<GenericTable.HeaderCell key={'closedAt'} direction={sort[1]} active={sort[0] === 'closedAt'} onClick={onHeaderClick} sort='closedAt' w='x200'>{t('Closed_At')}</GenericTable.HeaderCell>,
].filter(Boolean), [sort, onHeaderClick, t]);

const renderRow = useCallback(({ _id, fname, ts, closedAt, department, tags }) => <Table.Row key={_id} tabIndex={0} role='link' onClick={() => onRowClick(_id)} action qa-user-id={_id}>
<Table.Cell withTruncatedText>
<Box display='flex' flexDirection='column'>
<Box color='default' withTruncatedText>{fname}</Box>
{tags && <Box color='hint' display='flex' flex-direction='row'>
{tags.map((tag) => (
<Box style={{ marginTop: 4, whiteSpace: 'nowrap', overflow: tag.length > 10 ? 'hidden' : 'visible', textOverflow: 'ellipsis' }} key={tag} mie='x4'>
<Tag style={{ display: 'inline' }} disabled>{tag}</Tag>
</Box>
))}
</Box>}
</Box>
</Table.Cell>
<Table.Cell withTruncatedText>{department ? department.name : ''}</Table.Cell>
<Table.Cell withTruncatedText>{moment(ts).format('L LTS')}</Table.Cell>
<Table.Cell withTruncatedText>{moment(closedAt).from(moment(ts), true)}</Table.Cell>
<Table.Cell withTruncatedText>{moment(closedAt).format('L LTS')}</Table.Cell>
</Table.Row>, [onRowClick]);

return <GenericTable
header={header}
renderRow={renderRow}
results={data && data.rooms}
total={data && data.total}
setParams={setParams}
params={params}
renderFilter={({ onChange, ...props }) => <FilterByText onChange={onChange} {...props} />}
/>;
};


export default function ChatTab(props) {
const hasAccess = usePermission('view-l-room');

if (hasAccess) {
return <ChatTable {...props} />;
}

return <NotAuthorizedPage />;
}
2 changes: 2 additions & 0 deletions client/omnichannel/directory/OmnichannelDirectoryPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ContactTab from './ContactTab';
import VerticalBar from '../../components/VerticalBar';
import { ContactNewEdit, ContactEditWithData } from './ContactForm';
import { ContactInfo } from './ContactInfo';
import ChatTab from './ChatTab';


const OmnichannelDirectoryPage = () => {
Expand Down Expand Up @@ -63,6 +64,7 @@ const OmnichannelDirectoryPage = () => {
<Page.Content>
{
(tab === 'contacts' && <ContactTab setContactReload={setContactReload} />)
|| (tab === 'chats' && <ChatTab />)
}
</Page.Content>
</Page>
Expand Down
2 changes: 2 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@
"Chatpal_Current_Room_Only": "Same room",
"Chatpal_Default_Result_Type": "Default Result Type",
"Chatpal_Default_Result_Type_Description": "Defines which result type is shown by result. All means that an overview for all types is provided.",
"Chat_Duration": "Chat Duration",
"Chatpal_Email_Address": "Email Address",
"Chatpal_ERROR_Email_must_be_set": "Email must be set",
"Chatpal_ERROR_Email_must_be_valid": "Email must be valid",
Expand Down Expand Up @@ -841,6 +842,7 @@
"Consulting": "Consulting",
"Consumer_Goods": "Consumer Goods",
"Contact": "Contact",
"Contact_Name": "Contact Name",
"Contact_Chat_History": "Contact Chat History",
"Contains_Security_Fixes": "Contains Security Fixes",
"Contact_Manager": "Contact Manager",
Expand Down

0 comments on commit a4221a4

Please sign in to comment.