Skip to content
Snippets Groups Projects
Unverified Commit 2c4cb351 authored by Tiago Evangelista Pinto's avatar Tiago Evangelista Pinto Committed by GitHub
Browse files

Chore: Rewrite some Omnichannel files to TypeScript (#25359)

parent b99a2946
No related branches found
No related tags found
No related merge requests found
Showing
with 372 additions and 44 deletions
...@@ -74,28 +74,25 @@ API.v1.addRoute( ...@@ -74,28 +74,25 @@ API.v1.addRoute(
'livechat/department/:_id', 'livechat/department/:_id',
{ authRequired: true }, { authRequired: true },
{ {
get() { async get() {
check(this.urlParams, { check(this.urlParams, {
_id: String, _id: String,
}); });
const { onlyMyDepartments } = this.queryParams; const { onlyMyDepartments } = this.queryParams;
const { department, agents } = Promise.await( const { department, agents } = await findDepartmentById({
findDepartmentById({ userId: this.userId,
userId: this.userId, departmentId: this.urlParams._id,
departmentId: this.urlParams._id, includeAgents: this.queryParams.includeAgents && this.queryParams.includeAgents === 'true',
includeAgents: this.queryParams.includeAgents && this.queryParams.includeAgents === 'true', onlyMyDepartments: onlyMyDepartments === 'true',
onlyMyDepartments: onlyMyDepartments === 'true', });
}),
);
// TODO: return 404 when department is not found // TODO: return 404 when department is not found
// Currently, FE relies on the fact that this endpoint returns an empty payload // Currently, FE relies on the fact that this endpoint returns an empty payload
// to show the "new" view. Returning 404 breaks it // to show the "new" view. Returning 404 breaks it
const result = { department, agents };
return API.v1.success(result); return API.v1.success({ department, agents });
}, },
put() { put() {
const permissionToSave = hasPermission(this.userId, 'manage-livechat-departments'); const permissionToSave = hasPermission(this.userId, 'manage-livechat-departments');
......
import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage'; import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { useState } from 'react'; import React, { ChangeEvent, ReactElement, useState } from 'react';
import { useSubscription } from 'use-subscription'; import { useSubscription } from 'use-subscription';
import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
...@@ -10,30 +10,48 @@ import { useEndpointData } from '../../hooks/useEndpointData'; ...@@ -10,30 +10,48 @@ import { useEndpointData } from '../../hooks/useEndpointData';
import { formsSubscription } from '../../views/omnichannel/additionalForms'; import { formsSubscription } from '../../views/omnichannel/additionalForms';
import { FormSkeleton } from './Skeleton'; import { FormSkeleton } from './Skeleton';
const Tags = ({ tags = [], handler = () => {}, error = '', tagRequired = false }) => { const Tags = ({
const { value: tagsResult = [], phase: stateTags } = useEndpointData('livechat/tags.list'); tags,
handler,
error,
tagRequired,
}: {
tags?: string[];
handler: (value: string[]) => void;
error?: string;
tagRequired?: boolean;
}): ReactElement => {
const t = useTranslation(); const t = useTranslation();
const forms = useSubscription(formsSubscription); const forms = useSubscription<any>(formsSubscription);
const { useCurrentChatTags = () => {} } = forms; const { value: tagsResult, phase: stateTags } = useEndpointData('livechat/tags.list');
const Tags = useCurrentChatTags();
const { useCurrentChatTags } = forms;
const EETagsComponent = useCurrentChatTags();
const dispatchToastMessage = useToastMessageDispatch(); const dispatchToastMessage = useToastMessageDispatch();
const [tagValue, handleTagValue] = useState(''); const [tagValue, handleTagValue] = useState('');
const [paginatedTagValue, handlePaginatedTagValue] = useState(tags); const [paginatedTagValue, handlePaginatedTagValue] = useState<{ label: string; value: string }[]>();
const removeTag = (tag) => { const removeTag = (tagToRemove: string): void => {
const tagsFiltered = tags.filter((tagArray) => tagArray !== tag); if (tags) {
handler(tagsFiltered); const tagsFiltered = tags.filter((tag: string) => tag !== tagToRemove);
handler(tagsFiltered);
}
}; };
const handleTagTextSubmit = useMutableCallback(() => { const handleTagTextSubmit = useMutableCallback(() => {
if (!tags) {
return;
}
if (!tagValue || tagValue.trim() === '') { if (!tagValue || tagValue.trim() === '') {
dispatchToastMessage({ type: 'error', message: t('Enter_a_tag') }); dispatchToastMessage({ type: 'error', message: t('Enter_a_tag') });
handleTagValue(''); handleTagValue('');
return; return;
} }
if (tags.includes(tagValue)) { if (tags.includes(tagValue)) {
dispatchToastMessage({ type: 'error', message: t('Tag_already_exists') }); dispatchToastMessage({ type: 'error', message: t('Tag_already_exists') });
return; return;
...@@ -46,18 +64,16 @@ const Tags = ({ tags = [], handler = () => {}, error = '', tagRequired = false } ...@@ -46,18 +64,16 @@ const Tags = ({ tags = [], handler = () => {}, error = '', tagRequired = false }
return <FormSkeleton />; return <FormSkeleton />;
} }
const { tags: tagsList } = tagsResult;
return ( return (
<> <>
<Field.Label required={tagRequired} mb='x4'> <Field.Label required={tagRequired} mb='x4'>
{t('Tags')} {t('Tags')}
</Field.Label> </Field.Label>
{Tags && tagsList && tagsList.length > 0 ? ( {EETagsComponent && tagsResult?.tags && tagsResult?.tags.length ? (
<Field.Row> <Field.Row>
<Tags <EETagsComponent
value={paginatedTagValue} value={paginatedTagValue}
handler={(tags) => { handler={(tags: { label: string; value: string }[]): void => {
handler(tags.map((tag) => tag.label)); handler(tags.map((tag) => tag.label));
handlePaginatedTagValue(tags); handlePaginatedTagValue(tags);
}} }}
...@@ -68,19 +84,19 @@ const Tags = ({ tags = [], handler = () => {}, error = '', tagRequired = false } ...@@ -68,19 +84,19 @@ const Tags = ({ tags = [], handler = () => {}, error = '', tagRequired = false }
<Field.Row> <Field.Row>
<TextInput <TextInput
error={error} error={error}
value={tagValue?.value ? tagValue.value : tagValue} value={tagValue}
onChange={(event) => handleTagValue(event.target.value)} onChange={({ currentTarget }: ChangeEvent<HTMLInputElement>): void => handleTagValue(currentTarget.value)}
flexGrow={1} flexGrow={1}
placeholder={t('Enter_a_tag')} placeholder={t('Enter_a_tag')}
/> />
<Button disabled={!tagValue} mis='x8' title={t('add')} onClick={handleTagTextSubmit}> <Button disabled={!tagValue} mis='x8' title={t('Add')} onClick={handleTagTextSubmit}>
{t('Add')} {t('Add')}
</Button> </Button>
</Field.Row> </Field.Row>
<Field.Row justifyContent='flex-start'> <Field.Row justifyContent='flex-start'>
{tags.map((tag, i) => ( {tags?.map((tag, i) => (
<Chip key={i} onClick={() => removeTag(tag)} mie='x8'> <Chip key={i} onClick={(): void => removeTag(tag)} mie='x8'>
{tag?.value ? tag.value : tag} {tag}
</Chip> </Chip>
))} ))}
</Field.Row> </Field.Row>
......
import { ILivechatDepartment } from '@rocket.chat/core-typings';
import { Field, Button, TextInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage'; import { Field, Button, TextInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage';
import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; import React, { useCallback, useState, useEffect, ReactElement, useMemo } from 'react';
import React, { useCallback, useState, useMemo, useEffect } from 'react'; import { useForm } from 'react-hook-form';
import { useSetting } from '../../../contexts/SettingsContext'; import { useSetting } from '../../../contexts/SettingsContext';
import { useTranslation } from '../../../contexts/TranslationContext'; import { useTranslation } from '../../../contexts/TranslationContext';
import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate';
import { useForm } from '../../../hooks/useForm';
import GenericModal from '../../GenericModal'; import GenericModal from '../../GenericModal';
import Tags from '../Tags'; import Tags from '../Tags';
const CloseChatModal = ({ department = {}, onCancel, onConfirm }) => { const CloseChatModal = ({
department,
onCancel,
onConfirm,
}: {
department?: ILivechatDepartment | null;
onCancel: () => void;
onConfirm: (comment?: string, tags?: string[]) => Promise<void>;
}): ReactElement => {
const t = useTranslation(); const t = useTranslation();
const inputRef = useAutoFocus(true); const {
formState: { errors },
handleSubmit,
register,
setError,
setFocus,
setValue,
watch,
} = useForm();
const { values, handlers } = useForm({ comment: '', tags: [] }); const commentRequired = useSetting('Livechat_request_comment_when_closing_conversation') as boolean;
const [tagRequired, setTagRequired] = useState(false);
const commentRequired = useSetting('Livechat_request_comment_when_closing_conversation'); const tags = watch('tags');
const comment = watch('comment');
const { comment, tags } = values; const handleTags = (value: string[]): void => {
const { handleComment, handleTags } = handlers; setValue('tags', value);
const [commentError, setCommentError] = useState(''); };
const [tagError, setTagError] = useState('');
const [tagRequired, setTagRequired] = useState(false); const onSubmit = useCallback(
({ comment, tags }): void => {
if (!comment && commentRequired) {
setError('comment', { type: 'custom', message: t('The_field_is_required', t('Comment')) });
}
if (!tags?.length && tagRequired) {
setError('tags', { type: 'custom', message: t('error-tags-must-be-assigned-before-closing-chat') });
}
const handleConfirm = useCallback(() => { if (!errors.comment || errors.tags) {
onConfirm(comment, tags); onConfirm(comment, tags);
}, [comment, onConfirm, tags]); }
},
[commentRequired, tagRequired, errors, setError, t, onConfirm],
);
useComponentDidUpdate(() => { const cannotSubmit = useMemo(() => {
setCommentError(!comment && commentRequired ? t('The_field_is_required', t('Comment')) : ''); const cannotSendTag = (tagRequired && !tags?.length) || errors.tags;
}, [commentRequired, comment, t]); const cannotSendComment = (commentRequired && !comment) || errors.comment;
return cannotSendTag || cannotSendComment;
}, [comment, commentRequired, errors, tagRequired, tags]);
const canConfirm = useMemo(() => { useEffect(() => {
const canConfirmTag = !tagError && (tagRequired ? tags.length > 0 : true); if (department?.requestTagBeforeClosingChat) {
const canConfirmComment = !commentError && (commentRequired ? !!comment : true); setTagRequired(true);
return canConfirmTag && canConfirmComment; }
}, [comment, commentError, commentRequired, tagError, tagRequired, tags.length]); }, [department]);
useEffect(() => { useEffect(() => {
department?.requestTagBeforeClosingChat && setTagRequired(true); if (commentRequired) {
setTagError(tagRequired && (!tags || tags.length === 0) ? t('error-tags-must-be-assigned-before-closing-chat') : ''); setFocus('comment');
}, [department, tagRequired, t, tags]); }
}, [commentRequired, setFocus]);
if (!commentRequired && !tagRequired) { useEffect(() => {
return ( if (tagRequired) {
<GenericModal register('tags');
variant='warning' }
title={t('Are_you_sure_you_want_to_close_this_chat')} }, [register, tagRequired]);
onConfirm={handleConfirm}
onCancel={onCancel}
onClose={onCancel}
confirmText={t('Confirm')}
></GenericModal>
);
}
return ( return commentRequired || tagRequired ? (
<Modal> <Modal is='form' onSubmit={handleSubmit(onSubmit)}>
<Modal.Header> <Modal.Header>
<Icon name='baloon-close-top-right' size={20} /> <Icon name='baloon-close-top-right' size={20} />
<Modal.Title>{t('Closing_chat')}</Modal.Title> <Modal.Title>{t('Closing_chat')}</Modal.Title>
...@@ -68,33 +92,33 @@ const CloseChatModal = ({ department = {}, onCancel, onConfirm }) => { ...@@ -68,33 +92,33 @@ const CloseChatModal = ({ department = {}, onCancel, onConfirm }) => {
<Field marginBlock='x15'> <Field marginBlock='x15'>
<Field.Label required={commentRequired}>{t('Comment')}</Field.Label> <Field.Label required={commentRequired}>{t('Comment')}</Field.Label>
<Field.Row> <Field.Row>
<TextInput <TextInput {...register('comment')} error={errors.comment} flexGrow={1} placeholder={t('Please_add_a_comment')} />
ref={inputRef}
error={commentError}
flexGrow={1}
value={comment}
onChange={handleComment}
placeholder={t('Please_add_a_comment')}
/>
</Field.Row> </Field.Row>
<Field.Error>{commentError}</Field.Error> <Field.Error>{errors.comment?.message}</Field.Error>
</Field>
<Field>
<Tags tagRequired={tagRequired} tags={tags} handler={handleTags} />
<Field.Error>{errors.tags?.message}</Field.Error>
</Field> </Field>
{Tags && (
<Field>
<Tags tagRequired={tagRequired} tags={tags} handler={handleTags} error={tagError} />
<Field.Error>{tagError}</Field.Error>
</Field>
)}
</Modal.Content> </Modal.Content>
<Modal.Footer> <Modal.Footer>
<ButtonGroup align='end'> <ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button> <Button onClick={onCancel}>{t('Cancel')}</Button>
<Button disabled={!canConfirm} primary onClick={handleConfirm}> <Button type='submit' disabled={cannotSubmit} primary>
{t('Confirm')} {t('Confirm')}
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>
) : (
<GenericModal
variant='warning'
title={t('Are_you_sure_you_want_to_close_this_chat')}
onConfirm={onConfirm}
onCancel={onCancel}
onClose={onCancel}
confirmText={t('Confirm')}
></GenericModal>
); );
}; };
......
import React from 'react'; import { ILivechatDepartment, ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import React, { ReactElement } from 'react';
import { AsyncStatePhase } from '../../../hooks/useAsyncState'; import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData'; import { useEndpointData } from '../../../hooks/useEndpointData';
import { FormSkeleton } from '../Skeleton'; import { FormSkeleton } from '../Skeleton';
import CloseChatModal from './CloseChatModal'; import CloseChatModal from './CloseChatModal';
const CloseChatModalData = ({ departmentId, onCancel, onConfirm }) => { const CloseChatModalData = ({
const { value: data, phase: state } = useEndpointData(`livechat/department/${departmentId}?includeAgents=false`); departmentId,
onCancel,
onConfirm,
}: {
departmentId: ILivechatDepartment['_id'];
onCancel: () => void;
onConfirm: (comment?: string, tags?: string[]) => Promise<void>;
}): ReactElement => {
const { value: data, phase: state } = useEndpointData(`livechat/department/${departmentId}`);
if ([state].includes(AsyncStatePhase.LOADING)) { if ([state].includes(AsyncStatePhase.LOADING)) {
return <FormSkeleton />; return <FormSkeleton />;
} }
const { department } = data || {};
return <CloseChatModal onCancel={onCancel} onConfirm={onConfirm} department={department} />;
};
// TODO: chapter day: fix issue with rest typing
// TODO: This is necessary because of a weird problem
// There is an endpoint livechat/department/${departmentId}/agents
// that is causing the problem. type A | type B | undefined
return (
<CloseChatModal
onCancel={onCancel}
onConfirm={onConfirm}
department={
(
data as {
department: ILivechatDepartment | null;
agents?: ILivechatDepartmentAgents[];
}
).department
}
/>
);
};
export default CloseChatModalData; export default CloseChatModalData;
import { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { Field, Button, TextAreaInput, Icon, ButtonGroup, Modal, Box, PaginatedSelectFiltered } from '@rocket.chat/fuselage'; import { Field, Button, TextAreaInput, Icon, ButtonGroup, Modal, Box, PaginatedSelectFiltered } from '@rocket.chat/fuselage';
import { useMutableCallback, useAutoFocus, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { useEffect, useMemo, useState } from 'react'; import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useEndpoint } from '../../../contexts/ServerContext'; import { useEndpoint } from '../../../contexts/ServerContext';
import { useTranslation } from '../../../contexts/TranslationContext'; import { useTranslation } from '../../../contexts/TranslationContext';
import { useRecordList } from '../../../hooks/lists/useRecordList'; import { useRecordList } from '../../../hooks/lists/useRecordList';
import { AsyncStatePhase } from '../../../hooks/useAsyncState'; import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useForm } from '../../../hooks/useForm';
import UserAutoComplete from '../../UserAutoComplete'; import UserAutoComplete from '../../UserAutoComplete';
import { useDepartmentsList } from '../hooks/useDepartmentsList'; import { useDepartmentsList } from '../hooks/useDepartmentsList';
import ModalSeparator from './ModalSeparator'; import ModalSeparator from './ModalSeparator';
const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => { const ForwardChatModal = ({
onForward,
onCancel,
room,
...props
}: {
onForward: (departmentId?: string, userId?: string, comment?: string) => Promise<void>;
onCancel: () => void;
room: IOmnichannelRoom;
}): ReactElement => {
const t = useTranslation(); const t = useTranslation();
const getUserData = useEndpoint('GET', 'users.info');
const inputRef = useAutoFocus(true); const { getValues, handleSubmit, register, setFocus, setValue, watch } = useForm();
const { values, handlers } = useForm({ useEffect(() => {
username: '', setFocus('comment');
comment: '', }, [setFocus]);
department: {},
});
const { username, comment, department } = values;
const [userId, setUserId] = useState('');
const { handleUsername, handleComment, handleDepartment } = handlers;
const getUserData = useEndpoint('GET', `users.info?username=${username}`);
const [departmentsFilter, setDepartmentsFilter] = useState(''); const department = watch('department');
const username = watch('username');
const [departmentsFilter, setDepartmentsFilter] = useState<string | number | undefined>('');
const debouncedDepartmentsFilter = useDebouncedValue(departmentsFilter, 500); const debouncedDepartmentsFilter = useDebouncedValue(departmentsFilter, 500);
const { itemsList: departmentsList, loadMoreItems: loadMoreDepartments } = useDepartmentsList( const { itemsList: departmentsList, loadMoreItems: loadMoreDepartments } = useDepartmentsList(
useMemo(() => ({ filter: debouncedDepartmentsFilter, enabled: true }), [debouncedDepartmentsFilter]), useMemo(() => ({ filter: debouncedDepartmentsFilter as string, enabled: true }), [debouncedDepartmentsFilter]),
); );
const { phase: departmentsPhase, items: departments, itemCount: departmentsTotal } = useRecordList(departmentsList);
const hasDepartments = useMemo(() => departments && departments.length > 0, [departments]);
const { phase: departmentsPhase, items: departmentsItems, itemCount: departmentsTotal } = useRecordList(departmentsList); const _id = { $ne: room.servedBy?._id };
const conditions = { _id, status: { $ne: 'offline' }, statusLivechat: 'available' };
const handleSend = useMutableCallback(() => {
onForward(department?.value, userId, comment);
}, [onForward, department.value, userId, comment]);
const onChangeUsername = useMutableCallback((username) => {
handleUsername(username);
});
useEffect(() => {
if (!username) {
return;
}
const fetchData = async () => {
const { user } = await getUserData();
setUserId(user._id);
};
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [username]);
const canForward = department || username;
const departments = departmentsItems; const endReached = useCallback(
(start) => {
if (departmentsPhase === AsyncStatePhase.LOADING) {
loadMoreDepartments(start, Math.min(50, departmentsTotal));
}
},
[departmentsPhase, departmentsTotal, loadMoreDepartments],
);
const hasDepartments = departments && departments.length > 0; const onSubmit = useCallback(
async ({ department: departmentId, username, comment }) => {
let uid;
const { servedBy: { _id: agentId } = {} } = room || {}; if (username) {
const { user } = await getUserData({ userName: username });
uid = user?._id;
}
const _id = agentId && { $ne: agentId }; onForward(departmentId, uid, comment);
},
[getUserData, onForward],
);
const conditions = { _id, status: { $ne: 'offline' }, statusLivechat: 'available' }; useEffect(() => {
register('department');
register('username');
}, [register]);
return ( return (
<Modal {...props}> <Modal {...props} is='form' onSubmit={handleSubmit(onSubmit)}>
<Modal.Header> <Modal.Header>
<Icon name='baloon-arrow-top-right' size={20} /> <Icon name='baloon-arrow-top-right' size={20} />
<Modal.Title>{t('Forward_chat')}</Modal.Title> <Modal.Title>{t('Forward_chat')}</Modal.Title>
...@@ -80,34 +85,37 @@ const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => { ...@@ -80,34 +85,37 @@ const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => {
<Field mbe={'x30'}> <Field mbe={'x30'}>
<Field.Label>{t('Forward_to_department')}</Field.Label> <Field.Label>{t('Forward_to_department')}</Field.Label>
<Field.Row> <Field.Row>
<PaginatedSelectFiltered {
withTitle // TODO: Definitions on fuselage are incorrect, need to be fixed!
filter={departmentsFilter} // @ts-ignore-next-line
setFilter={setDepartmentsFilter} <PaginatedSelectFiltered
options={departmentsItems} withTitle
value={department} filter={departmentsFilter as string}
maxWidth='100%' setFilter={setDepartmentsFilter}
placeholder={t('Select_an_option')} options={departments.map(({ _id, name }) => ({ value: _id, label: name }))}
onChange={handleDepartment} maxWidth='100%'
flexGrow={1} placeholder={t('Select_an_option')}
endReached={ onChange={(value: string): void => {
departmentsPhase === AsyncStatePhase.LOADING setValue('department', value);
? () => {} }}
: (start) => loadMoreDepartments(start, Math.min(50, departmentsTotal)) flexGrow={1}
} endReached={endReached}
/> />
}
</Field.Row> </Field.Row>
</Field> </Field>
<ModalSeparator text={t('or')} /> <ModalSeparator text={t('or')} />
<Field mbs={hasDepartments && 'x30'}> <Field {...(hasDepartments && { mbs: 'x30' })}>
<Field.Label>{t('Forward_to_user')}</Field.Label> <Field.Label>{t('Forward_to_user')}</Field.Label>
<Field.Row> <Field.Row>
<UserAutoComplete <UserAutoComplete
conditions={conditions} conditions={conditions}
flexGrow={1} flexGrow={1}
value={username}
onChange={onChangeUsername}
placeholder={t('Username')} placeholder={t('Username')}
onChange={(value: string): void => {
setValue('username', value);
}}
value={getValues().username}
/> />
</Field.Row> </Field.Row>
</Field> </Field>
...@@ -119,14 +127,14 @@ const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => { ...@@ -119,14 +127,14 @@ const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => {
</Box> </Box>
</Field.Label> </Field.Label>
<Field.Row> <Field.Row>
<TextAreaInput ref={inputRef} rows={8} flexGrow={1} value={comment} onChange={handleComment} /> <TextAreaInput {...register('comment')} rows={8} flexGrow={1} />
</Field.Row> </Field.Row>
</Field> </Field>
</Modal.Content> </Modal.Content>
<Modal.Footer> <Modal.Footer>
<ButtonGroup align='end'> <ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button> <Button onClick={onCancel}>{t('Cancel')}</Button>
<Button disabled={!canForward} primary onClick={handleSend}> <Button type='submit' disabled={!username && !department} primary>
{t('Forward')} {t('Forward')}
</Button> </Button>
</ButtonGroup> </ButtonGroup>
......
...@@ -71,7 +71,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => { ...@@ -71,7 +71,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => {
const { useCurrentChatTags = (): void => undefined } = forms; const { useCurrentChatTags = (): void => undefined } = forms;
const Tags = useCurrentChatTags(); const EETagsComponent = useCurrentChatTags();
const onSubmit = useMutableCallback((e) => e.preventDefault()); const onSubmit = useMutableCallback((e) => e.preventDefault());
const reducer = function (acc: any, curr: string): any { const reducer = function (acc: any, curr: string): any {
...@@ -151,11 +151,11 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => { ...@@ -151,11 +151,11 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => {
<AutoCompleteDepartment haveAll value={department} onChange={handleDepartment} label={t('All')} onlyMyDepartments /> <AutoCompleteDepartment haveAll value={department} onChange={handleDepartment} label={t('All')} onlyMyDepartments />
</Box> </Box>
</Box> </Box>
{Tags && ( {EETagsComponent && (
<Box display='flex' flexDirection='row' marginBlockStart='x8' {...props}> <Box display='flex' flexDirection='row' marginBlockStart='x8' {...props}>
<Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'>
<Label mb='x4'>{t('Tags')}</Label> <Label mb='x4'>{t('Tags')}</Label>
<Tags value={tags} handler={handleTags} /> <EETagsComponent value={tags} handler={handleTags} />
</Box> </Box>
</Box> </Box>
)} )}
......
...@@ -143,11 +143,9 @@ function RoomEdit({ room, visitor, reload, reloadInfo, close }) { ...@@ -143,11 +143,9 @@ function RoomEdit({ room, visitor, reload, reloadInfo, close }) {
<TextInput flexGrow={1} value={topic} onChange={handleTopic} /> <TextInput flexGrow={1} value={topic} onChange={handleTopic} />
</Field.Row> </Field.Row>
</Field> </Field>
{Tags && ( <Field>
<Field> <Tags tags={tags} handler={handleTags} />
<Tags tags={tags} handler={handleTags} /> </Field>
</Field>
)}
{PrioritiesSelect && priorities && priorities.length > 0 && ( {PrioritiesSelect && priorities && priorities.length > 0 && (
<PrioritiesSelect value={priorityId} label={t('Priority')} options={priorities} handler={handlePriorityId} /> <PrioritiesSelect value={priorityId} label={t('Priority')} options={priorities} handler={handlePriorityId} />
)} )}
......
...@@ -178,7 +178,7 @@ export const useQuickActions = ( ...@@ -178,7 +178,7 @@ export const useQuickActions = (
const closeChat = useMethod('livechat:closeRoom'); const closeChat = useMethod('livechat:closeRoom');
const handleClose = useCallback( const handleClose = useCallback(
async (comment: string, tags: string[]) => { async (comment?: string, tags?: string[]) => {
try { try {
await closeChat(rid, comment, { clientAction: true, tags }); await closeChat(rid, comment, { clientAction: true, tags });
closeModal(); closeModal();
......
...@@ -3334,6 +3334,7 @@ ...@@ -3334,6 +3334,7 @@
"Opened": "Opened", "Opened": "Opened",
"Opened_in_a_new_window": "Opened in a new window.", "Opened_in_a_new_window": "Opened in a new window.",
"Opens_a_channel_group_or_direct_message": "Opens a channel, group or direct message", "Opens_a_channel_group_or_direct_message": "Opens a channel, group or direct message",
"Optional": "Optional",
"optional": "optional", "optional": "optional",
"Options": "Options", "Options": "Options",
"or": "or", "or": "or",
......
...@@ -2,6 +2,7 @@ import type { ...@@ -2,6 +2,7 @@ import type {
IOmnichannelCannedResponse, IOmnichannelCannedResponse,
ILivechatAgent, ILivechatAgent,
ILivechatDepartment, ILivechatDepartment,
ILivechatDepartmentRecord,
ILivechatDepartmentAgents, ILivechatDepartmentAgents,
ILivechatMonitor, ILivechatMonitor,
ILivechatTag, ILivechatTag,
...@@ -67,12 +68,12 @@ export type OmnichannelEndpoints = { ...@@ -67,12 +68,12 @@ export type OmnichannelEndpoints = {
}; };
'livechat/department/:_id': { 'livechat/department/:_id': {
GET: (params: { onlyMyDepartments?: booleanString; includeAgents?: booleanString }) => { GET: (params: { onlyMyDepartments?: booleanString; includeAgents?: booleanString }) => {
department: ILivechatDepartment | null; department: ILivechatDepartmentRecord | null;
agents?: any[]; agents?: ILivechatDepartmentAgents[];
}; };
PUT: (params: { department: Partial<ILivechatDepartment>[]; agents: any[] }) => { PUT: (params: { department: Partial<ILivechatDepartment>[]; agents: any[] }) => {
department: ILivechatDepartment; department: ILivechatDepartment;
agents: any[]; agents: ILivechatDepartmentAgents[];
}; };
DELETE: () => void; DELETE: () => void;
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment