[IMPROVE] Rewrite Omnichannel Queue Page to React(#24176)
Co-authored-by: Kevin Aleman <kevin.aleman@rocket.chat>pull/23818/head^2
parent
c09cf332e5
commit
08a087e28e
@ -1,23 +0,0 @@ |
||||
<template name="livechatAutocompleteUser"> |
||||
<div class="rc-input" id='search-{{name}}' {{disabled}}> |
||||
<label class="rc-input__label"> |
||||
{{#if label}} |
||||
<div class="rc-input__title">{{_ label}}</div> |
||||
{{/if}} |
||||
<div class="rc-input__wrapper"> |
||||
{{# if icon}} |
||||
<div class="rc-input__icon"> |
||||
{{> icon block="rc-input__icon-svg" icon=icon}} |
||||
</div> |
||||
{{/if}} |
||||
<div class="rc-tags{{# unless icon}} rc-tags--no-icon{{/unless}}"> |
||||
{{#each item in list}} {{> tag item}} {{/each}} |
||||
<input type="text" id="{{name}}" class="rc-tags__input" placeholder="{{_ placeholder}}" name="{{name}}" autocomplete="off" |
||||
{{disabled}} /> |
||||
</div> |
||||
</div> |
||||
{{#with config}} {{#if autocomplete 'isShowing'}} {{#if autocomplete 'isLoaded'}} {{> popupList data=config items=items}} |
||||
{{/if}} {{/if}} {{/with}} |
||||
</label> |
||||
</div> |
||||
</template> |
||||
@ -1,118 +0,0 @@ |
||||
import { Blaze } from 'meteor/blaze'; |
||||
import { Template } from 'meteor/templating'; |
||||
import { ReactiveVar } from 'meteor/reactive-var'; |
||||
|
||||
import { AutoComplete } from '../../../../meteor-autocomplete/client'; |
||||
import './livechatAutocompleteUser.html'; |
||||
|
||||
Template.livechatAutocompleteUser.helpers({ |
||||
list() { |
||||
return this.list; |
||||
}, |
||||
items() { |
||||
return Template.instance().ac.filteredList(); |
||||
}, |
||||
config() { |
||||
const { filter } = Template.instance(); |
||||
const { noMatchTemplate, templateItem, modifier } = Template.instance().data; |
||||
return { |
||||
filter: filter.get(), |
||||
template_item: templateItem, |
||||
noMatchTemplate, |
||||
modifier(text) { |
||||
return modifier(filter, text); |
||||
}, |
||||
}; |
||||
}, |
||||
label() { |
||||
const instance = Template.instance(); |
||||
return instance.showLabel && instance.label; |
||||
}, |
||||
autocomplete(key) { |
||||
const instance = Template.instance(); |
||||
const param = instance.ac[key]; |
||||
return typeof param === 'function' ? param.apply(instance.ac) : param; |
||||
}, |
||||
}); |
||||
|
||||
Template.livechatAutocompleteUser.events({ |
||||
'input input'(e, t) { |
||||
const input = e.target; |
||||
const position = input.selectionEnd || input.selectionStart; |
||||
const { length } = input.value; |
||||
document.activeElement === input && e && /input/i.test(e.type) && (input.selectionEnd = position + input.value.length - length); |
||||
t.filter.set(input.value); |
||||
}, |
||||
'click .rc-popup-list__item'(e, t) { |
||||
t.ac.onItemClick(this, e); |
||||
}, |
||||
'keydown input'(e, t) { |
||||
t.ac.onKeyDown(e); |
||||
if ([8, 46].includes(e.keyCode) && e.target.value === '') { |
||||
const { deleteLastItem } = t; |
||||
return deleteLastItem && deleteLastItem(); |
||||
} |
||||
}, |
||||
'keyup input'(e, t) { |
||||
t.ac.onKeyUp(e); |
||||
}, |
||||
'focus input'(e, t) { |
||||
t.ac.onFocus(e); |
||||
}, |
||||
'blur input'(e, t) { |
||||
t.ac.onBlur(e); |
||||
}, |
||||
'click .rc-tags__tag'({ target }, t) { |
||||
const { onClickTag } = t; |
||||
return onClickTag && onClickTag(Blaze.getData(target)); |
||||
}, |
||||
}); |
||||
|
||||
Template.livechatAutocompleteUser.onRendered(function () { |
||||
const { name } = this.data; |
||||
|
||||
this.ac.element = this.firstNode.querySelector(`[name=${name}]`); |
||||
this.ac.$element = $(this.ac.element); |
||||
this.deleteLastItem = this.data.deleteLastItem; |
||||
}); |
||||
|
||||
Template.livechatAutocompleteUser.onCreated(function () { |
||||
this.filter = new ReactiveVar(''); |
||||
this.selected = new ReactiveVar([]); |
||||
this.onClickTag = this.data.onClickTag; |
||||
this.showLabel = this.data.showLabel; |
||||
this.label = this.data.label; |
||||
|
||||
const filter = {}; |
||||
this.autorun(() => { |
||||
const { exceptions, conditions } = Template.currentData(); |
||||
filter.exceptions = exceptions; |
||||
filter.conditions = conditions; |
||||
}); |
||||
|
||||
const { collection, endpoint, field, sort, onSelect, selector = (match) => ({ term: match }) } = this.data; |
||||
this.ac = new AutoComplete({ |
||||
selector: { |
||||
anchor: '.rc-input__label', |
||||
item: '.rc-popup-list__item', |
||||
container: '.rc-popup-list__list', |
||||
}, |
||||
onSelect, |
||||
position: 'fixed', |
||||
limit: 10, |
||||
inputDelay: 300, |
||||
rules: [ |
||||
{ |
||||
filter, |
||||
collection, |
||||
endpoint, |
||||
field, |
||||
matchAll: true, |
||||
doNotChangeWidth: false, |
||||
selector, |
||||
sort, |
||||
}, |
||||
], |
||||
}); |
||||
this.ac.tmplInst = this; |
||||
}); |
||||
@ -1,114 +0,0 @@ |
||||
<template name="livechatQueue"> |
||||
{{#if hasPermission}} |
||||
<fieldset> |
||||
<form class="form-inline" method="post"> |
||||
<div class="livechat-group-filters-wrapper"> |
||||
<div class="livechat-group-filters-container"> |
||||
<div class="livechat-queue-standard-filters"> |
||||
<div class="form-group"> |
||||
{{> livechatAutocompleteUser |
||||
onClickTag=onClickTagAgent |
||||
list=selectedAgents |
||||
onSelect=onSelectAgents |
||||
collection='UserAndRoom' |
||||
endpoint='users.autocomplete' |
||||
field='username' |
||||
sort='username' |
||||
label="Served_By" |
||||
placeholder="Served_By" |
||||
name="agent" |
||||
icon="at" |
||||
noMatchTemplate="userSearchEmpty" |
||||
templateItem="popupList_item_default" |
||||
modifier=agentModifier |
||||
showLabel=true |
||||
}} |
||||
</div> |
||||
<div class="form-group"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Status"}}</div> |
||||
<div class="rc-select"> |
||||
<select class="rc-select__element" name="agentStatus"> |
||||
<option class="rc-select__option" value="">{{_ "Online"}}</option> |
||||
<option class="rc-select__option" value="offline">{{_ "Include_Offline_Agents"}}</option> |
||||
</select> |
||||
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
||||
</div> |
||||
</label> |
||||
</div> |
||||
<div class="form-group"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Department"}}</div> |
||||
<div class="rc-select"> |
||||
<select class="rc-select__element" name="department"> |
||||
<option class="rc-select__option" value="">{{_ "Select_a_department"}}</option> |
||||
{{#each departments}} |
||||
<option class="rc-select__option" value="{{_id}}">{{name}}</option> |
||||
{{/each}} |
||||
</select> |
||||
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
||||
</div> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="livechat-group-filters-buttons"> |
||||
<div class="rc-button__group"> |
||||
<button class="rc-button rc-button--primary">{{_ "Filter"}}</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
</fieldset> |
||||
<div class="rc-table-content"> |
||||
{{#table fixed='true' onScroll=onTableScroll onResize=onTableResize onSort=onTableSort}} |
||||
<thead> |
||||
<tr> |
||||
<th width="25%"> |
||||
<div class="table-fake-th">{{_ "Served_By"}}</div> |
||||
</th> |
||||
<th width="25%"> |
||||
<div class="table-fake-th">{{_ "Department"}}</div> |
||||
</th> |
||||
<th width="25%"> |
||||
<div class="table-fake-th">{{_ "Total"}}</div> |
||||
</th> |
||||
<th width="25%"> |
||||
<div class="table-fake-th">{{_ "Status"}}</div> |
||||
</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{{#each queue}} |
||||
<tr class="rc-table-tr manage row-link" data-name="{{user.username}}"> |
||||
<td> |
||||
<div class="rc-table-wrapper"> |
||||
<div class="rc-table-avatar">{{> avatar username=user.username}}</div> |
||||
<div class="rc-table-info"> |
||||
<span class="rc-table-title">{{user.username}}</span> |
||||
</div> |
||||
</div> |
||||
</td> |
||||
<td>{{department.name}}</td> |
||||
<td>{{chats}}</td> |
||||
<td>{{user.status}}</td> |
||||
</tr> |
||||
{{/each}} |
||||
{{#if isLoading}} |
||||
<tr class="table-no-click"> |
||||
<td colspan="5" class="table-loading-td">{{> loading}}</td> |
||||
</tr> |
||||
{{/if}} |
||||
</tbody> |
||||
{{/table}} |
||||
</div> |
||||
{{#if hasMore}} |
||||
<div class="rc-button__group"> |
||||
<button class="rc-button rc-button--primary js-load-more">{{_ "Load_more"}}</button> |
||||
</div> |
||||
{{/if}} |
||||
{{else}} |
||||
<p>{{_ "You_are_not_authorized_to_view_this_page"}}</p> |
||||
{{/if}} |
||||
</template> |
||||
@ -1,125 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { ReactiveVar } from 'meteor/reactive-var'; |
||||
import { Template } from 'meteor/templating'; |
||||
|
||||
import { settings } from '../../../../settings'; |
||||
import { hasPermission } from '../../../../authorization'; |
||||
import { Users } from '../../../../models'; |
||||
import './livechatQueue.html'; |
||||
import { APIClient } from '../../../../utils/client'; |
||||
|
||||
const QUEUE_COUNT = 50; |
||||
|
||||
Template.livechatQueue.helpers({ |
||||
departments() { |
||||
return Template.instance() |
||||
.departments.get() |
||||
.filter((department) => department.enabled === true); |
||||
}, |
||||
onSelectAgents() { |
||||
return Template.instance().onSelectAgents; |
||||
}, |
||||
agentModifier() { |
||||
return (filter, text = '') => { |
||||
const f = filter.get(); |
||||
return `@${f.length === 0 ? text : text.replace(new RegExp(filter.get()), (part) => `<strong>${part}</strong>`)}`; |
||||
}; |
||||
}, |
||||
selectedAgents() { |
||||
return Template.instance().selectedAgents.get(); |
||||
}, |
||||
onClickTagAgent() { |
||||
return Template.instance().onClickTagAgent; |
||||
}, |
||||
queue() { |
||||
return Template.instance().queue.get(); |
||||
}, |
||||
isLoading() { |
||||
return Template.instance().isLoading.get(); |
||||
}, |
||||
hasPermission() { |
||||
const user = Users.findOne(Meteor.userId(), { fields: { statusLivechat: 1 } }); |
||||
return ( |
||||
hasPermission(Meteor.userId(), 'view-livechat-queue') || |
||||
(user.statusLivechat === 'available' && settings.get('Livechat_show_queue_list_link')) |
||||
); |
||||
}, |
||||
hasMore() { |
||||
const instance = Template.instance(); |
||||
const queue = instance.queue.get(); |
||||
return instance.total.get() > queue.length; |
||||
}, |
||||
}); |
||||
|
||||
Template.livechatQueue.events({ |
||||
'click .show-offline'(event, instance) { |
||||
const showOffline = instance.showOffline.get(); |
||||
|
||||
showOffline[this._id] = event.currentTarget.checked; |
||||
|
||||
instance.showOffline.set(showOffline); |
||||
}, |
||||
'submit form'(event, instance) { |
||||
event.preventDefault(); |
||||
instance.queue.set([]); |
||||
instance.offset.set(0); |
||||
|
||||
const filter = {}; |
||||
$(':input', event.currentTarget).each(function () { |
||||
if (!this.name) { |
||||
return; |
||||
} |
||||
|
||||
const value = $(this).val(); |
||||
|
||||
filter[this.name] = value; |
||||
}); |
||||
const agents = instance.selectedAgents.get(); |
||||
if (agents && agents.length > 0) { |
||||
filter.agent = agents[0]._id; |
||||
} |
||||
instance.filter.set(filter); |
||||
}, |
||||
'click .js-load-more'(event, instance) { |
||||
instance.offset.set(instance.offset.get() + QUEUE_COUNT); |
||||
}, |
||||
}); |
||||
|
||||
Template.livechatQueue.onCreated(async function () { |
||||
this.selectedAgents = new ReactiveVar([]); |
||||
this.departments = new ReactiveVar([]); |
||||
this.limit = new ReactiveVar(20); |
||||
this.filter = new ReactiveVar({}); |
||||
this.queue = new ReactiveVar([]); |
||||
this.isLoading = new ReactiveVar(true); |
||||
this.offset = new ReactiveVar(0); |
||||
this.total = new ReactiveVar(0); |
||||
|
||||
this.onSelectAgents = ({ item: agent }) => { |
||||
this.selectedAgents.set([agent]); |
||||
}; |
||||
|
||||
this.onClickTagAgent = ({ username }) => { |
||||
this.selectedAgents.set(this.selectedAgents.get().filter((user) => user.username !== username)); |
||||
}; |
||||
|
||||
this.autorun(async () => { |
||||
this.isLoading.set(true); |
||||
const filter = this.filter.get(); |
||||
const offset = this.offset.get(); |
||||
let query = `includeOfflineAgents=${filter.agentStatus === 'offline'}`; |
||||
if (filter.agent) { |
||||
query += `&agentId=${filter.agent}`; |
||||
} |
||||
if (filter.department) { |
||||
query += `&departmentId=${filter.department}`; |
||||
} |
||||
const { queue, total } = await APIClient.v1.get(`livechat/queue?${query}&count=${QUEUE_COUNT}&offset=${offset}`); |
||||
this.total.set(total); |
||||
this.queue.set(this.queue.get().concat(queue)); |
||||
this.isLoading.set(false); |
||||
}); |
||||
|
||||
const { departments } = await APIClient.v1.get('livechat/department?sort={"name": 1}'); |
||||
this.departments.set(departments); |
||||
}); |
||||
@ -0,0 +1,66 @@ |
||||
import { Box, Select, Label } from '@rocket.chat/fuselage'; |
||||
import { useMutableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks'; |
||||
import React, { Dispatch, FC, SetStateAction, useEffect } from 'react'; |
||||
|
||||
import AutoCompleteAgent from '../../../components/AutoCompleteAgent'; |
||||
import AutoCompleteDepartment from '../../../components/AutoCompleteDepartment'; |
||||
import { useTranslation } from '../../../contexts/TranslationContext'; |
||||
|
||||
type QueueListFilterPropsType = FC<{ |
||||
setFilter: Dispatch<SetStateAction<any>>; |
||||
}>; |
||||
|
||||
export const QueueListFilter: QueueListFilterPropsType = ({ setFilter, ...props }) => { |
||||
const t = useTranslation(); |
||||
|
||||
const statusOptions: [string, string][] = [ |
||||
['online', t('Online')], |
||||
['offline', t('Include_Offline_Agents')], |
||||
]; |
||||
|
||||
const [servedBy, setServedBy] = useLocalStorage('servedBy', 'all'); |
||||
const [status, setStatus] = useLocalStorage('status', 'online'); |
||||
const [department, setDepartment] = useLocalStorage<{ label: string; value: string }>('department', { value: 'all', label: t('All') }); |
||||
|
||||
const handleServedBy = useMutableCallback((e) => setServedBy(e)); |
||||
const handleStatus = useMutableCallback((e) => setStatus(e)); |
||||
const handleDepartment = useMutableCallback((e) => setDepartment(e)); |
||||
|
||||
const onSubmit = useMutableCallback((e) => e.preventDefault()); |
||||
|
||||
useEffect(() => { |
||||
const filters = { status } as { |
||||
status: string; |
||||
servedBy?: string; |
||||
departmentId?: string; |
||||
}; |
||||
|
||||
if (servedBy !== 'all') { |
||||
filters.servedBy = servedBy; |
||||
} |
||||
if (department?.value && department.value !== 'all') { |
||||
filters.departmentId = department.value; |
||||
} |
||||
|
||||
setFilter(filters); |
||||
}, [setFilter, servedBy, status, department]); |
||||
|
||||
return ( |
||||
<Box mb='x16' is='form' onSubmit={onSubmit} display='flex' flexDirection='column' {...props}> |
||||
<Box display='flex' flexDirection='row' flexWrap='wrap' {...props}> |
||||
<Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> |
||||
<Label mb='x4'>{t('Served_By')}</Label> |
||||
<AutoCompleteAgent haveAll value={servedBy} onChange={handleServedBy} /> |
||||
</Box> |
||||
<Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> |
||||
<Label mb='x4'>{t('Status')}</Label> |
||||
<Select flexShrink={0} options={statusOptions} value={status} onChange={handleStatus} placeholder={t('Status')} /> |
||||
</Box> |
||||
<Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> |
||||
<Label mb='x4'>{t('Department')}</Label> |
||||
<AutoCompleteDepartment haveAll value={department} onChange={handleDepartment} label={t('All')} onlyMyDepartments /> |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
); |
||||
}; |
||||
@ -0,0 +1,48 @@ |
||||
import React, { Dispatch, Key, ReactElement, ReactNode, SetStateAction } from 'react'; |
||||
|
||||
import GenericTable from '../../../components/GenericTable'; |
||||
import Page from '../../../components/Page'; |
||||
import { QueueListFilter } from './QueueListFilter'; |
||||
|
||||
type QueueListPagePropsParamsType = { |
||||
servedBy: string; |
||||
status: string; |
||||
departmentId: string; |
||||
itemsPerPage: number; |
||||
current: number; |
||||
}; |
||||
|
||||
type QueueListPagePropsType = { |
||||
title: string; |
||||
header: ReactNode; |
||||
data?: { |
||||
queue: { |
||||
chats: number; |
||||
department: { _id: string; name: string }; |
||||
user: { _id: string; username: string; status: string }; |
||||
}[]; |
||||
count: number; |
||||
offset: number; |
||||
total: number; |
||||
}; |
||||
params: QueueListPagePropsParamsType; |
||||
setParams: Dispatch<SetStateAction<QueueListPagePropsParamsType>>; |
||||
renderRow: (props: { _id?: Key }) => ReactElement; |
||||
}; |
||||
|
||||
export const QueueListPage = ({ title, header, data, renderRow, params, setParams }: QueueListPagePropsType): ReactElement => ( |
||||
<Page> |
||||
<Page.Header title={title} /> |
||||
<Page.Content> |
||||
<GenericTable |
||||
header={header} |
||||
renderFilter={({ onChange, ...props }: any): ReactElement => <QueueListFilter setFilter={onChange} {...props} />} |
||||
renderRow={renderRow} |
||||
results={data?.queue} |
||||
total={data?.total} |
||||
params={params} |
||||
setParams={setParams} |
||||
/> |
||||
</Page.Content> |
||||
</Page> |
||||
); |
||||
@ -0,0 +1,54 @@ |
||||
import { useMemo } from 'react'; |
||||
|
||||
import { ILivechatAgent } from '../../../../../definition/ILivechatAgent'; |
||||
import { ILivechatDepartment } from '../../../../../definition/ILivechatDepartment'; |
||||
|
||||
type useQueryType = ( |
||||
debouncedParams: { |
||||
servedBy: string; |
||||
status: string; |
||||
departmentId: ILivechatDepartment['_id']; |
||||
itemsPerPage: number; |
||||
current: number; |
||||
}, |
||||
debouncedSort: [string, 'asc' | 'desc'], |
||||
) => { |
||||
agentId?: ILivechatAgent['_id']; |
||||
includeOfflineAgents?: boolean; |
||||
departmentId?: ILivechatAgent['_id']; |
||||
offset: number; |
||||
count: number; |
||||
sort: string; |
||||
}; |
||||
|
||||
const sortDir = (sortDir: string): number => (sortDir === 'asc' ? 1 : -1); |
||||
|
||||
export const useQuery: useQueryType = ({ servedBy, status, departmentId, itemsPerPage, current }, [column, direction]) => |
||||
useMemo(() => { |
||||
const query: { |
||||
agentId?: string; |
||||
includeOflineAgents?: boolean; |
||||
departmentId?: string; |
||||
sort: string; |
||||
count: number; |
||||
offset: number; |
||||
} = { |
||||
sort: JSON.stringify({ |
||||
[column]: sortDir(direction), |
||||
}), |
||||
count: itemsPerPage, |
||||
offset: current, |
||||
}; |
||||
|
||||
if (status !== 'online') { |
||||
query.includeOflineAgents = true; |
||||
} |
||||
if (servedBy) { |
||||
query.agentId = servedBy; |
||||
} |
||||
if (departmentId) { |
||||
query.departmentId = departmentId; |
||||
} |
||||
|
||||
return query; |
||||
}, [column, direction, itemsPerPage, current, status, servedBy, departmentId]); |
||||
@ -0,0 +1,107 @@ |
||||
import { Box, Table } from '@rocket.chat/fuselage'; |
||||
import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
import React, { ReactElement, useCallback, useMemo, useState } from 'react'; |
||||
|
||||
import GenericTable from '../../../components/GenericTable'; |
||||
import UserAvatar from '../../../components/avatar/UserAvatar'; |
||||
import { useTranslation } from '../../../contexts/TranslationContext'; |
||||
import { useEndpointData } from '../../../hooks/useEndpointData'; |
||||
import { QueueListPage } from './QueueListPage'; |
||||
import { useQuery } from './hooks/useQuery'; |
||||
|
||||
const QueueList = (): ReactElement => { |
||||
const t = useTranslation(); |
||||
const [sort, setSort] = useState<[string, 'asc' | 'desc']>(['servedBy', 'desc']); |
||||
|
||||
const onHeaderClick = useMutableCallback((id) => { |
||||
const [sortBy, sortDirection] = sort; |
||||
|
||||
if (sortBy === id) { |
||||
setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); |
||||
return; |
||||
} |
||||
setSort([id, 'asc']); |
||||
}); |
||||
|
||||
const mediaQuery = useMediaQuery('(min-width: 1024px)'); |
||||
|
||||
const header = useMemo( |
||||
() => |
||||
[ |
||||
mediaQuery && ( |
||||
<GenericTable.HeaderCell |
||||
key={'servedBy'} |
||||
direction={sort[1]} |
||||
active={sort[0] === 'servedBy'} |
||||
onClick={onHeaderClick} |
||||
sort='servedBy' |
||||
> |
||||
{t('Served_By')} |
||||
</GenericTable.HeaderCell> |
||||
), |
||||
<GenericTable.HeaderCell |
||||
key={'department'} |
||||
direction={sort[1]} |
||||
active={sort[0] === 'departmend'} |
||||
onClick={onHeaderClick} |
||||
sort='department' |
||||
> |
||||
{t('Department')} |
||||
</GenericTable.HeaderCell>, |
||||
<GenericTable.HeaderCell key={'total'} direction={sort[1]} active={sort[0] === 'total'} onClick={onHeaderClick} sort='total'> |
||||
{t('Total')} |
||||
</GenericTable.HeaderCell>, |
||||
<GenericTable.HeaderCell key={'status'} direction={sort[1]} active={sort[0] === 'status'} onClick={onHeaderClick} sort='status'> |
||||
{t('Status')} |
||||
</GenericTable.HeaderCell>, |
||||
].filter(Boolean), |
||||
[mediaQuery, sort, onHeaderClick, t], |
||||
); |
||||
|
||||
const renderRow = useCallback( |
||||
({ user, department, chats }) => { |
||||
const getStatusText = (): string => { |
||||
if (user.status === 'online') { |
||||
return t('Online'); |
||||
} |
||||
|
||||
return t('Offline'); |
||||
}; |
||||
|
||||
return ( |
||||
<Table.Row key={user._id} tabIndex={0}> |
||||
<Table.Cell withTruncatedText> |
||||
<Box display='flex' alignItems='center' mb='5px'> |
||||
<UserAvatar size={mediaQuery ? 'x28' : 'x40'} username={user.username} /> |
||||
<Box display='flex' mi='x8'> |
||||
{user.username} |
||||
</Box> |
||||
</Box> |
||||
</Table.Cell> |
||||
<Table.Cell withTruncatedText>{department ? department.name : ''}</Table.Cell> |
||||
<Table.Cell withTruncatedText>{chats}</Table.Cell> |
||||
<Table.Cell withTruncatedText>{getStatusText()}</Table.Cell> |
||||
</Table.Row> |
||||
); |
||||
}, |
||||
[mediaQuery, t], |
||||
); |
||||
|
||||
const [params, setParams] = useState({ |
||||
servedBy: '', |
||||
status: '', |
||||
departmentId: '', |
||||
itemsPerPage: 25, |
||||
current: 0, |
||||
}); |
||||
const debouncedParams = useDebouncedValue(params, 500); |
||||
const debouncedSort = useDebouncedValue(sort, 500); |
||||
const query = useQuery(debouncedParams, debouncedSort); |
||||
const { value: data } = useEndpointData('livechat/queue', query); |
||||
|
||||
return ( |
||||
<QueueListPage title={t('Livechat_Queue')} header={header} data={data} renderRow={renderRow} params={params} setParams={setParams} /> |
||||
); |
||||
}; |
||||
|
||||
export default QueueList; |
||||
Loading…
Reference in new issue