You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
529 lines
16 KiB
529 lines
16 KiB
import {
|
|
FieldGroup,
|
|
Field,
|
|
TextInput,
|
|
Chip,
|
|
Box,
|
|
Icon,
|
|
Divider,
|
|
ToggleSwitch,
|
|
TextAreaInput,
|
|
ButtonGroup,
|
|
Button,
|
|
PaginatedSelectFiltered,
|
|
} from '@rocket.chat/fuselage';
|
|
import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks';
|
|
import React, { useMemo, useState, useRef } from 'react';
|
|
import { useSubscription } from 'use-subscription';
|
|
|
|
import { isEmail } from '../../../../lib/utils/isEmail';
|
|
import Page from '../../../components/Page';
|
|
import { useRoomsList } from '../../../components/RoomAutoComplete/hooks/useRoomsList';
|
|
import { useRoute } from '../../../contexts/RouterContext';
|
|
import { useMethod, useEndpoint } from '../../../contexts/ServerContext';
|
|
import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext';
|
|
import { useTranslation } from '../../../contexts/TranslationContext';
|
|
import { useRecordList } from '../../../hooks/lists/useRecordList';
|
|
import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate';
|
|
import { useForm } from '../../../hooks/useForm';
|
|
import { AsyncStatePhase } from '../../../lib/asyncState';
|
|
import { formsSubscription } from '../additionalForms';
|
|
import DepartmentsAgentsTable from './DepartmentsAgentsTable';
|
|
|
|
function withDefault(key, defaultValue) {
|
|
return key || defaultValue;
|
|
}
|
|
|
|
function EditDepartment({ data, id, title, reload, allowedToForwardData }) {
|
|
const t = useTranslation();
|
|
const departmentsRoute = useRoute('omnichannel-departments');
|
|
|
|
const {
|
|
useEeNumberInput = () => {},
|
|
useEeTextInput = () => {},
|
|
useEeTextAreaInput = () => {},
|
|
useDepartmentForwarding = () => {},
|
|
useDepartmentBusinessHours = () => {},
|
|
useSelectForwardDepartment = () => {},
|
|
} = useSubscription(formsSubscription);
|
|
|
|
const initialAgents = useRef((data && data.agents) || []);
|
|
|
|
const MaxChats = useEeNumberInput();
|
|
const VisitorInactivity = useEeNumberInput();
|
|
const WaitingQueueMessageInput = useEeTextAreaInput();
|
|
const AbandonedMessageInput = useEeTextInput();
|
|
const DepartmentForwarding = useDepartmentForwarding();
|
|
const DepartmentBusinessHours = useDepartmentBusinessHours();
|
|
const AutoCompleteDepartment = useSelectForwardDepartment();
|
|
const [agentList, setAgentList] = useState([]);
|
|
const [agentsRemoved, setAgentsRemoved] = useState([]);
|
|
const [agentsAdded, setAgentsAdded] = useState([]);
|
|
|
|
const { department } = data || { department: {} };
|
|
|
|
const [[tags, tagsText], setTagsState] = useState(() => [department?.chatClosingTags ?? [], '']);
|
|
|
|
const { values, handlers, hasUnsavedChanges } = useForm({
|
|
name: withDefault(department?.name, ''),
|
|
email: withDefault(department?.email, ''),
|
|
description: withDefault(department?.description, ''),
|
|
enabled: !!department?.enabled,
|
|
maxNumberSimultaneousChat: department?.maxNumberSimultaneousChat,
|
|
showOnRegistration: !!department?.showOnRegistration,
|
|
showOnOfflineForm: !!department?.showOnOfflineForm,
|
|
abandonedRoomsCloseCustomMessage: withDefault(department?.abandonedRoomsCloseCustomMessage, ''),
|
|
requestTagBeforeClosingChat: !!department?.requestTagBeforeClosingChat,
|
|
offlineMessageChannelName: withDefault(department?.offlineMessageChannelName, ''),
|
|
visitorInactivityTimeoutInSeconds: department?.visitorInactivityTimeoutInSeconds,
|
|
waitingQueueMessage: withDefault(department?.waitingQueueMessage, ''),
|
|
departmentsAllowedToForward:
|
|
allowedToForwardData?.departments?.map((dep) => ({ label: dep.name, value: dep._id })) || [],
|
|
fallbackForwardDepartment: withDefault(department?.fallbackForwardDepartment, ''),
|
|
});
|
|
const {
|
|
handleName,
|
|
handleEmail,
|
|
handleDescription,
|
|
handleEnabled,
|
|
handleMaxNumberSimultaneousChat,
|
|
handleShowOnRegistration,
|
|
handleShowOnOfflineForm,
|
|
handleAbandonedRoomsCloseCustomMessage,
|
|
handleRequestTagBeforeClosingChat,
|
|
handleOfflineMessageChannelName,
|
|
handleVisitorInactivityTimeoutInSeconds,
|
|
handleWaitingQueueMessage,
|
|
handleDepartmentsAllowedToForward,
|
|
handleFallbackForwardDepartment,
|
|
} = handlers;
|
|
|
|
const {
|
|
name,
|
|
email,
|
|
description,
|
|
enabled,
|
|
maxNumberSimultaneousChat,
|
|
showOnRegistration,
|
|
showOnOfflineForm,
|
|
abandonedRoomsCloseCustomMessage,
|
|
requestTagBeforeClosingChat,
|
|
offlineMessageChannelName,
|
|
visitorInactivityTimeoutInSeconds,
|
|
waitingQueueMessage,
|
|
departmentsAllowedToForward,
|
|
fallbackForwardDepartment,
|
|
} = values;
|
|
|
|
const { itemsList: RoomsList, loadMoreItems: loadMoreRooms } = useRoomsList(
|
|
useMemo(() => ({ text: offlineMessageChannelName }), [offlineMessageChannelName]),
|
|
);
|
|
|
|
const { phase: roomsPhase, items: roomsItems, itemCount: roomsTotal } = useRecordList(RoomsList);
|
|
|
|
const handleTagChipClick = (tag) => () => {
|
|
setTagsState(([tags, tagsText]) => [tags.filter((_tag) => _tag !== tag), tagsText]);
|
|
};
|
|
|
|
const handleTagTextSubmit = useMutableCallback(() => {
|
|
setTagsState((state) => {
|
|
const [tags, tagsText] = state;
|
|
|
|
if (tags.includes(tagsText)) {
|
|
return state;
|
|
}
|
|
|
|
return [[...tags, tagsText], ''];
|
|
});
|
|
});
|
|
|
|
const handleTagTextChange = (e) => {
|
|
setTagsState(([tags]) => [tags, e.target.value]);
|
|
};
|
|
|
|
const saveDepartmentInfo = useMethod('livechat:saveDepartment');
|
|
const saveDepartmentAgentsInfoOnEdit = useEndpoint('POST', `livechat/department/${id}/agents`);
|
|
|
|
const dispatchToastMessage = useToastMessageDispatch();
|
|
|
|
const [nameError, setNameError] = useState();
|
|
const [emailError, setEmailError] = useState();
|
|
const [tagError, setTagError] = useState();
|
|
|
|
useComponentDidUpdate(() => {
|
|
setNameError(!name ? t('The_field_is_required', 'name') : '');
|
|
}, [t, name]);
|
|
useComponentDidUpdate(() => {
|
|
setEmailError(!email ? t('The_field_is_required', 'email') : '');
|
|
}, [t, email]);
|
|
useComponentDidUpdate(() => {
|
|
setEmailError(!isEmail(email) ? t('Validate_email_address') : '');
|
|
}, [t, email]);
|
|
useComponentDidUpdate(() => {
|
|
setTagError(
|
|
requestTagBeforeClosingChat && (!tags || tags.length === 0)
|
|
? t('The_field_is_required', 'name')
|
|
: '',
|
|
);
|
|
}, [requestTagBeforeClosingChat, t, tags]);
|
|
|
|
const handleSubmit = useMutableCallback(async (e) => {
|
|
e.preventDefault();
|
|
let error = false;
|
|
if (!name) {
|
|
setNameError(t('The_field_is_required', 'name'));
|
|
error = true;
|
|
}
|
|
if (!email) {
|
|
setEmailError(t('The_field_is_required', 'email'));
|
|
error = true;
|
|
}
|
|
if (!isEmail(email)) {
|
|
setEmailError(t('Validate_email_address'));
|
|
error = true;
|
|
}
|
|
if (requestTagBeforeClosingChat && (!tags || tags.length === 0)) {
|
|
setTagError(t('The_field_is_required', 'tags'));
|
|
error = true;
|
|
}
|
|
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
enabled,
|
|
name,
|
|
description,
|
|
showOnRegistration,
|
|
showOnOfflineForm,
|
|
requestTagBeforeClosingChat,
|
|
email,
|
|
chatClosingTags: tags,
|
|
offlineMessageChannelName,
|
|
maxNumberSimultaneousChat,
|
|
visitorInactivityTimeoutInSeconds,
|
|
abandonedRoomsCloseCustomMessage,
|
|
waitingQueueMessage,
|
|
departmentsAllowedToForward: departmentsAllowedToForward?.map((dep) => dep.value).join(),
|
|
fallbackForwardDepartment: fallbackForwardDepartment.value,
|
|
};
|
|
|
|
const agentListPayload = {
|
|
upsert: agentList.filter(
|
|
(agent) =>
|
|
!initialAgents.current.some(
|
|
(initialAgent) =>
|
|
initialAgent._id === agent._id &&
|
|
agent.count === initialAgent.count &&
|
|
agent.order === initialAgent.order,
|
|
),
|
|
),
|
|
remove: initialAgents.current.filter(
|
|
(initialAgent) => !agentList.some((agent) => initialAgent._id === agent._id),
|
|
),
|
|
};
|
|
|
|
try {
|
|
if (id) {
|
|
await saveDepartmentInfo(id, payload, []);
|
|
if (agentListPayload.upsert.length > 0 || agentListPayload.remove.length > 0) {
|
|
await saveDepartmentAgentsInfoOnEdit(agentListPayload);
|
|
}
|
|
} else {
|
|
await saveDepartmentInfo(id, payload, agentList);
|
|
}
|
|
dispatchToastMessage({ type: 'success', message: t('Saved') });
|
|
reload();
|
|
departmentsRoute.push({});
|
|
} catch (error) {
|
|
dispatchToastMessage({ type: 'error', message: error });
|
|
}
|
|
});
|
|
|
|
const handleReturn = useMutableCallback(() => {
|
|
departmentsRoute.push({});
|
|
});
|
|
|
|
const invalidForm =
|
|
!name ||
|
|
!email ||
|
|
!isEmail(email) ||
|
|
!hasUnsavedChanges ||
|
|
(requestTagBeforeClosingChat && (!tags || tags.length === 0));
|
|
|
|
const formId = useUniqueId();
|
|
|
|
const hasNewAgent = useMemo(
|
|
() => data.agents.length === agentList.length,
|
|
[data.agents, agentList],
|
|
);
|
|
|
|
const agentsHaveChanged = () => {
|
|
let hasChanges = false;
|
|
if (agentList.length !== initialAgents.current.length) {
|
|
hasChanges = true;
|
|
}
|
|
|
|
if (agentsAdded.length > 0 && agentsRemoved.length > 0) {
|
|
hasChanges = true;
|
|
}
|
|
|
|
agentList.forEach((agent) => {
|
|
const existingAgent = initialAgents.current.find(
|
|
(initial) => initial.agentId === agent.agentId,
|
|
);
|
|
if (existingAgent) {
|
|
if (agent.count !== existingAgent.count) {
|
|
hasChanges = true;
|
|
}
|
|
if (agent.order !== existingAgent.order) {
|
|
hasChanges = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
return hasChanges;
|
|
};
|
|
|
|
return (
|
|
<Page flexDirection='row'>
|
|
<Page>
|
|
<Page.Header title={title}>
|
|
<ButtonGroup>
|
|
<Button onClick={handleReturn}>
|
|
<Icon name='back' /> {t('Back')}
|
|
</Button>
|
|
<Button
|
|
type='submit'
|
|
form={formId}
|
|
primary
|
|
disabled={invalidForm && hasNewAgent && !(id && agentsHaveChanged())}
|
|
>
|
|
{t('Save')}
|
|
</Button>
|
|
</ButtonGroup>
|
|
</Page.Header>
|
|
<Page.ScrollableContentWithShadow>
|
|
<FieldGroup
|
|
w='full'
|
|
alignSelf='center'
|
|
maxWidth='x600'
|
|
id={formId}
|
|
is='form'
|
|
autoComplete='off'
|
|
onSubmit={handleSubmit}
|
|
>
|
|
<Field>
|
|
<Box display='flex' flexDirection='row'>
|
|
<Field.Label>{t('Enabled')}</Field.Label>
|
|
<Field.Row>
|
|
<ToggleSwitch flexGrow={1} checked={enabled} onChange={handleEnabled} />
|
|
</Field.Row>
|
|
</Box>
|
|
</Field>
|
|
<Field>
|
|
<Field.Label>{t('Name')}*</Field.Label>
|
|
<Field.Row>
|
|
<TextInput
|
|
flexGrow={1}
|
|
error={nameError}
|
|
value={name}
|
|
onChange={handleName}
|
|
placeholder={t('Name')}
|
|
/>
|
|
</Field.Row>
|
|
</Field>
|
|
<Field>
|
|
<Field.Label>{t('Description')}</Field.Label>
|
|
<Field.Row>
|
|
<TextAreaInput
|
|
flexGrow={1}
|
|
value={description}
|
|
onChange={handleDescription}
|
|
placeholder={t('Description')}
|
|
/>
|
|
</Field.Row>
|
|
</Field>
|
|
<Field>
|
|
<Box display='flex' flexDirection='row'>
|
|
<Field.Label>{t('Show_on_registration_page')}</Field.Label>
|
|
<Field.Row>
|
|
<ToggleSwitch
|
|
flexGrow={1}
|
|
checked={showOnRegistration}
|
|
onChange={handleShowOnRegistration}
|
|
/>
|
|
</Field.Row>
|
|
</Box>
|
|
</Field>
|
|
<Field>
|
|
<Field.Label>{t('Email')}*</Field.Label>
|
|
<Field.Row>
|
|
<TextInput
|
|
flexGrow={1}
|
|
error={emailError}
|
|
value={email}
|
|
addon={<Icon name='mail' size='x20' />}
|
|
onChange={handleEmail}
|
|
placeholder={t('Email')}
|
|
/>
|
|
</Field.Row>
|
|
</Field>
|
|
<Field>
|
|
<Box display='flex' flexDirection='row'>
|
|
<Field.Label>{t('Show_on_offline_page')}</Field.Label>
|
|
<Field.Row>
|
|
<ToggleSwitch
|
|
flexGrow={1}
|
|
checked={showOnOfflineForm}
|
|
onChange={handleShowOnOfflineForm}
|
|
/>
|
|
</Field.Row>
|
|
</Box>
|
|
</Field>
|
|
<Field>
|
|
<Field.Label>{t('Livechat_DepartmentOfflineMessageToChannel')}</Field.Label>
|
|
<Field.Row>
|
|
<PaginatedSelectFiltered
|
|
value={offlineMessageChannelName}
|
|
onChange={handleOfflineMessageChannelName}
|
|
flexShrink={0}
|
|
filter={offlineMessageChannelName}
|
|
setFilter={handleOfflineMessageChannelName}
|
|
options={roomsItems}
|
|
placeholder={t('Channel_name')}
|
|
endReached={
|
|
roomsPhase === AsyncStatePhase.LOADING
|
|
? () => {}
|
|
: (start) => loadMoreRooms(start, Math.min(50, roomsTotal))
|
|
}
|
|
/>
|
|
</Field.Row>
|
|
</Field>
|
|
{MaxChats && (
|
|
<Field>
|
|
<MaxChats
|
|
value={maxNumberSimultaneousChat}
|
|
handler={handleMaxNumberSimultaneousChat}
|
|
label={'Max_number_of_chats_per_agent'}
|
|
placeholder='Max_number_of_chats_per_agent_description'
|
|
/>
|
|
</Field>
|
|
)}
|
|
{VisitorInactivity && (
|
|
<Field>
|
|
<VisitorInactivity
|
|
value={visitorInactivityTimeoutInSeconds}
|
|
handler={handleVisitorInactivityTimeoutInSeconds}
|
|
label={'How_long_to_wait_to_consider_visitor_abandonment_in_seconds'}
|
|
placeholder='Number_in_seconds'
|
|
/>
|
|
</Field>
|
|
)}
|
|
{AbandonedMessageInput && (
|
|
<Field>
|
|
<AbandonedMessageInput
|
|
value={abandonedRoomsCloseCustomMessage}
|
|
handler={handleAbandonedRoomsCloseCustomMessage}
|
|
label={'Livechat_abandoned_rooms_closed_custom_message'}
|
|
placeholder='Enter_a_custom_message'
|
|
/>
|
|
</Field>
|
|
)}
|
|
{WaitingQueueMessageInput && (
|
|
<Field>
|
|
<WaitingQueueMessageInput
|
|
value={waitingQueueMessage}
|
|
handler={handleWaitingQueueMessage}
|
|
label={'Waiting_queue_message'}
|
|
/>
|
|
</Field>
|
|
)}
|
|
{DepartmentForwarding && (
|
|
<Field>
|
|
<DepartmentForwarding
|
|
departmentId={id}
|
|
value={departmentsAllowedToForward}
|
|
handler={handleDepartmentsAllowedToForward}
|
|
label={'List_of_departments_for_forward'}
|
|
placeholder='Enter_a_department_name'
|
|
/>
|
|
</Field>
|
|
)}
|
|
{AutoCompleteDepartment && (
|
|
<Field>
|
|
<Field.Label>{t('Fallback_forward_department')}</Field.Label>
|
|
<AutoCompleteDepartment
|
|
haveNone
|
|
value={fallbackForwardDepartment}
|
|
onChange={handleFallbackForwardDepartment}
|
|
placeholder={t('Fallback_forward_department')}
|
|
label={t('Fallback_forward_department')}
|
|
onlyMyDepartments
|
|
/>
|
|
</Field>
|
|
)}
|
|
<Field>
|
|
<Box display='flex' flexDirection='row'>
|
|
<Field.Label>{t('Request_tag_before_closing_chat')}</Field.Label>
|
|
<Field.Row>
|
|
<ToggleSwitch
|
|
flexGrow={1}
|
|
checked={requestTagBeforeClosingChat}
|
|
onChange={handleRequestTagBeforeClosingChat}
|
|
/>
|
|
</Field.Row>
|
|
</Box>
|
|
</Field>
|
|
{requestTagBeforeClosingChat && (
|
|
<Field>
|
|
<Field.Label alignSelf='stretch'>{t('Conversation_closing_tags')}*</Field.Label>
|
|
<Field.Row>
|
|
<TextInput
|
|
error={tagError}
|
|
value={tagsText}
|
|
onChange={handleTagTextChange}
|
|
placeholder={t('Enter_a_tag')}
|
|
/>
|
|
<Button mis='x8' title={t('add')} onClick={handleTagTextSubmit}>
|
|
{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>
|
|
)}
|
|
</Field>
|
|
)}
|
|
{DepartmentBusinessHours && (
|
|
<Field>
|
|
<DepartmentBusinessHours bhId={department?.businessHourId} />
|
|
</Field>
|
|
)}
|
|
<Divider mb='x16' />
|
|
<Field>
|
|
<Field.Label mb='x4'>{t('Agents')}:</Field.Label>
|
|
<Box display='flex' flexDirection='column' height='50vh'>
|
|
<DepartmentsAgentsTable
|
|
agents={data && data.agents}
|
|
setAgentListFinal={setAgentList}
|
|
setAgentsAdded={setAgentsAdded}
|
|
setAgentsRemoved={setAgentsRemoved}
|
|
/>
|
|
</Box>
|
|
</Field>
|
|
</FieldGroup>
|
|
</Page.ScrollableContentWithShadow>
|
|
</Page>
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
export default EditDepartment;
|
|
|