From 6977b453fce37fe8a29ce91f313eb9056e7d73da Mon Sep 17 00:00:00 2001 From: shedoev Date: Tue, 10 Nov 2020 11:17:03 +0800 Subject: [PATCH] =?UTF-8?q?#752=20[NEW]=20=D0=9C=D0=B5=D0=BD=D1=8E=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D1=83=D0=BD=D0=BA=D1=82=D0=B0,=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=BE=D0=B2=20=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=83=D0=BD=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/server/models/Protocols.js | 34 ++- app/protocols/client/views/EditItem.js | 66 ------ app/protocols/client/views/EditSection.js | 66 ------ app/protocols/client/views/Protocol.js | 221 ++++++++++++++--- app/protocols/client/views/Sections.js | 50 +++- .../client/views/participants/Participants.js | 2 - app/protocols/server/index.js | 3 +- .../methods/downloadProtocolParticipants.js | 223 ------------------ app/protocols/server/methods/moveItem.js | 86 +++++++ app/protocols/server/methods/moveSection.js | 70 ++++++ packages/rocketchat-i18n/i18n/ru.i18n.json | 10 + 11 files changed, 429 insertions(+), 402 deletions(-) delete mode 100644 app/protocols/server/methods/downloadProtocolParticipants.js create mode 100644 app/protocols/server/methods/moveItem.js create mode 100644 app/protocols/server/methods/moveSection.js diff --git a/app/models/server/models/Protocols.js b/app/models/server/models/Protocols.js index 38515af36b765..ac25d887ba918 100644 --- a/app/models/server/models/Protocols.js +++ b/app/models/server/models/Protocols.js @@ -27,7 +27,22 @@ class Protocols extends Base { sectionData._id = _id; const data = this.findOne({ _id: protocolId }); - data.sections = data.sections ? [...data.sections, sectionData] : [sectionData]; + + if (data.sections) { + let internalNum = 0; + data.sections.forEach((section) => { + if (section.inum > internalNum) { + internalNum = section.inum; + } + }) + internalNum++; + sectionData.inum = internalNum; + data.sections = [...data.sections, sectionData]; + } else { + sectionData.inum = 1; + data.sections = [sectionData]; + } + data._updatedAt = new Date(); this.update({ _id: protocolId }, { $set: { ...data } }); @@ -73,7 +88,22 @@ class Protocols extends Base { data.sections.forEach((section) => { if (section._id === sectionId) { - section.items = section.items ? [...section.items, item] : [item]; + + if (section.items) { + let internalNum = 0; + section.items.forEach((item) => { + if (item.inum > internalNum) { + internalNum = item.inum; + } + }) + internalNum++; + item.inum = internalNum; + section.items = [...section.items, item]; + } else { + item.inum = 1; + section.items = [item]; + } + } }); diff --git a/app/protocols/client/views/EditItem.js b/app/protocols/client/views/EditItem.js index a0317cb5ab52b..864ffafc9393e 100644 --- a/app/protocols/client/views/EditItem.js +++ b/app/protocols/client/views/EditItem.js @@ -4,12 +4,10 @@ import { Button, ButtonGroup, Field, - Icon, Skeleton, Throbber, InputBox, TextInput, - Modal } from '@rocket.chat/fuselage'; import DatePicker, { registerLocale } from 'react-datepicker'; import ru from 'date-fns/locale/ru'; @@ -24,50 +22,10 @@ import { useMethod } from '../../../../client/contexts/ServerContext'; import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../../client/hooks/useEndpointDataExperimental'; import { validateItemData, createItemData } from './lib'; -import { useSetModal } from '../../../../client/contexts/ModalContext'; import VerticalBar from '../../../../client/components/basic/VerticalBar'; require('react-datepicker/dist/react-datepicker.css'); -const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => { - const t = useTranslation(); - return - - - {t('Are_you_sure')} - - - - {t('Item_Delete_Warning')} - - - - - - - - ; -}; - -const SuccessModal = ({ onClose, ...props }) => { - const t = useTranslation(); - return - - - {t('Deleted')} - - - - {t('Item_Has_Been_Deleted')} - - - - - - - ; -}; - export function EditItem({ protocolId, sectionId, _id, cache, onChange, ...props }) { const query = useMemo(() => ({ query: JSON.stringify({ _id: protocolId }), @@ -89,9 +47,6 @@ export function EditItem({ protocolId, sectionId, _id, cache, onChange, ...props - - - ; } @@ -115,7 +70,6 @@ function EditItemWithData({ close, onChange, protocol, sectionId, itemId, ...pro const [name, setName] = useState(''); const [responsible, setResponsible] = useState(''); const [expireAt, setExpireAt] = useState(''); - const setModal = useSetModal(); useEffect(() => { setNumber(previousNumber || ''); @@ -124,7 +78,6 @@ function EditItemWithData({ close, onChange, protocol, sectionId, itemId, ...pro setExpireAt(previousExpireAt ? new Date(previousExpireAt) : ''); }, [previousNumber, previousName, previousResponsible, previousExpireAt, _id]); - const deleteItem = useMethod('deleteItem'); const insertOrUpdateItem = useMethod('insertOrUpdateItem'); const hasUnsavedChanges = useMemo(() => previousNumber !== number || previousName !== name || previousResponsible !== responsible || previousExpireAt !== expireAt, @@ -144,18 +97,6 @@ function EditItemWithData({ close, onChange, protocol, sectionId, itemId, ...pro onChange(); }, [saveAction, onChange]); - const onDeleteConfirm = useCallback(async () => { - try { - await deleteItem(protocol._id, sectionId, _id); - setModal(() => { setModal(undefined); close(); onChange(); }}/>); - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - onChange(); - } - }, [_id, close, deleteItem, dispatchToastMessage, onChange]); - - const openConfirmDelete = () => setModal(() => setModal(undefined)}/>); - return {t('Item_Number')} @@ -206,12 +147,5 @@ function EditItemWithData({ close, onChange, protocol, sectionId, itemId, ...pro - - - - - - - ; } diff --git a/app/protocols/client/views/EditSection.js b/app/protocols/client/views/EditSection.js index 666e25a49a0ec..8445ecde68f4b 100644 --- a/app/protocols/client/views/EditSection.js +++ b/app/protocols/client/views/EditSection.js @@ -4,11 +4,9 @@ import { Button, ButtonGroup, Field, - Icon, Skeleton, Throbber, InputBox, - Modal } from '@rocket.chat/fuselage'; import CKEditor from '@ckeditor/ckeditor5-react'; import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; @@ -19,50 +17,10 @@ import { useMethod } from '../../../../client/contexts/ServerContext'; import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../../client/hooks/useEndpointDataExperimental'; import { validateSectionData, createSectionData } from './lib'; -import { useSetModal } from '../../../../client/contexts/ModalContext'; import VerticalBar from '../../../../client/components/basic/VerticalBar'; require('react-datepicker/dist/react-datepicker.css'); -const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => { - const t = useTranslation(); - return - - - {t('Are_you_sure')} - - - - {t('Section_Delete_Warning')} - - - - - - - - ; -}; - -const SuccessModal = ({ onClose, ...props }) => { - const t = useTranslation(); - return - - - {t('Deleted')} - - - - {t('Section_Has_Been_Deleted')} - - - - - - - ; -}; - export function EditSection({ protocolId, _id, cache, onChange, ...props }) { const query = useMemo(() => ({ query: JSON.stringify({ _id: protocolId }), @@ -80,9 +38,6 @@ export function EditSection({ protocolId, _id, cache, onChange, ...props }) { - - - ; } @@ -104,14 +59,12 @@ function EditSectionWithData({ close, onChange, protocol, sectionId, ...props }) const [number, setNumber] = useState(''); const [name, setName] = useState(''); - const setModal = useSetModal(); useEffect(() => { setNumber(previousNumber || ''); setName(previousName || ''); }, [previousNumber, previousName, _id]); - const deleteSection = useMethod('deleteSection'); const insertOrUpdateSection = useMethod('insertOrUpdateSection'); const hasUnsavedChanges = useMemo(() => previousNumber !== number || previousName !== name, @@ -131,18 +84,6 @@ function EditSectionWithData({ close, onChange, protocol, sectionId, ...props }) onChange(); }, [saveAction, onChange]); - const onDeleteConfirm = useCallback(async () => { - try { - await deleteSection(protocol._id, _id); - setModal(() => { setModal(undefined); close(); onChange(); }}/>); - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - onChange(); - } - }, [_id, close, deleteSection, dispatchToastMessage, onChange]); - - const openConfirmDelete = () => setModal(() => setModal(undefined)}/>); - return {t('Section_Number')} @@ -175,12 +116,5 @@ function EditSectionWithData({ close, onChange, protocol, sectionId, ...props }) - - - - - - - ; } diff --git a/app/protocols/client/views/Protocol.js b/app/protocols/client/views/Protocol.js index 60aff5e94f5d2..3377eb1664d65 100644 --- a/app/protocols/client/views/Protocol.js +++ b/app/protocols/client/views/Protocol.js @@ -1,14 +1,15 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { Box, Button, ButtonGroup, Field, Icon, Label } from '@rocket.chat/fuselage'; +import { Box, Button, ButtonGroup, Field, Icon, Label, Modal } from '@rocket.chat/fuselage'; import Page from '../../../../client/components/basic/Page'; import { useTranslation } from '../../../../client/contexts/TranslationContext'; +import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext'; +import { useSetModal } from '../../../../client/contexts/ModalContext'; import { useRoute, useRouteParameter } from '../../../../client/contexts/RouterContext'; import { useEndpointData } from '../../../../client/hooks/useEndpointData'; import { useFormatDate } from '../../../../client/hooks/useFormatDate'; import { useMethod } from '../../../../client/contexts/ServerContext'; import { Sections } from './Sections'; -import VerticalBar from "/client/components/basic/VerticalBar"; import { AddSection } from './AddSection'; import { AddItem } from './AddItem'; import { EditSection } from './EditSection'; @@ -16,12 +17,55 @@ import { EditItem } from './EditItem'; import { Participants } from '../views/participants/Participants'; import { AddParticipant } from '../views/participants/AddParticipant'; import { CreateParticipant } from '../views/participants/CreateParticipant'; +import { popover } from '../../../ui-utils/client/lib/popover'; +import VerticalBar from '../../../../client/components/basic/VerticalBar'; + +const DeleteWarningModal = ({ title, onDelete, onCancel, ...props }) => { + const t = useTranslation(); + return + + + {t('Are_you_sure')} + + + + {title} + + + + + + + + ; +}; + +const SuccessModal = ({ title, onClose, ...props }) => { + const t = useTranslation(); + return + + + {t('Deleted')} + + + + {title} + + + + + + + ; +}; export function ProtocolPage() { const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); const formatDate = useFormatDate(); const [cache, setCache] = useState(); + const setModal = useSetModal(); const router = useRoute('protocol'); const protocolId = useRouteParameter('id'); @@ -37,28 +81,16 @@ export function ProtocolPage() { const title = t('Protocol').concat(' ').concat(data.num).concat(' ').concat(t('Date_to')).concat(' ').concat(formatDate(data.d)); - const downloadProtocolParticipantsMethod = useMethod('downloadProtocolParticipants'); - - const downloadProtocolParticipants = (_id) => async (e) => { - e.preventDefault(); - try { - const res = await downloadProtocolParticipantsMethod({ _id }); - const url = window.URL.createObjectURL(new Blob([res])); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', 'file.docx'); - document.body.appendChild(link); - link.click(); - } catch (e) { - console.error('[index.js].downloadProtocolParticipants :', e); - } - }; + const deleteSection = useMethod('deleteSection'); + const deleteItem = useMethod('deleteItem'); + const moveSection = useMethod('moveSection'); + const moveItem = useMethod('moveItem'); const onAddSectionClick = useCallback((context) => () => { router.push({ id: protocolId, context: context }); }, [router]); - const onSectionClick = useCallback((_id) => () => { + const onEditSectionClick = useCallback((_id) => () => { router.push({ id: protocolId, context: 'edit-section', @@ -84,7 +116,7 @@ export function ProtocolPage() { }); }, [router]); - const onItemClick = useCallback((sectionId, _id) => () => { + const onEditItemClick = useCallback((sectionId, _id) => () => { router.push({ id: protocolId, context: 'edit-item', @@ -104,6 +136,138 @@ export function ProtocolPage() { }); }, [router]); + const onDeleteSectionConfirm = useCallback((_id) => async () => { + try { + await deleteSection(protocolId, _id); + setModal(() => { setModal(undefined); onChange(); }}/>); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + onChange(); + } + }, [deleteSection, dispatchToastMessage, onChange]); + + const openConfirmDeleteSection = (sectionId) => () => setModal(() => setModal(undefined)}/>); + + const onMoveSectionClick = useCallback((direction, _id) => async () => { + try { + const moved = await moveSection(direction, protocolId, _id); + if (moved) { + dispatchToastMessage({type: 'success', message: t('Section_Moved_Successfully')}); + onChange(); + } + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + onChange(); + } + }, [moveSection, dispatchToastMessage, onChange]); + + const onSectionMenuClick = useCallback((event) => { + const items = [ + { + // icon: 'edit', + name: t('Section_Edit'), + action: onEditSectionClick(event.currentTarget.dataset.section) + }, + { + // icon: '', + name: t('Item_Add'), + action: onAddItemClick('new-item', event.currentTarget.dataset.section) + }, + event.currentTarget.dataset.first !== 'true' && { + // icon: 'edit', + name: t('Section_Move_Up'), + action: onMoveSectionClick('up', event.currentTarget.dataset.section), + }, + event.currentTarget.dataset.last !== 'true' && { + // icon: 'edit', + name: t('Section_Move_Down'), + action: onMoveSectionClick('down', event.currentTarget.dataset.section), + }, + { + // icon: 'edit', + name: t('Section_Delete'), + action: openConfirmDeleteSection(event.currentTarget.dataset.section) + }, + ] + const config = { + columns: [ + { + groups: [ + { items } + ] + } + ], + currentTarget: event.currentTarget, + offsetVertical: 10, + }; + popover.open(config); + }, []) + + const onDeleteItemConfirm = useCallback((sectionId, _id) => async () => { + try { + await deleteItem(protocolId, sectionId, _id); + setModal(() => { setModal(undefined); onChange(); }}/>); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + onChange(); + } + }, [deleteItem, dispatchToastMessage, onChange]); + + const openConfirmDeleteItem = (sectionId, itemId) => () => setModal(() => setModal(undefined)}/>); + + const onMoveItemClick = useCallback((direction, sectionId, _id) => async () => { + try { + const moved = await moveItem(direction, protocolId, sectionId, _id); + if (moved) { + dispatchToastMessage({type: 'success', message: t('Item_Moved_Successfully')}); + onChange(); + } + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + onChange(); + } + }, [moveItem, dispatchToastMessage, onChange]); + + const onItemMenuClick = useCallback((event) => { + const items = [ + { + // icon: 'edit', + name: t('Item_Edit'), + action: onEditItemClick(event.currentTarget.dataset.section, event.currentTarget.dataset.item), + }, + event.currentTarget.dataset.first !== 'true' && { + // icon: 'edit', + name: t('Item_Move_Up'), + action: onMoveItemClick('up', event.currentTarget.dataset.section, event.currentTarget.dataset.item), + }, + event.currentTarget.dataset.last !== 'true' && { + // icon: 'edit', + name: t('Item_Move_Down'), + action: onMoveItemClick('down', event.currentTarget.dataset.section, event.currentTarget.dataset.item), + }, + { + // icon: 'edit', + name: t('Item_Delete'), + action: openConfirmDeleteItem(event.currentTarget.dataset.section, event.currentTarget.dataset.item) + }, + ] + const config = { + columns: [ + { + groups: [ + { items } + ] + } + ], + currentTarget: event.currentTarget, + offsetVertical: 10, + }; + popover.open(config); + }, []) + const goBack = () => { window.history.back(); }; @@ -117,13 +281,16 @@ export function ProtocolPage() { - - + + + {title} @@ -131,11 +298,7 @@ export function ProtocolPage() { {data.place} - - + @@ -151,8 +314,8 @@ export function ProtocolPage() { { context === 'create-participant' && t('Participant_Create') } - {context === 'new-section' && } - {context === 'new-item' && } + {context === 'new-section' && } + {context === 'new-item' && } {context === 'edit-section' && } {context === 'edit-item' && } {context === 'participants' && } diff --git a/app/protocols/client/views/Sections.js b/app/protocols/client/views/Sections.js index 2174c534f2495..89f8fd6d567a6 100644 --- a/app/protocols/client/views/Sections.js +++ b/app/protocols/client/views/Sections.js @@ -1,26 +1,41 @@ import React from 'react'; -import {Box, Button, Scrollable, Tile} from '@rocket.chat/fuselage'; +import { Box, Icon } from '@rocket.chat/fuselage'; import { useTranslation } from '../../../../client/contexts/TranslationContext'; import { useFormatDate } from '../../../../client/hooks/useFormatDate'; +import { css } from '@rocket.chat/css-in-js'; -export function Sections({ data, onSectionClick, onAddItemClick, onItemClick }) { +const clickable = css` + cursor: pointer; + // border-bottom: 2px solid #F2F3F5 !important; + + &:hover, + &:focus { + background: #F7F8FA; + } + `; + +export function Sections({ data, onSectionMenuClick, onItemMenuClick }) { const t = useTranslation(); const formatDate = useFormatDate(); const Item = (item) => <> {item.num}. - + { item.responsible && {t('Item_Responsible')}: {item.responsible} } { item.expireAt && {t('Item_ExpireAt')}: {formatDate(item.expireAt)} } + + + ; @@ -28,24 +43,30 @@ export function Sections({ data, onSectionClick, onAddItemClick, onItemClick }) {section.num}. - + + + + + + {( section.items - ? section.items.map((props, index) => ) + ? section.items.map((props, index) => ) : <> )} - ; return <> @@ -55,7 +76,10 @@ export function Sections({ data, onSectionClick, onAddItemClick, onItemClick }) {( data - ? data.map((props, index) =>
) + ? data.map((props, index) =>
) : <> )} diff --git a/app/protocols/client/views/participants/Participants.js b/app/protocols/client/views/participants/Participants.js index 0a8504bbf5678..8125a217c0ed2 100644 --- a/app/protocols/client/views/participants/Participants.js +++ b/app/protocols/client/views/participants/Participants.js @@ -72,7 +72,6 @@ const SuccessModal = ({ onClose, ...props }) => { }; export function Participants({ protocolId, onAddParticipantClick }) { - console.log('Participants') const [params, setParams] = useState({ _id: protocolId, current: 0, itemsPerPage: 25 }); const [sort, setSort] = useState(['surname', 'asc']); const [cache, setCache] = useState(); @@ -92,7 +91,6 @@ export function Participants({ protocolId, onAddParticipantClick }) { }; function ParticipantsWithData({ data, protocolId, onAddClick, onChange }) { - console.log('WithData') const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/app/protocols/server/index.js b/app/protocols/server/index.js index b79086030eff0..61d059b7469e1 100644 --- a/app/protocols/server/index.js +++ b/app/protocols/server/index.js @@ -6,4 +6,5 @@ import './methods/deleteSection'; import './methods/deleteItem'; import './methods/addParticipantToProtocol'; import './methods/deleteParticipantFromProtocol'; -import './methods/downloadProtocolParticipants'; +import './methods/moveSection'; +import './methods/moveItem'; diff --git a/app/protocols/server/methods/downloadProtocolParticipants.js b/app/protocols/server/methods/downloadProtocolParticipants.js deleted file mode 100644 index b9955509a617a..0000000000000 --- a/app/protocols/server/methods/downloadProtocolParticipants.js +++ /dev/null @@ -1,223 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import s from 'underscore.string'; -import { - AlignmentType, - Document, - HeadingLevel, - Packer, - PageOrientation, - Paragraph, - Table, - TableCell, - TableRow, - TextRun, VerticalAlign, WidthType, -} from 'docx'; -import moment from 'moment'; - -import { hasPermission } from '../../../authorization'; -import { Protocols } from '../../../models'; - -Meteor.methods({ - async downloadProtocolParticipants({ _id }) { - if (!hasPermission(this.userId, 'manage-protocols')) { - throw new Meteor.Error('not_authorized'); - } - - if (!_id) { - throw new Meteor.Error('error-the-field-is-required', 'The field _id is required', { method: 'downloadProtocolParticipants', field: '_id' }); - } - - const protocol = Protocols.findOne({ _id }); - - if (!protocol) { - throw new Meteor.Error('error-protocol-does-not-exists', `The protocol with _id: ${ _id } doesn't exist`, { method: 'downloadProtocolParticipants', field: '_id' }); - } - - const doc = new Document(); - - let usersRows = [ - new TableRow({ - tableHeader: true, - children: [ - new TableCell({ - children: [new Paragraph({ text: '№', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 5, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: 'Участник', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: 'Должность с указанием названия организации', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: 'Контактное лицо', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: 'Электронная почта', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: 'Номер телефона', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: 'Заявлен', bold: true, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - ], - }), - ]; - if (protocol.invitedUsers) { - usersRows = usersRows.concat(protocol.invitedUsers.map((value, index) => { - const contactFace = value.contactPersonFirstName ? `${ value.contactPersonLastName } ${ value.contactPersonFirstName } ${ value.contactPersonPatronymicName }`.trim() : '-'; - return new TableRow({ - children: [ - new TableCell({ - children: [new Paragraph({ text: `${ index + 1 }`, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 5, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: `${ value.lastName } ${ value.firstName } ${ value.patronymic }`.trim(), alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: `${ value.position }`, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: contactFace, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: `${ value.email }`, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: `${ value.phone }`, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - new TableCell({ - children: [new Paragraph({ text: `${ moment(new Date(value.ts)).format('DD MMMM YYYY, HH:mm') }`, alignment: AlignmentType.CENTER })], - verticalAlign: VerticalAlign.CENTER, - alignment: AlignmentType.CENTER, - width: { - size: 19, - type: WidthType.PERCENTAGE, - }, - }), - ], - }); - }, - ), - ); - } - - - doc.addSection({ - size: { - orientation: PageOrientation.LANDSCAPE, - }, - children: [ - new Paragraph({ - children: [ - new TextRun({ - text: `Отчет сформирован ${ moment(new Date()).format('DD MMMM YYYY, HH:mm') }`, - }), - ], - alignment: AlignmentType.RIGHT, - }), - new Paragraph({ - children: [ - new TextRun({ - text: 'Совещание', - }), - ], - heading: HeadingLevel.HEADING_1, - alignment: AlignmentType.CENTER, - }), - new Paragraph({ - children: [ - new TextRun({ - text: `От ${ moment(protocol.d).format('DD MMMM YYYY, HH:mm') }`, - }), - ], - heading: HeadingLevel.HEADING_2, - alignment: AlignmentType.CENTER, - }), - new Table({ - rows: usersRows, - width: { - size: 100, - type: WidthType.PERCENTAGE, - }, - cantSplit: true, - }), - ], - }); - - const buffer = await Packer.toBuffer(doc); - - return buffer; - }, -}); diff --git a/app/protocols/server/methods/moveItem.js b/app/protocols/server/methods/moveItem.js new file mode 100644 index 0000000000000..df536b9dd8950 --- /dev/null +++ b/app/protocols/server/methods/moveItem.js @@ -0,0 +1,86 @@ +import { Meteor } from 'meteor/meteor'; + +import { hasPermission } from '../../../authorization'; +import { Protocols } from '../../../models'; + +const sortItems = (a, b) => { + if (a.inum > b.inum) { + return 1; + } + if (a.inum < b.inum) { + return -1; + } + return 0; +} + +Meteor.methods({ + moveItem(direction, protocolId, sectionId, _id) { + let protocol = null; + + if (hasPermission(this.userId, 'manage-protocols')) { + protocol = Protocols.findOneById(protocolId); + } else { + throw new Meteor.Error('not_authorized'); + } + + if (protocol == null) { + throw new Meteor.Error('Protocol_Error_Invalid_Protocol', 'Invalid protocol', { method: 'moveItem' }); + } + + if (protocol.sections) { + let section; + + protocol.sections.forEach((currentSection) => { + if (currentSection._id === sectionId) { + section = currentSection; + } + }); + + if (!section) { + throw new Meteor.Error('Protocol_Error_Invalid_Section', 'Invalid section', { method: 'moveItem' }); + } + + if (section.items) { + let item; + let index; + + section.items.sort((s1, s2) => s1.inum - s2.inum).forEach((currentItem, currentIndex) => { + if (currentItem._id === _id) { + item = currentItem; + index = currentIndex; + } + }); + + if (!item) { + throw new Meteor.Error('Protocol_Error_Invalid_Item', 'Invalid item', { method: 'moveItem' }); + } + + if (direction === 'down') { + if (index < section.items.length - 1) { + const newIndex = section.items[index + 1].inum; + section.items[index + 1].inum = item.inum; + item.inum = newIndex; + section.items.sort(sortItems); + Protocols.updateProtocol(protocolId, protocol); + return true; + } + } else { + if (index > 0) { + const newIndex = section.items[index - 1].inum; + section.items[index - 1].inum = item.inum; + item.inum = newIndex; + section.items.sort(sortItems); + Protocols.updateProtocol(protocolId, protocol); + return true; + } + } + } else { + throw new Meteor.Error('Protocol_Error_Invalid_Item', 'Invalid item', { method: 'moveItem' }); + } + } else { + throw new Meteor.Error('Protocol_Error_Invalid_Section', 'Invalid section', { method: 'moveItem' }); + } + + return false; + }, +}); diff --git a/app/protocols/server/methods/moveSection.js b/app/protocols/server/methods/moveSection.js new file mode 100644 index 0000000000000..d8f394f6274aa --- /dev/null +++ b/app/protocols/server/methods/moveSection.js @@ -0,0 +1,70 @@ +import { Meteor } from 'meteor/meteor'; + +import { hasPermission } from '../../../authorization'; +import { Protocols } from '../../../models'; + +const sortSections = (a, b) => { + if (a.inum > b.inum) { + return 1; + } + if (a.inum < b.inum) { + return -1; + } + return 0; +} + +Meteor.methods({ + moveSection(direction, protocolId, _id) { + let protocol = null; + + if (hasPermission(this.userId, 'manage-protocols')) { + protocol = Protocols.findOneById(protocolId); + } else { + throw new Meteor.Error('not_authorized'); + } + + if (protocol == null) { + throw new Meteor.Error('Protocol_Error_Invalid_Protocol', 'Invalid protocol', { method: 'moveSection' }); + } + + if (protocol.sections) { + let section; + let index; + + protocol.sections.forEach((currentSection, currentIndex) => { + if (currentSection._id === _id) { + section = currentSection; + index = currentIndex; + } + }); + + if (!section) { + throw new Meteor.Error('Protocol_Error_Invalid_Section', 'Invalid section', { method: 'moveSection' }); + } + + if (direction === 'down') { + if (index < protocol.sections.length - 1) { + const newIndex = protocol.sections[index + 1].inum; + protocol.sections[index + 1].inum = section.inum; + section.inum = newIndex; + protocol.sections.sort(sortSections) + Protocols.updateProtocol(protocolId, protocol); + return true; + } + } else { + if (index > 0) { + const newIndex = protocol.sections[index - 1].inum; + protocol.sections[index - 1].inum = section.inum; + section.inum = newIndex; + protocol.sections.sort(sortSections); + Protocols.updateProtocol(protocolId, protocol); + return true; + } + } + } else { + throw new Meteor.Error('Protocol_Error_Invalid_Section', 'Invalid section', { method: 'moveSection' }); + } + + return false; + }, +}); diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index d05bdb0274f86..70f0b0ce338aa 100755 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -1922,10 +1922,15 @@ "italics": "курсив", "Item_Add": "Добавить пункт", "Item_Added_Successfully": "Пункт успешно добавлен", + "Item_Delete": "Удалить пункт", "Item_Delete_Warning": "Удаление пункта не может быть отменено", + "Item_Edit": "Редактировать пункт", "Item_ExpireAt": "Срок", "Item_Has_Been_Deleted": "Пункт успешно удален", "Item_Info": "Информация о пункте", + "Item_Move_Down": "Переместить вниз", + "Item_Move_Up": "Переместить вверх", + "Item_Moved_Successfully": "Пункт успешно перемещен", "Item_Number": "Номер", "Item_Name": "Наименование", "Item_Responsible": "Ответственные", @@ -2980,9 +2985,14 @@ "Secret_token": "Секретный токен", "Section_Add": "Добавить раздел", "Section_Added_Successfully": "Раздел успешно добавлен", + "Section_Delete": "Удалить раздел", "Section_Delete_Warning": "Удаление раздела не может быть отменено", + "Section_Edit": "Редактировать раздел", "Section_Has_Been_Deleted": "Раздел успешно удален", "Section_Info": "Информация о разделе", + "Section_Move_Down": "Переместить вниз", + "Section_Move_Up": "Переместить вверх", + "Section_Moved_Successfully": "Раздел успешно перемещен", "Section_Number": "Номер", "Section_Name": "Наименование", "Security": "Безопасность",