Skip to content

Commit

Permalink
RocketChat#703 Выбор отправителей
Browse files Browse the repository at this point in the history
  • Loading branch information
shedoev committed Oct 21, 2020
1 parent 6e87f9c commit c9a7154
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 141 deletions.
19 changes: 19 additions & 0 deletions app/api/server/v1/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,25 @@ API.v1.addRoute('users.list', { authRequired: true }, {
},
});

API.v1.addRoute('users.all', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-d-room')) {
return API.v1.unauthorized();
}

const { sort, fields, query } = this.parseJsonQuery();

const users = Users.find(query, {
sort: sort || { username: 1 },
fields,
}).fetch();

return API.v1.success({
users
});
},
});

API.v1.addRoute('users.register', { authRequired: false }, {
post() {
if (this.userId) {
Expand Down
169 changes: 111 additions & 58 deletions app/manual-mail-sender/client/views/MailForm.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Box, Button, ButtonGroup, Field, Margins, TextInput, InputBox, Chip } from '@rocket.chat/fuselage';
import React, { useMemo, useState } from 'react';
import { Box, Button, ButtonGroup, Chip, Field, InputBox, Margins, Scrollable, TextInput } from '@rocket.chat/fuselage';
import React, { Component, useMemo, useState } from 'react';
import 'react-dropdown-tree-select/dist/styles.css'
import '../../public/stylesheets/mail-sender.css'
import CKEditor from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import '@ckeditor/ckeditor5-build-classic/build/translations/ru';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import DropdownTreeSelect from 'react-dropdown-tree-select';
import isEqual from 'lodash.isequal';

import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useMethod } from '../../../../client/contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
Expand All @@ -19,7 +23,44 @@ function packData(data, files) {
return dataToSend;
}

function MailForm() {
class DropdownTreeSelectContainer extends Component {
constructor(props) {
super(props);
this.state = { data: props.data }
}

componentWillReceiveProps = (nextProps) => {
if (!isEqual(nextProps.data, this.state.data)) {
this.setState({data: nextProps.data})
}
}

shouldComponentUpdate = (nextProps) => {
return !isEqual(nextProps.data, this.state.data)
}

render() {
const { data, ...rest } = this.props;
return (
<DropdownTreeSelect data={this.state.data} {...rest} />
)
}
}

const getEmails = (obj, path) => {
let emails = '';
obj.forEach(node => {
if (!node.children && node.path.startsWith(path)) {
emails += node.value + ',';
}
if (node.children) {
emails += getEmails(node.children, path)
}
})
return emails;
}

function MailForm({ recipients }) {
const [newData, setNewData] = useState({
email: { value: '', required: true },
topic: { value: '', required: true },
Expand Down Expand Up @@ -76,8 +117,6 @@ function MailForm() {

dispatchToastMessage({ type: 'success', message: t('Integrations_Outgoing_Type_SendMessage') });
} catch (e) {
console.error('[MailForm.js].handleSubmit e:', e);

const { invalidMails } = e.details;

dispatchToastMessage({ type: 'error', message: `${ t('Mail_Message_Invalid_emails') }: ${ invalidMails }` });
Expand All @@ -86,59 +125,73 @@ function MailForm() {
}
};

return <Margins blockEnd='x32'>
<Box>
<Box display='flex' flexDirection='column'>
<Margins all='x8'>
<Field>
<Field.Label>{t('Email_receivers')} <span style={ { color: 'red' } }>*</span></Field.Label>
<Field.Row>
<TextInput value={newData.email.value} flexGrow={1} onChange={handleChange('email')} placeholder={`${ t('Email_Placeholder_with_comma') }`}/>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Email_subject')} <span style={ { color: 'red' } }>*</span></Field.Label>
<Field.Row>
<TextInput value={newData.topic.value} flexGrow={1} onChange={handleChange('topic')} placeholder={`${ t('Email_subject_placeholder') }`}/>
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Email_body')} <span style={ { color: 'red' } }>*</span></Field.Label>
<Field.Row>
<CKEditor
editor={ ClassicEditor }
config={ {
language: 'ru',
removePlugins: ["ImageUpload"],
} }
data='<p>Здравствуйте, </p>'
onChange={ (event, editor) => {
const data = editor.getData();
setNewData({ ...newData, message: { value: data, required: newData.message.required } });
} }
/>
</Field.Row>
</Field>
<Field>
<Field.Label alignSelf='stretch' htmlFor={fileSourceInputId}>{t('Add_files')}</Field.Label>
<Field.Row>
<InputBox type='file' id={fileSourceInputId} multiple onChange={handleImportFileChange} />
</Field.Row>
{files?.length > 0 && <Box display='flex' flexDirection='row' flexWrap='wrap' justifyContent='flex-start' mbs='x4'>
<Margins inlineEnd='x4' blockEnd='x4'>
{files.map((file, i) => <Chip pi='x4' key={i} onClick={handleFileUploadChipClick(file)}>{file.filename}</Chip>)}
</Margins>
</Box>}
</Field>
$('.main-content').removeClass('rc-old');

const handleDropdownTreeSelectChange = (currentNode, selectedNodes) => {
let emails = '';
selectedNodes.forEach(item => {
emails += getEmails(recipients, item.path)
})
setNewData( { ...newData, email: { value: emails, required: newData.email.required } });
};

return <>
<Field mbe='x8'>
<Field.Label>{t('Email_receivers')} <span style={ { color: 'red' } }>*</span></Field.Label>
<Field.Row>
<DropdownTreeSelectContainer
className='tree-select'
data={recipients}
onChange={handleDropdownTreeSelectChange}
texts={
{
placeholder: 'Поиск...',
noMatches: 'Не найдено совпадений'
}
}
/>
</Field.Row>
</Field>
<Field mbe='x8'>
<Field.Label>{t('Email_subject')} <span style={ { color: 'red' } }>*</span></Field.Label>
<Field.Row>
<TextInput value={newData.topic.value} flexGrow={1} onChange={handleChange('topic')} placeholder={`${ t('Email_subject_placeholder') }`}/>
</Field.Row>
</Field>
<Field mbe='x8'>
<Field.Label>{t('Email_body')} <span style={ { color: 'red' } }>*</span></Field.Label>
<Field.Row>
<CKEditor
editor={ ClassicEditor }
config={ {
language: 'ru',
removePlugins: ["ImageUpload"],
} }
data='<p>Здравствуйте, </p>'
onChange={ (event, editor) => {
const data = editor.getData();
setNewData({ ...newData, message: { value: data, required: newData.message.required } });
} }
/>
</Field.Row>
</Field>
<Field mbe='x8'>
<Field.Label alignSelf='stretch' htmlFor={fileSourceInputId}>{t('Add_files')}</Field.Label>
<Field.Row>
<InputBox type='file' id={fileSourceInputId} multiple onChange={handleImportFileChange} />
</Field.Row>
{files?.length > 0 && <Box display='flex' flexDirection='row' flexWrap='wrap' justifyContent='flex-start' mbs='x4'>
<Margins inlineEnd='x4' blockEnd='x4'>
{files.map((file, i) => <Chip pi='x4' key={i} onClick={handleFileUploadChipClick(file)}>{file.filename}</Chip>)}
</Margins>
</Box>
<ButtonGroup align='end'>
<Button onClick={handleSubmit} primary disabled={!allFieldAreFilled || commiting} >
{t('Send')}
</Button>
</ButtonGroup>
</Box>
</Margins>;
</Box>}
</Field>
<ButtonGroup align='end'>
<Button onClick={handleSubmit} primary disabled={!allFieldAreFilled || commiting} >
{t('Send')}
</Button>
</ButtonGroup>
</>;
}

export default MailForm;
113 changes: 58 additions & 55 deletions app/manual-mail-sender/client/views/index.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,75 @@
import React, { useCallback, useMemo, useState } from 'react';
import { Button, Icon } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMediaQuery } from '@rocket.chat/fuselage-hooks';
import React, {useMemo, useState, useEffect} from 'react';
import '../../public/stylesheets/mail-sender.css';
import Page from '../../../../client/components/basic/Page';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
import MailForm from './MailForm';
import { useRoute, useRouteParameter } from '../../../../client/contexts/RouterContext';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useEndpointData } from '../../../../client/hooks/useEndpointData';
import MailForm from './MailForm';

const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1);

export const useQuery = (params, sort, cache) => useMemo(() => ({
query: JSON.stringify({ desc: { $regex: params.text || '', $options: 'i' } }),
sort: JSON.stringify({ [sort[0]]: sortDir(sort[1]) }),
...params.itemsPerPage && { count: params.itemsPerPage },
...params.current && { offset: params.current },
}), [JSON.stringify(params), JSON.stringify(sort), cache]);
const useQuery = (params, sort) => useMemo(() => ({
fields: JSON.stringify({ name: 1, username: 1, emails: 1 }),
query: JSON.stringify({
$or: [
{ 'emails.address': { $regex: params.text || '', $options: 'i' } },
{ username: { $regex: params.text || '', $options: 'i' } },
{ name: { $regex: params.text || '', $options: 'i' } },
],
}),
sort: JSON.stringify({ [sort[0]]: sortDir(sort[1]), usernames: sort[0] === 'name' ? sortDir(sort[1]) : undefined }),
}), [JSON.stringify(params), JSON.stringify(sort)]);

const assignObjectPaths = (obj, stack) => {
const isArray = Array.isArray(obj);
Object.keys(obj).forEach(k => {
const node = obj[k];
const key = isArray ? `[${k}]` : k;
if (typeof node === 'object') {
node.path = stack ? `${stack}.${key}` : key;
assignObjectPaths(node, node.path);
}
})
}

export function MailSenderPage() {
const t = useTranslation();

const routeName = 'councils';

const [params, setParams] = useState({ current: 0, itemsPerPage: 25 });
const [sort, setSort] = useState(['d', 'desc']);
const [cache, setCache] = useState();
const [params, setParams] = useState({ text: '' });
const [sort, setSort] = useState(['name', 'asc']);

const debouncedParams = useDebouncedValue(params, 500);
const debouncedSort = useDebouncedValue(sort, 500);

const query = useQuery(debouncedParams, debouncedSort, cache);

const data = useEndpointData('councils.list', query) || { result: [] };

const router = useRoute(routeName);

const mobile = useMediaQuery('(max-width: 420px)');
const small = useMediaQuery('(max-width: 780px)');

const onEditClick = (_id) => () => {
router.push({
context: 'edit',
id: _id,
});
};

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

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

const handleHeaderButtonClick = useCallback((context) => () => {
router.push({ context });
}, [router]);

return <Page flexDirection='row'>
<Page>
<Page.Header title={t('Send_email')}>
</Page.Header>
<Page.Content>
<MailForm/>
</Page.Content>
</Page>
const query = useQuery(debouncedParams, debouncedSort);

let data = useEndpointData('users.all', query) || {};

const [recipients, setRecipients] = useState([]);

useEffect(() => {
const users = data.users || [];
const recipients = [{
label: 'Все пользователи',
value: 'all_users',
children: users.map((value) => {
return {
label: value.name,
value: value.emails ? value.emails[0].address : '',
}
})
}]

assignObjectPaths(recipients);

setRecipients(recipients);
}, [data])

return <Page>
<Page.Header title={t('Send_email')}>
</Page.Header>
<Page.Content>
<MailForm recipients={recipients}/>
</Page.Content>
</Page>;
}

Expand Down
27 changes: 0 additions & 27 deletions app/manual-mail-sender/client/views/lib.js

This file was deleted.

Loading

0 comments on commit c9a7154

Please sign in to comment.