Skip to content

Commit

Permalink
refactor: Omnichannel Department re-write (#28948)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandernsilva authored May 26, 2023
1 parent 6e2f78f commit 6a474ff
Show file tree
Hide file tree
Showing 26 changed files with 868 additions and 802 deletions.
6 changes: 6 additions & 0 deletions .changeset/mean-keys-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/meteor': minor
'@rocket.chat/rest-typings': patch
---

Refactored Omnichannel department pages to use best practices, also fixed existing bugs
23 changes: 14 additions & 9 deletions apps/meteor/client/hooks/useRoomsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ type RoomListOptions = {
text: string;
};

type IRoomClient = Pick<IRoom, '_updatedAt' | '_id'> & {
label: string;
value: string;
};

export const useRoomsList = (
options: RoomListOptions,
): {
itemsList: RecordList<IRoom>;
itemsList: RecordList<IRoomClient>;
initialItemCount: number;
reload: () => void;
loadMoreItems: (start: number, end: number) => void;
} => {
const [itemsList, setItemsList] = useState(() => new RecordList<IRoom>());
const reload = useCallback(() => setItemsList(new RecordList<IRoom>()), []);
const [itemsList, setItemsList] = useState(() => new RecordList<IRoomClient>());
const reload = useCallback(() => setItemsList(new RecordList<IRoomClient>()), []);

const getRooms = useEndpoint('GET', '/v1/rooms.autocomplete.channelAndPrivate.withPagination');

Expand All @@ -36,12 +41,12 @@ export const useRoomsList = (
sort: JSON.stringify({ name: 1 }),
});

const items = rooms.map((room: any) => {
room._updatedAt = new Date(room._updatedAt);
room.label = room.name;
room.value = room.name;
return room;
});
const items = rooms.map((room: any) => ({
_id: room._id,
_updatedAt: new Date(room._updatedAt),
label: room.name ?? '',
value: room.name ?? '',
}));

return {
items,
Expand Down
41 changes: 0 additions & 41 deletions apps/meteor/client/views/omnichannel/departments/AgentRow.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import DepartmentsTable from './DepartmentsTable';

const ArchivedDepartmentsPageWithData = (): ReactElement => {
const [text, setText] = useState('');
const [debouncedText = ''] = useDebouncedValue(text, 500);
const debouncedText = useDebouncedValue(text, 500) || '';

const pagination = usePagination();
const sort = useSort<'name' | 'email' | 'active'>('name');
Expand Down
30 changes: 0 additions & 30 deletions apps/meteor/client/views/omnichannel/departments/Count.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useState } from 'react';

import AutoCompleteAgent from '../../../components/AutoCompleteAgent';
import { useEndpointAction } from '../../../hooks/useEndpointAction';
import AutoCompleteAgent from '../../../../components/AutoCompleteAgent';
import { useEndpointAction } from '../../../../hooks/useEndpointAction';
import type { IDepartmentAgent } from '../EditDepartment';

function AddAgent({ agentList, setAgentsAdded, setAgentList, ...props }) {
function AddAgent({ agentList, onAdd }: { agentList: IDepartmentAgent[]; onAdd: (agent: IDepartmentAgent) => void }) {
const t = useTranslation();
const [userId, setUserId] = useState();

const [userId, setUserId] = useState('');

const getAgent = useEndpointAction('GET', '/v1/livechat/users/agent/:_id', { keys: { _id: userId } });

const dispatchToastMessage = useToastMessageDispatch();

const handleAgent = useMutableCallback((e) => setUserId(e));
Expand All @@ -18,19 +22,22 @@ function AddAgent({ agentList, setAgentsAdded, setAgentList, ...props }) {
if (!userId) {
return;
}
const { user } = await getAgent();

if (agentList.filter((e) => e.agentId === user._id).length === 0) {
setAgentList([{ ...user, agentId: user._id }, ...agentList]);
setUserId();
setAgentsAdded((agents) => [...agents, { agentId: user._id }]);
const {
user: { _id, username, name },
} = await getAgent();

if (!agentList.some(({ agentId }) => agentId === _id)) {
setUserId('');
onAdd({ agentId: _id, username: username ?? '', name, count: 0, order: 0 });
} else {
dispatchToastMessage({ type: 'error', message: t('This_agent_was_already_selected') });
}
});

return (
<Box display='flex' alignItems='center' {...props}>
<AutoCompleteAgent empty value={userId} onChange={handleAgent} />
<Box display='flex' alignItems='center'>
<AutoCompleteAgent value={userId} onChange={handleAgent} />
<Button disabled={!userId} onClick={handleSave} mis='x8' primary>
{t('Add')}
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Box } from '@rocket.chat/fuselage';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import React, { memo } from 'react';

import UserAvatar from '../../../../components/avatar/UserAvatar';

const AgentAvatar = ({ name, username, eTag }: { name: string; username: string; eTag?: string }) => {
const mediaQuery = useMediaQuery('(min-width: 1024px)');

return (
<Box display='flex' alignItems='center'>
<UserAvatar size={mediaQuery ? 'x28' : 'x40'} title={username} username={username} etag={eTag} />
<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>
);
};

export default memo(AgentAvatar);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NumberInput, TableCell, TableRow } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo } from 'react';
import type { UseFormRegister } from 'react-hook-form';

import type { FormValues, IDepartmentAgent } from '../EditDepartment';
import AgentAvatar from './AgentAvatar';
import RemoveAgentButton from './RemoveAgentButton';

type AgentRowProps = {
agent: IDepartmentAgent;
index: number;
register: UseFormRegister<FormValues>;
onRemove: (agentId: string) => void;
};

const AgentRow = ({ index, agent, register, onRemove }: AgentRowProps) => {
const t = useTranslation();

return (
<TableRow key={agent.agentId} tabIndex={0} role='link' action qa-user-id={agent.agentId}>
<TableCell withTruncatedText>
<AgentAvatar name={agent.name || ''} username={agent.username || ''} />
</TableCell>
<TableCell fontScale='p2' color='hint' withTruncatedText>
<NumberInput title={t('Count')} maxWidth='100%' {...register(`agentList.${index}.count`, { valueAsNumber: true })} />
</TableCell>
<TableCell fontScale='p2' color='hint' withTruncatedText>
<NumberInput title={t('Order')} maxWidth='100%' {...register(`agentList.${index}.order`, { valueAsNumber: true })} />
</TableCell>
<TableCell fontScale='p2' color='hint'>
<RemoveAgentButton agentId={agent.agentId} onRemove={onRemove} />
</TableCell>
</TableRow>
);
};
export default memo(AgentRow);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
import type { Control, UseFormRegister } from 'react-hook-form';
import { useWatch, useFieldArray } from 'react-hook-form';

import { GenericTable, GenericTableBody, GenericTableHeader, GenericTableHeaderCell } from '../../../../components/GenericTable';
import type { FormValues } from '../EditDepartment';
import AddAgent from './AddAgent';
import AgentRow from './AgentRow';

type DepartmentAgentsTableProps = {
control: Control<FormValues>;
register: UseFormRegister<FormValues>;
};

function DepartmentAgentsTable({ control, register }: DepartmentAgentsTableProps) {
const t = useTranslation();
const { fields, append, remove } = useFieldArray({ control, name: 'agentList' });
const agentList = useWatch({ control, name: 'agentList' });

return (
<>
<AddAgent agentList={agentList} data-qa='DepartmentSelect-AgentsTable' onAdd={append} />

<GenericTable>
<GenericTableHeader>
<GenericTableHeaderCell w='x200'>{t('Name')}</GenericTableHeaderCell>
<GenericTableHeaderCell w='x140'>{t('Count')}</GenericTableHeaderCell>
<GenericTableHeaderCell w='x120'>{t('Order')}</GenericTableHeaderCell>
<GenericTableHeaderCell w='x40'>{t('Remove')}</GenericTableHeaderCell>
</GenericTableHeader>

<GenericTableBody>
{fields.map((agent, index) => (
<AgentRow key={agent.id} index={index} agent={agent} register={register} onRemove={() => remove(index)} />
))}
</GenericTableBody>
</GenericTable>
</>
);
}

export default DepartmentAgentsTable;
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';

import GenericModal from '../../../components/GenericModal';
import GenericModal from '../../../../components/GenericModal';

function RemoveAgentButton({ agentId, setAgentList, agentList, setAgentsRemoved }) {
function RemoveAgentButton({ agentId, onRemove }: { agentId: string; onRemove: (agentId: string) => void }) {
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const t = useTranslation();

const handleDelete = useMutableCallback((e) => {
e.stopPropagation();
const onDeleteAgent = async () => {
const newList = agentList.filter((listItem) => listItem.agentId !== agentId);
setAgentList(newList);

const onRemoveAgent = async () => {
onRemove(agentId);
dispatchToastMessage({ type: 'success', message: t('Agent_removed') });
setModal();
setAgentsRemoved((agents) => [...agents, { agentId }]);
};

setModal(<GenericModal variant='danger' onConfirm={onDeleteAgent} onCancel={() => setModal()} confirmText={t('Delete')} />);
setModal(<GenericModal variant='danger' onConfirm={onRemoveAgent} onCancel={() => setModal()} confirmText={t('Delete')} />);
});

return <IconButton icon='trash' mini title={t('Remove')} onClick={handleDelete} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Button, Chip, Field, TextInput } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FormEvent } from 'react';
import React, { useCallback, useState } from 'react';

type DepartmentTagsProps = {
error: string;
value: string[];
onChange: (tags: string[]) => void;
};

export const DepartmentTags = ({ error, value: tags, onChange }: DepartmentTagsProps) => {
const t = useTranslation();
const [tagText, setTagText] = useState('');

const handleAddTag = useCallback(() => {
if (tags.includes(tagText)) {
return;
}

setTagText('');
onChange([...tags, tagText]);
}, [onChange, tagText, tags]);

const handleTagChipClick = (tag: string) => () => {
onChange(tags.filter((_tag) => _tag !== tag));
};

return (
<>
<Field.Row>
<TextInput
data-qa='DepartmentEditTextInput-ConversationClosingTags'
error={error}
placeholder={t('Enter_a_tag')}
value={tagText}
onChange={(e: FormEvent<HTMLInputElement>) => setTagText(e.currentTarget.value)}
/>
<Button
disabled={Boolean(!tagText.trim()) || tags.includes(tagText)}
data-qa='DepartmentEditAddButton-ConversationClosingTags'
mis='x8'
title={t('Add')}
onClick={handleAddTag}
>
{t('Add')}
</Button>
</Field.Row>

<Field.Hint>{t('Conversation_closing_tags_description')}</Field.Hint>

{tags?.length > 0 && (
<Field.Row justifyContent='flex-start'>
{tags.map((tag, i) => (
<Chip key={i} onClick={handleTagChipClick(tag)} mie='x8'>
{tag}
</Chip>
))}
</Field.Row>
)}
</>
);
};
Loading

0 comments on commit 6a474ff

Please sign in to comment.