diff --git a/app/api/server/lib/rooms.js b/app/api/server/lib/rooms.js index 93535314e319..12d807f3cf4c 100644 --- a/app/api/server/lib/rooms.js +++ b/app/api/server/lib/rooms.js @@ -94,6 +94,8 @@ export async function findChannelAndPrivateAutocomplete({ uid, selector }) { fields: { _id: 1, name: 1, + t: 1, + avatarETag: 1, }, limit: 10, sort: { diff --git a/ee/app/auditing/client/routes.js b/ee/app/auditing/client/routes.js index 334de697eb2b..3087462f6aeb 100644 --- a/ee/app/auditing/client/routes.js +++ b/ee/app/auditing/client/routes.js @@ -1,16 +1,21 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { BlazeLayout } from 'meteor/kadira:blaze-layout'; +import { createTemplateForComponent } from '../../../../client/reactAdapters'; + +createTemplateForComponent('auditPage', () => import('../../../client/audit/AuditPage')); +createTemplateForComponent('auditLogPage', () => import('../../../client/audit/AuditLogPage')); + FlowRouter.route('/audit', { name: 'audit-home', action() { - BlazeLayout.render('main', { center: 'audit' }); + BlazeLayout.render('main', { center: 'auditPage' }); }, }); FlowRouter.route('/audit-log', { name: 'audit-log', action() { - BlazeLayout.render('main', { center: 'auditLog' }); + BlazeLayout.render('main', { center: 'auditLogPage' }); }, }); diff --git a/ee/app/auditing/client/templates/audit/audit.html b/ee/app/auditing/client/templates/audit/audit.html index b5247322ce60..4e147a15858b 100644 --- a/ee/app/auditing/client/templates/audit/audit.html +++ b/ee/app/auditing/client/templates/audit/audit.html @@ -1,127 +1,17 @@ - - - - diff --git a/ee/app/auditing/client/templates/audit/audit.js b/ee/app/auditing/client/templates/audit/audit.js index 8235c03cd28d..8ff72329cc87 100644 --- a/ee/app/auditing/client/templates/audit/audit.js +++ b/ee/app/auditing/client/templates/audit/audit.js @@ -1,12 +1,10 @@ -import { Blaze } from 'meteor/blaze'; import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import { AutoComplete } from '../../../../../../app/meteor-autocomplete/client'; import { hasAllPermission } from '../../../../../../app/authorization/client'; import { messageContext } from '../../../../../../app/ui-utils/client/lib/messageContext'; -import { call, convertDate, scrollTo } from '../../utils.js'; +import { call } from '../../utils.js'; import './audit.html'; @@ -27,107 +25,8 @@ const loadMessages = async function({ rid, users, startDate, endDate = new Date( } }; -Template.audit.events({ - 'submit form'(e) { - e.preventDefault(); - }, - 'change input[type=date]'(e) { - e.currentTarget.parentElement.parentElement.parentElement.classList.remove('rc-input--error'); - }, - 'change [name=type]'(e, t) { - t.type.set(e.currentTarget.value); - }, - async 'click .js-submit'(e, t) { - const form = e.currentTarget.parentElement; - - const type = t.type.get(); - const result = { type }; - - e.currentTarget.blur(); - - if (type === 'd') { - if (!t.users) { - return form.querySelector('#autocomplete-users').classList.add('rc-input--error'); - } - form.querySelector('#autocomplete-users').classList.remove('rc-input--error'); - result.users = t.users.map((user) => user.username); - } else if (type === 'l') { - if (!t.visitor && !t.agent) { - form.querySelector('#autocomplete-agent').classList.add('rc-input--error'); - form.querySelector('#autocomplete-visitor').classList.add('rc-input--error'); - return; - } - - form.querySelector('#autocomplete-agent').classList.remove('rc-input--error'); - form.querySelector('#autocomplete-visitor').classList.remove('rc-input--error'); - - result.visitor = t.visitor?._id; - result.agent = t.agent?._id; - } else { - if (!t.room || !t.room._id) { - return form.querySelector('#autocomplete-room').classList.add('rc-input--error'); - } - form.querySelector('#autocomplete-room').classList.remove('rc-input--error'); - result.rid = t.room._id; - } - - if (!form.startDate.value) { - return form.startDate.parentElement.parentElement.parentElement.classList.add('rc-input--error'); - } - form.startDate.parentElement.parentElement.parentElement.classList.remove('rc-input--error'); - result.startDate = form.startDate.type === 'date' ? convertDate(form.startDate.value) : form.startDate.value; - - - result.msg = form.msg.value; - - if (!form.endDate.value) { - return form.endDate.parentElement.parentElement.parentElement.classList.add('rc-input--error'); - } - form.endDate.parentElement.parentElement.parentElement.classList.remove('rc-input--error'); - result.endDate = form.endDate.type === 'date' ? convertDate(form.endDate.value) : form.endDate.value; - result.endDate = new Date(result.endDate.getTime() + 86400000); - - await t.loadMessages(result); - - setTimeout(() => { - const offset = $(document.querySelector('.rc-audit-container ul')).offset(); - if (!offset) { - return; - } - scrollTo(document.querySelector('.rc-audit-container'), offset.top - 150, 300); - }, 150); - }, -}); Template.audit.helpers({ - onChange() { - const that = Template.instance(); - return function(value, key) { - that[key] = value; - }; - }, - prepareRoom: () => function(room) { - room.username = room.name; - return room; - }, - modifierUser: () => function(text, filter) { - const f = filter.get(); - return `@${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - }, - nTypeOthers() { - return ['d', 'l'].includes(Template.instance().type.get()); - }, - nTypeDM() { - return Template.instance().type.get() !== 'd'; - }, - nTypeOmni() { - return Template.instance().type.get() !== 'l'; - }, - type() { - return Template.instance().type.get(); - }, isLoading() { return Template.instance().loading.get(); }, @@ -137,14 +36,10 @@ Template.audit.helpers({ hasResults() { return Template.instance().hasResults.get(); }, - agentConditions() { - return { role: 'livechat-agent' }; - }, }); Template.audit.onCreated(async function() { this.messagesContext = new ReactiveVar({}); - this.type = new ReactiveVar(); this.loading = new ReactiveVar(false); this.hasResults = new ReactiveVar(false); @@ -159,243 +54,14 @@ Template.audit.onCreated(async function() { }); this.loadMessages = loadMessages.bind(this); -}); - -const acEvents = (key/* , variable, name*/) => ({ - 'click .rc-popup-list__item'(e, t) { - t[key].onItemClick(this, e); - }, - 'keydown input'(e, t) { - if ([8, 46].includes(e.keyCode) && e.target.value === '') { - const users = t.selected; - const usersArr = users.get(); - usersArr.pop(); - return users.set(usersArr); - } - t[key].onKeyDown(e); - }, - 'keyup input'(e, t) { - t[key].onKeyUp(e); - }, - 'focus input'(e, t) { - t[key].onFocus(e); - }, - 'blur input'(e, t) { - t[key].onBlur(e); - }, -}); - -Template.auditAutocompleteDirectMessage.events({ - ...acEvents('ac', 'selected'), - 'input input'(e, t) { - t.filter.set(e.target.value); - }, - 'click .rc-tags__tag-icon'(e, t) { - t.selected.set(); - }, - 'click .rc-tags__tag'({ target }, t) { - const { onClickTag } = t; - return onClickTag & onClickTag(Blaze.getData(target)); - }, -}); - -Template.auditAutocomplete.events({ - ...acEvents('ac', 'selected'), - 'input input'(e, t) { - t.filter.set(e.target.value); - }, - 'click .rc-tags__tag-icon'(e, t) { - t.selected.set(); - }, - 'click .rc-tags__tag'({ target }, t) { - const { onClickTag } = t; - return onClickTag & onClickTag(Blaze.getData(target)); - }, -}); - -Template.auditAutocompleteDirectMessage.helpers({ - selected() { - const instance = Template.instance(); - const selected = instance.selected.get(); - return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected); - }, - config() { - const { filter } = Template.instance(); - return { - template_item: 'popupList_item_channel', - // noMatchTemplate: Template.roomSearchEmpty, - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier: (text) => (Template.parentData(8).modifier || function(text, filter) { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - })(text, filter), - }; - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, -}); - -Template.auditAutocomplete.helpers({ - selected() { - const instance = Template.instance(); - const selected = instance.selected.get(); - return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected); - }, - config() { - const { filter } = Template.instance(); - return { - template_item: 'popupList_item_channel', - // noMatchTemplate: Template.roomSearchEmpty, - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier: (text) => (Template.parentData(8).modifier || function(text, filter) { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - })(text, filter), - }; - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, -}); - -Template.auditAutocomplete.onRendered(async function() { - const { selected } = this; - - this.ac.element = this.firstNode.querySelector('input'); - this.ac.$element = $(this.ac.element); - - this.ac.$element.on('autocompleteselect', function(e, { item }) { - selected.set(item); - }); -}); - -Template.auditAutocomplete.helpers({ - selected() { - const instance = Template.instance(); - const selected = instance.selected.get(); - return selected && (instance.data.prepare ? instance.data.prepare(selected) : selected); - }, - config() { - const { filter } = Template.instance(); - const { templateItem } = Template.instance().data; - return { - template_item: templateItem || 'popupList_item_channel', - // noMatchTemplate: Template.roomSearchEmpty, - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier: (text) => (Template.parentData(8).modifier || function(text, filter) { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(f), function(part) { - return `${ part }`; - }) }`; - })(text, filter), - }; - }, - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, -}); -const autocompleteConfig = ({ - collection, - endpoint, - field, - term = 'term', -}) => ({ - selector: { - item: '.rc-popup-list__item', - container: '.rc-popup-list__list', - }, - - limit: 10, - inputDelay: 300, - rules: [{ - // @TODO maybe change this 'collection' and/or template - collection, - endpoint, - field, - matchAll: true, - selector(match) { - return { - [term]: match, - }; - }, - sort: field, - }], -}); - -Template.auditAutocomplete.onCreated(function() { - this.filter = new ReactiveVar(''); - this.selected = new ReactiveVar(''); - - this.onClickTag = () => { - this.selected.set(''); - }; - - this.autorun(() => { - const value = this.selected.get(); - this.data.onChange(value, this.data.key); - }); - this.ac = new AutoComplete(autocompleteConfig({ - field: this.data.field, - collection: this.data.collection || 'CachedChannelList', - endpoint: this.data.endpoint || 'rooms.autocomplete.channelAndPrivate', - term: this.data.term || 'term', - })); - this.ac.tmplInst = this; -}); - - -Template.auditAutocompleteDirectMessage.onCreated(function() { - this.filter = new ReactiveVar(''); - this.selected = new ReactiveVar([]); - - this.onClickTag = ({ username }) => { - this.selected.set(this.selected.get().filter((user) => user.username !== username)); - }; - - this.autorun(() => { - const value = this.selected.get(); - this.data.onChange(value, this.data.key); - }); - this.ac = new AutoComplete(autocompleteConfig({ - field: this.data.field, - collection: this.data.collection || 'CachedChannelList', - endpoint: this.data.endpoint || 'rooms.autocomplete.channelAndPrivate', - term: this.data.term || 'term', - })); - this.ac.tmplInst = this; -}); - -Template.auditAutocompleteDirectMessage.onRendered(async function() { - const { selected } = this; - - this.ac.element = this.firstNode.querySelector('input'); - this.ac.$element = $(this.ac.element); - - this.ac.$element.on('autocompleteselect', function(e, { item }) { - selected.set([...selected.get(), item]); - }); + const { + visitor, + agent, + users, + rid, + } = this.data; + if (rid || users.length || agent || visitor) { + await this.loadMessages(this.data); + } }); diff --git a/ee/app/auditing/client/templates/auditLog/auditLog.html b/ee/app/auditing/client/templates/auditLog/auditLog.html deleted file mode 100644 index 22082570c5cc..000000000000 --- a/ee/app/auditing/client/templates/auditLog/auditLog.html +++ /dev/null @@ -1,74 +0,0 @@ - - - diff --git a/ee/app/auditing/client/templates/auditLog/auditLog.js b/ee/app/auditing/client/templates/auditLog/auditLog.js deleted file mode 100644 index 902dfd4228c2..000000000000 --- a/ee/app/auditing/client/templates/auditLog/auditLog.js +++ /dev/null @@ -1,73 +0,0 @@ -import moment from 'moment'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import { FlowRouter } from 'meteor/kadira:flow-router'; - -import { hasAllPermission } from '../../../../../../app/authorization'; -import { call, convertDate } from '../../utils.js'; - -import './auditLog.html'; - -const loadLog = async function({ startDate, endDate = new Date() }) { - this.logs = this.logs || new ReactiveVar([]); - this.loading = this.loading || new ReactiveVar(false); - if (this.loading.get() === true) { - return; - } - this.loading.set(true); - try { - const logs = await call('auditGetAuditions', { startDate, endDate }); - this.logs.set(logs); - } catch (e) { - this.logs.set([]); - } finally { - this.loading.set(false); - } -}; - -Template.auditLog.events({ - 'click button'(e, t) { - const form = e.currentTarget.parentElement; - t.loadLog({ - startDate: convertDate(form.startDate.value), - endDate: new Date(convertDate(form.endDate.value).getTime() + 86400000), - }); - }, -}); - -Template.auditLog.helpers({ - logs() { - return Template.instance().logs.get(); - }, -}); - -Template.auditLog.onRendered(function() { - this.loadLog = loadLog.bind(this); -}); - -Template.auditLog.onCreated(function() { - this.logs = new ReactiveVar([]); - - if (!hasAllPermission('can-audit-log')) { - return FlowRouter.go('/home'); - } -}); - -Template.auditLogItem.helpers({ - msg() { - return this.fields.msg; - }, - username() { - return this.u.username; - }, - ts() { - return moment(this.ts).format('lll'); - }, - fields() { - const { fields } = this; - - const from = fields.users ? `@${ fields.users[0] } : @${ fields.users[1] }` : `#${ fields.room }`; - - return `${ from }

${ moment(fields.startDate).format('DD/MM/YYYY') } to ${ moment(fields.endDate).format('DD/MM/YYYY') }

`; - }, -}); diff --git a/ee/app/auditing/client/templates/index.js b/ee/app/auditing/client/templates/index.js index b6bc546b034e..8e4cbfbcf7e6 100644 --- a/ee/app/auditing/client/templates/index.js +++ b/ee/app/auditing/client/templates/index.js @@ -1,2 +1 @@ import './audit/audit.js'; -import './auditLog/auditLog.js'; diff --git a/ee/client/audit/AuditLogPage.js b/ee/client/audit/AuditLogPage.js new file mode 100644 index 000000000000..0e2e3ea60119 --- /dev/null +++ b/ee/client/audit/AuditLogPage.js @@ -0,0 +1,44 @@ +import React, { useMemo, useState } from 'react'; +import { Field } from '@rocket.chat/fuselage'; + +import Page from '../../../client/components/basic/Page'; +import DateRangePicker from './DateRangePicker'; +import AuditLogTable from './AuditLogTable'; +import { useTranslation } from '../../../client/contexts/TranslationContext'; +import { useMethodData } from '../../../client/contexts/ServerContext'; + +const AuditLogPage = () => { + const t = useTranslation(); + + const [dateRange, setDateRange] = useState({ + start: '', + end: '', + }); + + const { + start, + end, + } = dateRange; + + const params = useMemo(() => [{ + startDate: new Date(start), + endDate: new Date(end), + }], [end, start]); + + const [data] = useMethodData('auditGetAuditions', params); + + return + + + + {t('Date')} + + + + + + + ; +}; + +export default AuditLogPage; diff --git a/ee/client/audit/AuditLogTable.js b/ee/client/audit/AuditLogTable.js new file mode 100644 index 000000000000..e1b06459b537 --- /dev/null +++ b/ee/client/audit/AuditLogTable.js @@ -0,0 +1,92 @@ +import React, { useMemo } from 'react'; +import { Box, Table } from '@rocket.chat/fuselage'; + +import UserAvatar from '../../../client/components/basic/avatar/UserAvatar'; +import { GenericTable } from '../../../client/components/GenericTable'; +import { useTranslation } from '../../../client/contexts/TranslationContext'; +import { useFormatDateAndTime } from '../../../client/hooks/useFormatDateAndTime'; +import { useFormatDate } from '../../../client/hooks/useFormatDate'; + + +const FilterDisplay = ({ users, room, startDate, endDate, t }) => + + {users ? `@${ users[0] } : @${ users[1] }` : `#${ room }`} + + + {startDate} {t('to')} {endDate} + +; + +const UserRow = React.memo(({ u, results, ts, _id, formatDateAndTime, formatDate, fields, mediaQuery }) => { + const t = useTranslation(); + + const { + username, + name, + avatarETag, + } = u; + + const { + msg, + users, + room, + startDate, + endDate, + } = fields; + + const when = useMemo(() => formatDateAndTime(ts), [formatDateAndTime, ts]); + + return + + + + + + {name || username} + {name && {`@${ username }`} } + + + + + + { msg } + + {when} + {results} + + + + ; +}); + +export function AuditLogTable({ data }) { + const t = useTranslation(); + + const formatDateAndTime = useFormatDateAndTime(); + const formatDate = useFormatDate(); + + return + + {t('Username')} + + + {t('Looked_for')} + + + {t('When')} + + + {t('Results')} + + + {t('Filters_applied')} + + } + results={data} + > + {(props) => } + ; +} + +export default AuditLogTable; diff --git a/ee/client/audit/AuditPage.js b/ee/client/audit/AuditPage.js new file mode 100644 index 000000000000..660c7debda7b --- /dev/null +++ b/ee/client/audit/AuditPage.js @@ -0,0 +1,191 @@ +import React, { useRef, useState } from 'react'; +import { Box, Field, TextInput, ButtonGroup, Button, Margins, Tabs } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import Page from '../../../client/components/basic/Page'; +import DateRangePicker from './DateRangePicker'; +import RoomAutoComplete from './RoomAutoComplete'; +import UserAutoCompleteMultiple from './UserAutoCompleteMultiple'; +import VisitorAutoComplete from './VisitorAutoComplete'; +import Result from './Result'; +import { AutoCompleteAgent } from '../../../client/components/basic/AutoCompleteAgent'; +import { useTranslation } from '../../../client/contexts/TranslationContext'; +import { useForm } from '../../../client/hooks/useForm'; + +const initialValues = { + msg: '', + type: '', + dateRange: { + start: '', + end: '', + }, + visitor: '', + agent: '', + rid: '', + users: [], +}; + +const AuditPage = () => { + const t = useTranslation(); + + const { values, handlers } = useForm(initialValues); + const setData = useRef(() => {}); + + const [errors, setErrors] = useState({}); + + const { + msg, + type, + dateRange: { + start: startDate, + end: endDate, + }, + visitor, + agent, + users, + rid, + } = values; + + const { + handleMsg, + handleType, + handleVisitor, + handleAgent, + handleUsers, + handleRid, + handleDateRange, + } = handlers; + + const useHandleType = (type) => useMutableCallback(() => { + handleType(type); + }); + + const onChangeUsers = useMutableCallback((value, action) => { + if (!action) { + if (users.includes(value)) { + return; + } + return handleUsers([...users, value]); + } + handleUsers(users.filter((current) => current !== value)); + }); + + const apply = useMutableCallback(() => { + if (!rid && type === '') { + return setErrors({ + rid: t('The_field_is_required', t('room_name')), + }); + } + + if (users.length < 2 && type === 'd') { + return setErrors({ + users: t('Select_at_least_two_users'), + }); + } + + if (type === 'l') { + const errors = {}; + + if (visitor === '') { + errors.visitor = t('The_field_is_required', t('Visitor')); + } + + if (agent === '') { + errors.visitor = t('The_field_is_required', t('Agent')); + } + + if (errors.visitor || errors.agent) { + return setErrors(errors); + } + } + + setErrors({}); + setData.current({ + msg, + type, + startDate: new Date(startDate), + endDate: new Date(endDate), + visitor, + agent, + users, + rid, + }); + }); + + return + + + {t('Others')} + {t('Direct_Messages')} + {t('Omnichannel')} + + + + + + + {t('Message')} + + + + + + {t('Date')} + + + + + + + + {type === '' && + {t('room_name')} + + + + {errors.rid && + {errors.rid} + } + } + {type === 'd' && + {t('Users')} + + + + {errors.users && + {errors.users} + } + } + {type === 'l' && + + + {t('Visitor')} + + + + {errors.visitor && + {errors.visitor} + } + + + {t('Agent')} + + + + {errors.agent && + {errors.agent} + } + + + } + + + + + + + + ; +}; + +export default AuditPage; diff --git a/ee/client/audit/AuditPage.stories.js b/ee/client/audit/AuditPage.stories.js new file mode 100644 index 000000000000..d97a3e618a38 --- /dev/null +++ b/ee/client/audit/AuditPage.stories.js @@ -0,0 +1,11 @@ +import React from 'react'; + +import AuditPage from './AuditPage'; + +export default { + title: 'ee/Audit', + component: AuditPage, +}; + + +export const Default = () => ; diff --git a/ee/client/audit/DateRangePicker.js b/ee/client/audit/DateRangePicker.js new file mode 100644 index 000000000000..16b64373102e --- /dev/null +++ b/ee/client/audit/DateRangePicker.js @@ -0,0 +1,122 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import { Box, InputBox, Menu, Margins } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../client/contexts/TranslationContext'; + +const date = new Date(); + +const formatToDateInput = (date) => date.toISOString().slice(0, 10); + +const todayDate = formatToDateInput(date); + +const getMonthRange = (monthsToSubtractFromToday) => { + const date = new Date(); + return { + start: formatToDateInput(new Date( + date.getFullYear(), + date.getMonth() - monthsToSubtractFromToday, + 1)), + end: formatToDateInput(new Date( + date.getFullYear(), + date.getMonth() - monthsToSubtractFromToday + 1, + 0)), + }; +}; + +const getWeekRange = (daysToSubtractFromStart, daysToSubtractFromEnd) => { + const date = new Date(); + return { + start: formatToDateInput(new Date( + date.getFullYear(), + date.getMonth(), + date.getDate() - daysToSubtractFromStart)), + end: formatToDateInput(new Date( + date.getFullYear(), + date.getMonth(), + date.getDate() - daysToSubtractFromEnd)), + }; +}; + +const DateRangePicker = ({ onChange = () => {}, ...props }) => { + const t = useTranslation(); + const [range, setRange] = useState({ start: '', end: '' }); + + const { + start, + end, + } = range; + + const handleStart = useMutableCallback(({ currentTarget }) => { + const rangeObj = { + start: currentTarget.value, + end: range.end, + }; + setRange(rangeObj); + onChange(rangeObj); + }); + + const handleEnd = useMutableCallback(({ currentTarget }) => { + const rangeObj = { + end: currentTarget.value, + start: range.start, + }; + setRange(rangeObj); + onChange(rangeObj); + }); + + const handleRange = useMutableCallback((range) => { + setRange(range); + onChange(range); + }); + + useEffect(() => { + handleRange({ + start: todayDate, + end: todayDate, + }); + }, [handleRange]); + + const options = useMemo(() => ({ + today: { + icon: 'history', + label: t('Today'), + action: () => { handleRange(getWeekRange(0, 0)); }, + }, + yesterday: { + icon: 'history', + label: t('Yesterday'), + action: () => { handleRange(getWeekRange(1, 1)); }, + }, + thisWeek: { + icon: 'history', + label: t('This_week'), + action: () => { handleRange(getWeekRange(7, 0)); }, + }, + previousWeek: { + icon: 'history', + label: t('Previous_week'), + action: () => { handleRange(getWeekRange(14, 7)); }, + }, + thisMonth: { + icon: 'history', + label: t('This_month'), + action: () => { handleRange(getMonthRange(0)); }, + }, + lastMonth: { + icon: 'history', + label: t('Previous_month'), + action: () => { handleRange(getMonthRange(1)); }, + }, + }), [handleRange, t]); + + return + + + + + + ; +}; + +export default DateRangePicker; diff --git a/ee/client/audit/Result.js b/ee/client/audit/Result.js new file mode 100644 index 000000000000..86f3ea5b87a8 --- /dev/null +++ b/ee/client/audit/Result.js @@ -0,0 +1,48 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { Box } from '@rocket.chat/fuselage'; +import { useStableArray } from '@rocket.chat/fuselage-hooks'; +import { Template } from 'meteor/templating'; +import { Blaze } from 'meteor/blaze'; + + +import '../../app/auditing/client/templates/audit/audit.html'; + +const Result = React.memo(({ setDataRef }) => { + const ref = useRef(); + + const [data, setData] = useState({}); + + const { + msg, + type, + startDate, + endDate, + visitor, + agent, + users = [], + rid, + } = data; + + const stableUsers = useStableArray(users); + + setDataRef.current = setData; + + useEffect(() => { + const view = Blaze.renderWithData(Template.audit, { + msg, + type, + startDate, + endDate, + visitor, + agent, + users: stableUsers, + rid, + }, ref.current); + + return () => Blaze.remove(view); + }, [agent, endDate, msg, rid, startDate, type, stableUsers, visitor]); + + return ; +}); + +export default Result; diff --git a/ee/client/audit/RoomAutoComplete.js b/ee/client/audit/RoomAutoComplete.js new file mode 100644 index 000000000000..f65b9c673e81 --- /dev/null +++ b/ee/client/audit/RoomAutoComplete.js @@ -0,0 +1,32 @@ +import React, { useMemo, useState } from 'react'; +import { AutoComplete, Option, Options } from '@rocket.chat/fuselage'; + +import { useEndpointDataExperimental } from '../../../client/hooks/useEndpointDataExperimental'; +import RoomAvatar from '../../../client/components/basic/avatar/RoomAvatar'; + +const query = (term = '') => ({ selector: JSON.stringify({ term }) }); + +const Avatar = ({ value, type, avatarETag, ...props }) => ; + +const RoomAutoComplete = React.memo((props) => { + const [filter, setFilter] = useState(''); + const { data } = useEndpointDataExperimental('rooms.autocomplete.channelAndPrivate', useMemo(() => query(filter), [filter])); + const options = useMemo(() => (data && data.items.map(({ name, _id, avatarETag, t }) => ({ + value: _id, + label: { name, avatarETag, type: t }, + }))) || [], [data]); + + return <> {label.name}} + renderItem={({ value, label, ...props }) =>