Merge pull request #28 from ONLYOFFICE/feature/user-actions-changes

Feature/user actions changes
This commit is contained in:
Alexey Safronov 2020-02-26 16:31:02 +03:00 committed by GitHub
commit 07413ece9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1415 additions and 152 deletions

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/DeleteProfileEverDialog/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
},
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,194 @@
import React, { memo } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router";
import PropTypes from "prop-types";
import {
toastr,
ModalDialog,
Button,
Text,
ToggleContent,
Checkbox,
CustomScrollbarsVirtualList
} from "asc-web-components";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import { withTranslation } from "react-i18next";
import i18n from "./i18n";
import { utils } from "asc-web-common";
import ModalDialogContainer from "../ModalDialogContainer";
import { updateUserStatus } from "../../../store/people/actions";
const { changeLanguage } = utils;
class ChangeUserStatusDialogComponent extends React.Component {
constructor(props) {
super(props);
const { userIds, selectedUsers } = props;
changeLanguage(i18n);
const listUsers = selectedUsers.map((item, index) => {
const disabled = userIds.find(x => x === item.id);
return (selectedUsers[index] = {
...selectedUsers[index],
checked: disabled ? true : false,
disabled: disabled ? false : true
});
});
this.state = { isRequestRunning: false, listUsers, userIds };
}
onChangeUserStatus = () => {
const {
updateUserStatus,
userStatus,
t,
setSelected,
onClose
} = this.props;
const { userIds } = this.state;
this.setState({ isRequestRunning: true }, () => {
updateUserStatus(userStatus, userIds, true)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch(error => toastr.error(error))
.finally(() => {
this.setState({ isRequestRunning: false }, () => {
setSelected("close");
onClose();
});
});
});
};
onChange = e => {
const { listUsers } = this.state;
const userIndex = listUsers.findIndex(x => x.id === e.target.value);
const newUsersList = listUsers;
newUsersList[userIndex].checked = !newUsersList[userIndex].checked;
const newUserIds = [];
for (let item of newUsersList) {
if (item.checked === true) {
newUserIds.push(item.id);
}
}
this.setState({ listUsers: newUsersList, userIds: newUserIds });
};
render() {
const { t, onClose, visible, userStatus } = this.props;
const { listUsers, isRequestRunning, userIds } = this.state;
const containerStyles = { height: listUsers.length * 25, maxHeight: 220 };
const itemSize = 25;
const renderItems = memo(({ data, index, style }) => {
return (
<Checkbox
truncate
style={style}
className="modal-dialog-checkbox"
value={data[index].id}
onChange={this.onChange}
key={`checkbox_${index}`}
isChecked={data[index].checked}
label={data[index].displayName}
isDisabled={data[index].disabled}
/>
);
}, areEqual);
const renderList = ({ height, width }) => (
<List
className="List"
height={height}
width={width}
itemSize={itemSize}
itemCount={listUsers.length}
itemData={listUsers}
outerElementType={CustomScrollbarsVirtualList}
>
{renderItems}
</List>
);
const statusTranslation =
userStatus === 1
? t("ChangeUsersActiveStatus")
: t("ChangeUsersDisableStatus");
const userStatusTranslation =
userStatus === 1 ? t("DisabledEmployeeTitle") : t("ActiveEmployeeTitle");
return (
<ModalDialogContainer>
<ModalDialog
visible={visible}
onClose={onClose}
headerContent={t("ChangeUserStatusDialogHeader")}
bodyContent={
<>
<Text>
{t("ChangeUserStatusDialog", {
status: statusTranslation,
userStatus: userStatusTranslation
})}
</Text>
<Text>{t("ChangeUserStatusDialogMessage")}</Text>
<ToggleContent
className="toggle-content-dialog"
label={t("ShowUsersList")}
>
<div style={containerStyles} className="modal-dialog-content">
<AutoSizer>{renderList}</AutoSizer>
</div>
</ToggleContent>
</>
}
footerContent={
<>
<Button
label={t("ChangeUsersStatusButton")}
size="medium"
primary
onClick={this.onChangeUserStatus}
isLoading={isRequestRunning}
isDisabled={!userIds.length}
/>
<Button
className="button-dialog"
label={t("CancelButton")}
size="medium"
onClick={onClose}
isDisabled={isRequestRunning}
/>
</>
}
/>
</ModalDialogContainer>
);
}
}
const ChangeUserStatusDialogTranslated = withTranslation()(
ChangeUserStatusDialogComponent
);
const ChangeUserStatusDialog = props => (
<ChangeUserStatusDialogTranslated i18n={i18n} {...props} />
);
ChangeUserStatusDialog.propTypes = {
visible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
setSelected: PropTypes.func.isRequired,
userIds: PropTypes.arrayOf(PropTypes.string).isRequired,
selectedUsers: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default connect(null, { updateUserStatus })(
withRouter(ChangeUserStatusDialog)
);

View File

@ -0,0 +1,14 @@
{
"CancelButton": "Cancel",
"DisabledEmployeeTitle": "Disabled",
"SuccessChangeUserStatus": "The user status was successfully changed",
"ShowUsersList": "View users list",
"ChangeUserStatusDialogHeader": "Change user status",
"ChangeUserStatusDialog": "The users with the '{{ status }}' status will be disabled.",
"ChangeUserStatusDialogMessage": "You cannot change the status for portal owner and for yourself",
"ChangeUsersStatusButton": "Change user status",
"ActiveEmployeeTitle": "Active",
"ChangeUsersActiveStatus": "disabled",
"ChangeUsersDisableStatus": "enabled"
}

View File

@ -0,0 +1,14 @@
{
"CancelButton": "Отмена",
"DisabledEmployeeTitle": "Заблокирован",
"SuccessChangeUserStatus": "Статус пользователя успешно изменен",
"ShowUsersList": "Показать список пользователей",
"ChangeUserStatusDialogHeader": "Изменение статуса пользователя",
"ChangeUserStatusDialog": "Пользователи со статусом '{{ userStatus }}' будут {{ status }}.",
"ChangeUserStatusDialogMessage": "Вы не можете изменить статус владельца портала и свой собственный статус",
"ChangeUsersStatusButton": "Изменить статус",
"ActiveEmployeeTitle": "Активный",
"ChangeUsersDisableStatus": "заблокированы",
"ChangeUsersActiveStatus": "разблокированы"
}

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/DeleteProfileEverDialog/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
},
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,183 @@
import React, { memo } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router";
import PropTypes from "prop-types";
import {
toastr,
ModalDialog,
Button,
Text,
ToggleContent,
Checkbox,
CustomScrollbarsVirtualList
} from "asc-web-components";
import { withTranslation } from "react-i18next";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import i18n from "./i18n";
import { utils } from "asc-web-common";
import ModalDialogContainer from "../ModalDialogContainer";
import { updateUserType } from "../../../store/people/actions";
const { changeLanguage } = utils;
class ChangeUserTypeDialogComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
const { selectedUsers, userIds } = props;
const listUsers = selectedUsers.map((item, index) => {
const disabled = userIds.find(x => x === item.id);
return (selectedUsers[index] = {
...selectedUsers[index],
checked: disabled ? true : false,
disabled: disabled ? false : true
});
});
this.state = { isRequestRunning: false, userIds, listUsers };
}
onChange = e => {
const { listUsers } = this.state;
const userIndex = listUsers.findIndex(x => x.id === e.target.value);
const newUsersList = listUsers;
newUsersList[userIndex].checked = !newUsersList[userIndex].checked;
const newUserIds = [];
for (let item of newUsersList) {
if (item.checked === true) {
newUserIds.push(item.id);
}
}
this.setState({ listUsers: newUsersList, userIds: newUserIds });
};
onChangeUserType = () => {
const { onClose, setSelected, t, userType, updateUserType } = this.props;
const { userIds } = this.state;
this.setState({ isRequestRunning: true }, () => {
updateUserType(userType, userIds)
.then(() => toastr.success(t("SuccessChangeUserType")))
.catch(error => toastr.error(error))
.finally(() => {
this.setState({ isRequestRunning: false }, () => {
setSelected("close");
onClose();
});
});
});
};
render() {
const { visible, onClose, t, userType } = this.props;
const { isRequestRunning, listUsers, userIds } = this.state;
const itemSize = 25;
const containerStyles = { height: listUsers.length * 25, maxHeight: 220 };
const renderItems = memo(({ data, index, style }) => {
return (
<Checkbox
truncate
style={style}
className="modal-dialog-checkbox"
value={data[index].id}
onChange={this.onChange}
key={`checkbox_${index}`}
isChecked={data[index].checked}
label={data[index].displayName}
isDisabled={data[index].disabled}
/>
);
}, areEqual);
const renderList = ({ height, width }) => (
<List
className="List"
height={height}
width={width}
itemSize={itemSize}
itemCount={listUsers.length}
itemData={listUsers}
outerElementType={CustomScrollbarsVirtualList}
>
{renderItems}
</List>
);
const firstType = userType === 1 ? t("GuestCaption") : t("UserCol");
const secondType = userType === 1 ? t("UserCol") : t("GuestCaption");
return (
<ModalDialogContainer>
<ModalDialog
visible={visible}
onClose={onClose}
headerContent={t("ChangeUserTypeHeader")}
bodyContent={
<>
<Text>
{t("ChangeUserTypeMessage", {
firstType: firstType,
secondType: secondType
})}
</Text>
<Text>{t("ChangeUserTypeMessageWarning")}</Text>
<ToggleContent
className="toggle-content-dialog"
label={t("ShowUsersList")}
>
<div style={containerStyles} className="modal-dialog-content">
<AutoSizer>{renderList}</AutoSizer>
</div>
</ToggleContent>
</>
}
footerContent={
<>
<Button
label={t("ChangeUserTypeButton")}
size="medium"
primary
onClick={this.onChangeUserType}
isLoading={isRequestRunning}
isDisabled={!userIds.length}
/>
<Button
className="button-dialog"
label={t("CancelButton")}
size="medium"
onClick={onClose}
isDisabled={isRequestRunning}
/>
</>
}
/>
</ModalDialogContainer>
);
}
}
const ChangeUserTypeDialogTranslated = withTranslation()(
ChangeUserTypeDialogComponent
);
const ChangeUserTypeDialog = props => (
<ChangeUserTypeDialogTranslated i18n={i18n} {...props} />
);
ChangeUserTypeDialog.propTypes = {
visible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
setSelected: PropTypes.func.isRequired,
userIds: PropTypes.arrayOf(PropTypes.string).isRequired,
selectedUsers: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default connect(null, { updateUserType })(
withRouter(ChangeUserTypeDialog)
);

View File

@ -0,0 +1,12 @@
{
"GuestCaption": "Guest",
"UserCol": "User",
"CancelButton": "Cancel",
"SuccessChangeUserType": "The user type was successfully changed",
"ChangeUserTypeHeader": "Change user type",
"ChangeUserTypeMessage": "Users with the '{{ firstType }}' type will be moved to '{{ secondType }}' type.",
"ChangeUserTypeMessageWarning": "You cannot change the type for portal administrators and for yourself",
"ChangeUserTypeButton": "Change type",
"ShowUsersList": "View users list"
}

View File

@ -0,0 +1,12 @@
{
"CancelButton": "Отмена",
"GuestCaption": "Гость",
"UserCol": "Пользователь",
"SuccessChangeUserType": "Тип пользователя успешно изменен",
"ChangeUserTypeHeader": "Изменение типа пользователя",
"ChangeUserTypeButton": "Изменить тип",
"ChangeUserTypeMessage": "Пользователи с типом '{{ firstType }}' будут переведены в тип '{{ secondType }}'.",
"ChangeUserTypeMessageWarning": "Вы не можете изменить тип для администраторов портала и для самого себя",
"ShowUsersList": "Показать список пользователей"
}

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/DeleteProfileEverDialog/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
},
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,188 @@
import React, { memo } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router";
import PropTypes from "prop-types";
import {
toastr,
ModalDialog,
Button,
Text,
ToggleContent,
Checkbox,
CustomScrollbarsVirtualList
} from "asc-web-components";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import { withTranslation } from "react-i18next";
import i18n from "./i18n";
import { api, utils } from "asc-web-common";
import { removeUser } from "../../../store/people/actions";
import ModalDialogContainer from "../ModalDialogContainer";
const { Filter } = api;
const { changeLanguage } = utils;
class DeleteGroupUsersDialogComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
const { selectedUsers, userIds } = props;
const listUsers = selectedUsers.map((item, index) => {
const disabled = userIds.find(x => x === item.id);
return (selectedUsers[index] = {
...selectedUsers[index],
checked: disabled ? true : false,
disabled: disabled ? false : true
});
});
this.state = { isRequestRunning: false, listUsers, userIds };
}
onDeleteGroupUsers = () => {
const { removeUser, t, setSelected, onClose, filter } = this.props;
const { userIds } = this.state;
this.setState({ isRequestRunning: true }, () => {
removeUser(userIds, filter)
.then(() => {
toastr.success(t("DeleteGroupUsersSuccessMessage"));
})
.catch(error => toastr.error(error))
.finally(() => {
this.setState({ isRequestRunning: false }, () => {
setSelected("close");
onClose();
});
});
});
};
onChange = e => {
const userIndex = this.state.listUsers.findIndex(
x => x.id === e.target.value
);
const newUsersList = this.state.listUsers;
newUsersList[userIndex].checked = !newUsersList[userIndex].checked;
const newUserIds = [];
for (let item of newUsersList) {
if (item.checked === true) {
newUserIds.push(item.id);
}
}
this.setState({ listUsers: newUsersList, userIds: newUserIds });
};
render() {
const { t, onClose, visible } = this.props;
const { isRequestRunning, userIds, listUsers } = this.state;
const itemSize = 25;
const containerStyles = { height: listUsers.length * 25, maxHeight: 220 };
const renderItems = memo(({ data, index, style }) => {
return (
<Checkbox
truncate
style={style}
className="modal-dialog-checkbox"
value={data[index].id}
onChange={this.onChange}
key={`checkbox_${index}`}
isChecked={data[index].checked}
label={data[index].displayName}
isDisabled={data[index].disabled}
/>
);
}, areEqual);
const renderList = ({ height, width }) => (
<List
className="List"
height={height}
width={width}
itemSize={itemSize}
itemCount={listUsers.length}
itemData={listUsers}
outerElementType={CustomScrollbarsVirtualList}
>
{renderItems}
</List>
);
//console.log("DeleteGroupUsersDialog render");
return (
<ModalDialogContainer>
<ModalDialog
visible={visible}
onClose={onClose}
headerContent={t("DeleteGroupUsersMessageHeader")}
bodyContent={
<>
<Text>{t("DeleteGroupUsersMessage")}</Text>
<Text>{t("NotBeUndone")}</Text>
<br />
<Text color="#c30" fontSize="18px">
{t("Warning")}
</Text>
<br />
<Text>{t("DeleteUserDataConfirmation")}</Text>
<ToggleContent
className="toggle-content-dialog"
label={t("ShowUsersList")}
>
<div style={containerStyles} className="modal-dialog-content">
<AutoSizer>{renderList}</AutoSizer>
</div>
</ToggleContent>
</>
}
footerContent={
<>
<Button
label={t("OKButton")}
size="medium"
primary
onClick={this.onDeleteGroupUsers}
isLoading={isRequestRunning}
isDisabled={!userIds.length}
/>
<Button
className="button-dialog"
label={t("CancelButton")}
size="medium"
onClick={onClose}
isDisabled={isRequestRunning}
/>
</>
}
/>
</ModalDialogContainer>
);
}
}
const DeleteGroupUsersDialogTranslated = withTranslation()(
DeleteGroupUsersDialogComponent
);
const DeleteUsersDialog = props => (
<DeleteGroupUsersDialogTranslated i18n={i18n} {...props} />
);
DeleteUsersDialog.propTypes = {
visible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
setSelected: PropTypes.func.isRequired,
selectedUsers: PropTypes.arrayOf(PropTypes.object).isRequired,
userIds: PropTypes.arrayOf(PropTypes.string).isRequired,
filter: PropTypes.instanceOf(Filter).isRequired,
removeUser: PropTypes.func.isRequired
};
export default connect(null, { removeUser })(withRouter(DeleteUsersDialog));

View File

@ -0,0 +1,12 @@
{
"NotBeUndone": "Note: this action cannot be undone.",
"Warning": "Warning",
"DeleteUserDataConfirmation": "User's personal documents that are available to others will be deleted. To avoid this, you must start the data reassign process before deleting.",
"OKButton": "OK",
"CancelButton": "Cancel",
"DeleteGroupUsersMessage": "The selected disabled users will be deleted from the portal.",
"DeleteGroupUsersMessageHeader": "Delete the users from portal",
"DeleteGroupUsersSuccessMessage": "Users have been successfully deleted.",
"ShowUsersList": "View users list"
}

View File

@ -0,0 +1,13 @@
{
"NotBeUndone": "Внимание: это действие необратимо.",
"Warning": "Внимание!",
"DeleteUserDataConfirmation": "Будут удалены личные документы пользователя, доступные для других. Чтобы избежать этого, нужно перед удалением запустить процесс передачи данных.",
"OKButton": "ОК",
"CancelButton": "Отмена",
"DeleteGroupUsersMessage": "Выбранные заблокированные пользователи будут удалены с портала.",
"DeleteGroupUsersMessageHeader": "Удаление пользователей с портала",
"DeleteGroupUsersSuccessMessage": "Пользователи были успешно удалены",
"ShowUsersList": "Показать список пользователей"
}

View File

@ -1,10 +1,9 @@
import styled from 'styled-components';
import styled from "styled-components";
const ModalDialogContainer = styled.div`
.flex {
display: flex;
justify-content: space-between;
.flex {
display: flex;
justify-content: space-between;
}
.text-dialog {
@ -20,7 +19,7 @@ const ModalDialogContainer = styled.div`
}
.warning-text {
margin: 20px 0;
margin: 20px 0;
}
.textarea-dialog {
@ -38,7 +37,22 @@ const ModalDialogContainer = styled.div`
.field-body {
position: relative;
}
}
.toggle-content-dialog {
.heading-toggle-content {
font-size: 16px;
}
.modal-dialog-content {
padding: 8px 16px;
border: 1px solid lightgray;
.modal-dialog-checkbox:not(:last-child) {
padding-bottom: 4px;
}
}
}
`;
export default ModalDialogContainer;
export default ModalDialogContainer;

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/DeleteProfileEverDialog/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
},
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,178 @@
import React, { memo } from "react";
import { withRouter } from "react-router";
import PropTypes from "prop-types";
import {
toastr,
ModalDialog,
Button,
Text,
ToggleContent,
Checkbox,
CustomScrollbarsVirtualList
} from "asc-web-components";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import { withTranslation } from "react-i18next";
import i18n from "./i18n";
import { api, utils } from "asc-web-common";
import ModalDialogContainer from "../ModalDialogContainer";
const { resendUserInvites } = api.people;
const { changeLanguage } = utils;
class SendInviteDialogComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
const { userIds, selectedUsers } = props;
const listUsers = selectedUsers.map((item, index) => {
const disabled = userIds.find(x => x === item.id);
return (selectedUsers[index] = {
...selectedUsers[index],
checked: disabled ? true : false,
disabled: disabled ? false : true
});
});
this.state = {
listUsers,
isRequestRunning: false,
userIds
};
}
onSendInvite = () => {
const { t, setSelected, onClose } = this.props;
const { userIds } = this.state;
this.setState({ isRequestRunning: true }, () => {
resendUserInvites(userIds)
.then(() => toastr.success(t("SuccessSendInvitation")))
.catch(error => toastr.error(error))
.finally(() => {
this.setState({ isRequestRunning: false }, () => {
setSelected("close");
onClose();
});
});
});
};
onChange = e => {
const userIndex = this.state.listUsers.findIndex(
x => x.id === e.target.value
);
const newUsersList = this.state.listUsers;
newUsersList[userIndex].checked = !newUsersList[userIndex].checked;
const newUserIds = [];
for (let item of newUsersList) {
if (item.checked === true) {
newUserIds.push(item.id);
}
}
this.setState({ listUsers: newUsersList, userIds: newUserIds });
};
render() {
const { t, onClose, visible } = this.props;
const { listUsers, isRequestRunning, userIds } = this.state;
const itemSize = 25;
const containerStyles = { height: listUsers.length * 25, maxHeight: 220 };
const renderItems = memo(({ data, index, style }) => {
return (
<Checkbox
truncate
style={style}
className="modal-dialog-checkbox"
value={data[index].id}
onChange={this.onChange}
key={`checkbox_${index}`}
isChecked={data[index].checked}
label={data[index].displayName}
isDisabled={data[index].disabled}
/>
);
}, areEqual);
const renderList = ({ height, width }) => (
<List
className="List"
height={height}
width={width}
itemSize={itemSize}
itemCount={listUsers.length}
itemData={listUsers}
outerElementType={CustomScrollbarsVirtualList}
>
{renderItems}
</List>
);
//console.log("SendInviteDialog render");
return (
<ModalDialogContainer>
<ModalDialog
visible={visible}
onClose={onClose}
headerContent={t("SendInviteAgain")}
bodyContent={
<>
<Text>{t("SendInviteAgainDialog")}</Text>
<Text>{t("SendInviteAgainDialogMessage")}</Text>
<ToggleContent
className="toggle-content-dialog"
label={t("ShowUsersList")}
>
<div style={containerStyles} className="modal-dialog-content">
<AutoSizer>{renderList}</AutoSizer>
</div>
</ToggleContent>
</>
}
footerContent={
<>
<Button
label={t("OKButton")}
size="medium"
primary
onClick={this.onSendInvite}
isLoading={isRequestRunning}
isDisabled={!userIds.length}
/>
<Button
className="button-dialog"
label={t("CancelButton")}
size="medium"
onClick={onClose}
isDisabled={isRequestRunning}
/>
</>
}
/>
</ModalDialogContainer>
);
}
}
const SendInviteDialogTranslated = withTranslation()(SendInviteDialogComponent);
const SendInviteDialog = props => (
<SendInviteDialogTranslated i18n={i18n} {...props} />
);
SendInviteDialog.propTypes = {
visible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
userIds: PropTypes.arrayOf(PropTypes.string).isRequired,
selectedUsers: PropTypes.arrayOf(PropTypes.object).isRequired,
setSelected: PropTypes.func.isRequired
};
export default withRouter(SendInviteDialog);

View File

@ -0,0 +1,10 @@
{
"OKButton": "OK",
"CancelButton": "Cancel",
"SendInviteAgain": "Send invitation once again",
"SuccessSendInvitation": "The invitation was successfully sent",
"ShowUsersList": "View users list",
"SendInviteAgainDialog": "The invitation to the portal will be sent once again to the selected users with the 'Pending' status who are not disabled.",
"SendInviteAgainDialogMessage": "After the users confirm the invitation to the portal their status will change to 'Active'"
}

View File

@ -0,0 +1,10 @@
{
"OKButton": "ОК",
"CancelButton": "Отмена",
"SendInviteAgain": "Отправить приглашение еще раз",
"SuccessSendInvitation": "Приглашение успешно отправлено",
"ShowUsersList": "Показать список пользователей",
"SendInviteAgainDialog": "Приглашение на портал будет отправлено еще раз выбранным пользователям со статусом 'Ожидание', которые не заблокированы.",
"SendInviteAgainDialogMessage": "После того, как пользователи подтвердят приглашение на портал, их статус изменится на 'Активный'"
}

View File

@ -3,7 +3,11 @@ import ChangePasswordDialog from "./ChangePasswordDialog";
import ChangePhoneDialog from "./ChangePhoneDialog";
import DeleteProfileEverDialog from "./DeleteProfileEverDialog";
import DeleteSelfProfileDialog from "./DeleteSelfProfileDialog";
import InviteDialog from './InviteDialog';
import DeleteUsersDialog from "./DeleteUsersDialog";
import InviteDialog from "./InviteDialog";
import SendInviteDialog from "./SendInviteDialog";
import ChangeUserStatusDialog from "./ChangeUserStatusDialog";
import ChangeUserTypeDialog from "./ChangeUserTypeDialog";
export {
ChangeEmailDialog,
@ -11,5 +15,9 @@ export {
ChangePhoneDialog,
DeleteProfileEverDialog,
DeleteSelfProfileDialog,
DeleteUsersDialog,
InviteDialog,
};
SendInviteDialog,
ChangeUserStatusDialog,
ChangeUserTypeDialog
};

View File

@ -7,43 +7,57 @@ import {
toastr,
ContextMenuButton
} from "asc-web-components";
import { Headline } from 'asc-web-common';
import { Headline } from "asc-web-common";
import { connect } from "react-redux";
import {
getSelectedGroup,
getSelectionIds
getUserType,
getUsersStatus,
getInactiveUsers,
getDeleteUsers,
getUsersIds
} from "../../../../../store/people/selectors";
import { withTranslation } from "react-i18next";
import {
updateUserStatus,
updateUserType,
fetchPeople,
removeUser
removeUser,
setSelected
} from "../../../../../store/people/actions";
import { deleteGroup } from "../../../../../store/group/actions";
import { store, api, constants } from 'asc-web-common';
import { InviteDialog } from '../../../../dialogs';
import { store, constants } from "asc-web-common";
import {
InviteDialog,
DeleteUsersDialog,
SendInviteDialog,
ChangeUserStatusDialog,
ChangeUserTypeDialog
} from "../../../../dialogs";
const { isAdmin } = store.auth.selectors;
const { resendUserInvites } = api.people;
const { EmployeeStatus, EmployeeType } = constants;
const isRefetchPeople = true;
const { EmployeeType, EmployeeStatus } = constants;
const StyledContainer = styled.div`
@media (min-width: 1024px) {
${props => props.isHeaderVisible && css`width: calc(100% + 76px);`}
${props =>
props.isHeaderVisible &&
css`
width: calc(100% + 76px);
`}
}
.group-button-menu-container {
margin: 0 -16px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
padding-bottom: 56px;
@media (max-width: 1024px) {
& > div:first-child {
${props => props.isArticlePinned && css`width: calc(100% - 240px);`}
${props =>
props.isArticlePinned &&
css`
width: calc(100% - 240px);
`}
position: absolute;
top: 56px;
z-index: 180;
@ -79,6 +93,12 @@ const StyledContainer = styled.div`
const SectionHeaderContent = props => {
const [dialogVisible, setDialogVisible] = useState(false);
const [showDeleteDialog, setDeleteDialog] = useState(false);
const [showSendInviteDialog, setSendInviteDialog] = useState(false);
const [showDisableDialog, setShowDisableDialog] = useState(false);
const [showActiveDialog, setShowActiveDialog] = useState(false);
const [showGuestDialog, setShowGuestDialog] = useState(false);
const [showEmployeeDialog, setShowEmployeeDialog] = useState(false);
const {
isHeaderVisible,
@ -90,62 +110,57 @@ const SectionHeaderContent = props => {
group,
isAdmin,
t,
selection,
updateUserStatus,
updateUserType,
onLoading,
filter,
history,
settings,
deleteGroup,
removeUser
usersWithEmployeeType,
usersWithGuestType,
usersWithActiveStatus,
usersWithDisableStatus,
usersToInvite,
usersToRemove,
setSelected,
selection
} = props;
const selectedUserIds = getSelectionIds(selection);
//console.log("SectionHeaderContent render", selection, selectedUserIds);
//console.log("SectionHeaderContent render");
const onSetActive = useCallback(() => {
onLoading(true);
updateUserStatus(EmployeeStatus.Active, selectedUserIds, isRefetchPeople)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch(error => toastr.error(error))
.finally(() => onLoading(false));
}, [selectedUserIds, updateUserStatus, t, onLoading]);
const onSetEmployee = useCallback(
() => setShowEmployeeDialog(!showEmployeeDialog),
[showEmployeeDialog]
);
const onSetDisabled = useCallback(() => {
onLoading(true);
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds, isRefetchPeople)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch(error => toastr.error(error))
.finally(() => onLoading(false));
}, [selectedUserIds, updateUserStatus, t, onLoading]);
const onSetGuest = useCallback(() => setShowGuestDialog(!showGuestDialog), [
showGuestDialog
]);
const onSetEmployee = useCallback(() => {
updateUserType(EmployeeType.User, selectedUserIds);
toastr.success(t("SuccessChangeUserType"));
}, [selectedUserIds, updateUserType, t]);
const onSetActive = useCallback(
() => setShowActiveDialog(!showActiveDialog),
[showActiveDialog]
);
const onSetGuest = useCallback(() => {
updateUserType(EmployeeType.Guest, selectedUserIds);
toastr.success(t("SuccessChangeUserType"));
}, [selectedUserIds, updateUserType, t]);
const onSetDisabled = useCallback(
() => setShowDisableDialog(!showDisableDialog),
[showDisableDialog]
);
const onSentInviteAgain = useCallback(() => {
resendUserInvites(selectedUserIds)
.then(() => toastr.success(t('SuccessSendInvitation')))
.catch(error => toastr.error(error));
}, [selectedUserIds, t]);
const onSendInviteAgain = useCallback(
() => setSendInviteDialog(!showSendInviteDialog),
[showSendInviteDialog]
);
const onDelete = useCallback(() => {
onLoading(true);
removeUser(selectedUserIds, filter)
.then(() => {
toastr.success(t('SuccessfullyRemovedUsers'));
return fetchPeople(filter);
})
.catch(error => toastr.error(error))
.finally(() => onLoading(false));
}, [selectedUserIds, removeUser, onLoading, filter, t]);
const onDelete = useCallback(() => setDeleteDialog(!showDeleteDialog), [
showDeleteDialog
]);
const onSendEmail = useCallback(() => {
let str = "";
for (let item of selection) {
str += `${item.email},`;
}
window.open(`mailto: ${str}`, "_self");
}, [selection]);
const menuItems = [
{
@ -162,38 +177,42 @@ const SectionHeaderContent = props => {
onSelect: item => onSelect(item.key)
},
{
label: t('ChangeToUser', { userCaption: settings.customNames.userCaption }),
disabled: !selection.length,
label: t("ChangeToUser", {
userCaption: settings.customNames.userCaption
}),
disabled: !usersWithEmployeeType.length,
onClick: onSetEmployee
},
{
label: t('ChangeToGuest', { guestCaption: settings.customNames.guestCaption }),
disabled: !selection.length,
label: t("ChangeToGuest", {
guestCaption: settings.customNames.guestCaption
}),
disabled: !usersWithGuestType.length,
onClick: onSetGuest
},
{
label: t("LblSetActive"),
disabled: !selection.length,
disabled: !usersWithActiveStatus.length,
onClick: onSetActive
},
{
label: t("LblSetDisabled"),
disabled: !selection.length,
disabled: !usersWithDisableStatus.length,
onClick: onSetDisabled
},
{
label: t("LblInviteAgain"),
disabled: !selection.length,
onClick: onSentInviteAgain
disabled: !usersToInvite.length,
onClick: onSendInviteAgain
},
{
label: t("LblSendEmail"),
disabled: !selection.length,
onClick: toastr.success.bind(this, t("SendEmailAction"))
onClick: onSendEmail
},
{
label: t("DeleteButton"),
disabled: !selection.length,
disabled: !usersToRemove.length,
onClick: onDelete
}
];
@ -236,8 +255,9 @@ const SectionHeaderContent = props => {
history.push(`${settings.homepage}/group/create`);
}, [history, settings]);
const onInvitationDialogClick = useCallback(() =>
setDialogVisible(!dialogVisible), [dialogVisible]
const onInvitationDialogClick = useCallback(
() => setDialogVisible(!dialogVisible),
[dialogVisible]
);
const getContextOptionsPlus = useCallback(() => {
@ -258,24 +278,97 @@ const SectionHeaderContent = props => {
label: groupCaption,
onClick: goToGroupCreate
},
{ key: 'separator', isSeparator: true },
{ key: "separator", isSeparator: true },
{
key: "make-invitation-link",
label: t("MakeInvitationLink"),
onClick: onInvitationDialogClick
}/* ,
} /* ,
{
key: "send-invitation",
label: t("SendInviteAgain"),
onClick: onSentInviteAgain
} */
];
}, [settings, t, goToEmployeeCreate, goToGuestCreate, goToGroupCreate, onInvitationDialogClick/* , onSentInviteAgain */]);
}, [
settings,
t,
goToEmployeeCreate,
goToGuestCreate,
goToGroupCreate,
onInvitationDialogClick /* , onSentInviteAgain */
]);
const isArticlePinned = window.localStorage.getItem('asc_article_pinned_key');
const isArticlePinned = window.localStorage.getItem("asc_article_pinned_key");
return (
<StyledContainer isHeaderVisible={isHeaderVisible} isArticlePinned={isArticlePinned}>
<StyledContainer
isHeaderVisible={isHeaderVisible}
isArticlePinned={isArticlePinned}
>
{showEmployeeDialog && (
<ChangeUserTypeDialog
visible={showEmployeeDialog}
userIds={getUsersIds(usersWithEmployeeType)}
selectedUsers={selection}
onClose={onSetEmployee}
userType={EmployeeType.User}
setSelected={setSelected}
/>
)}
{showGuestDialog && (
<ChangeUserTypeDialog
visible={showGuestDialog}
userIds={getUsersIds(usersWithGuestType)}
selectedUsers={selection}
onClose={onSetGuest}
userType={EmployeeType.Guest}
setSelected={setSelected}
/>
)}
{showActiveDialog && (
<ChangeUserStatusDialog
visible={showActiveDialog}
userIds={getUsersIds(usersWithActiveStatus)}
selectedUsers={selection}
onClose={onSetActive}
userStatus={EmployeeStatus.Active}
setSelected={setSelected}
/>
)}
{showDisableDialog && (
<ChangeUserStatusDialog
visible={showDisableDialog}
userIds={getUsersIds(usersWithDisableStatus)}
selectedUsers={selection}
onClose={onSetDisabled}
userStatus={EmployeeStatus.Disabled}
setSelected={setSelected}
/>
)}
{showSendInviteDialog && (
<SendInviteDialog
visible={showSendInviteDialog}
onClose={onSendInviteAgain}
userIds={getUsersIds(usersToInvite)}
selectedUsers={selection}
setSelected={setSelected}
/>
)}
{showDeleteDialog && (
<DeleteUsersDialog
visible={showDeleteDialog}
onClose={onDelete}
userIds={getUsersIds(usersToRemove)}
selectedUsers={selection}
filter={filter}
setSelected={setSelected}
/>
)}
{isHeaderVisible ? (
<div className="group-button-menu-container">
<GroupButtonsMenu
@ -291,66 +384,107 @@ const SectionHeaderContent = props => {
/>
</div>
) : (
<div className="header-container">
{group ? (
<>
<Headline className='headline-header' type="content" truncate={true}>{group.name}</Headline>
{isAdmin && (
<div className="header-container">
{group ? (
<>
<Headline
className="headline-header"
type="content"
truncate={true}
>
{group.name}
</Headline>
{isAdmin && (
<ContextMenuButton
className="action-button"
directionX="right"
title={t("Actions")}
iconName="VerticalDotsIcon"
size={16}
color="#A3A9AE"
getData={getContextOptionsGroup}
isDisabled={false}
/>
)}
</>
) : (
<>
<Headline
className="headline-header"
truncate={true}
type="content"
>
{settings.customNames.groupsCaption}
</Headline>
{isAdmin && (
<>
<ContextMenuButton
className="action-button"
directionX="right"
title={t("Actions")}
iconName="VerticalDotsIcon"
iconName="PlusIcon"
size={16}
color="#A3A9AE"
getData={getContextOptionsGroup}
color="#657077"
getData={getContextOptionsPlus}
isDisabled={false}
/>
)}
</>
) : (
<>
<Headline className='headline-header' truncate={true} type="content">{settings.customNames.groupsCaption}</Headline>
{isAdmin && (
<>
<ContextMenuButton
className="action-button"
directionX="right"
title={t("Actions")}
iconName="PlusIcon"
size={16}
color="#657077"
getData={getContextOptionsPlus}
isDisabled={false}
/>
{dialogVisible &&
<InviteDialog
visible={dialogVisible}
onClose={onInvitationDialogClick}
onCloseButton={onInvitationDialogClick}
/>
}
</>
{dialogVisible && (
<InviteDialog
visible={dialogVisible}
onClose={onInvitationDialogClick}
onCloseButton={onInvitationDialogClick}
/>
)}
</>
)}
</div>
)}
</>
)}
</div>
)}
</StyledContainer>
);
};
const mapStateToProps = state => {
const selection = state.people.selection;
const activeUsers = 1;
const disabledUsers = 2;
const currentUserId = state.auth.user.id;
const employeeStatus = true;
const guestStatus = false;
return {
group: getSelectedGroup(state.people.groups, state.people.selectedGroup),
selection: state.people.selection,
selection,
isAdmin: isAdmin(state.auth.user),
filter: state.people.filter,
settings: state.auth.settings
settings: state.auth.settings,
usersWithEmployeeType: getUserType(
selection,
employeeStatus,
currentUserId
),
usersWithGuestType: getUserType(selection, guestStatus, currentUserId),
usersWithActiveStatus: getUsersStatus(
selection,
activeUsers,
currentUserId
),
usersWithDisableStatus: getUsersStatus(
selection,
disabledUsers,
currentUserId
),
usersToInvite: getInactiveUsers(selection),
usersToRemove: getDeleteUsers(selection)
};
};
export default connect(
mapStateToProps,
{ updateUserStatus, updateUserType, fetchPeople, deleteGroup, removeUser }
)(withTranslation()(withRouter(SectionHeaderContent)));
export default connect(mapStateToProps, {
updateUserStatus,
fetchPeople,
deleteGroup,
removeUser,
setSelected
})(withTranslation()(withRouter(SectionHeaderContent)));

View File

@ -85,14 +85,9 @@ class PureHome extends React.Component {
};
onClose = () => {
const { selection, setSelected } = this.props;
if (!selection.length) {
setSelected("none");
this.setState({ isHeaderVisible: false });
} else {
setSelected("close");
}
const { setSelected } = this.props;
setSelected("none");
this.setState({ isHeaderVisible: false });
};
onLoading = status => {

View File

@ -23,8 +23,6 @@
"Administrator": "Administrator",
"LblOther": "Other",
"DeleteButton": "Delete",
"SuccessChangeUserStatus": "The user status was successfully changed",
"SuccessChangeUserType": "The user type was successfully changed",
"LblSelect": "Select",
"More": "More",
"CloseButton": "Close",
@ -35,7 +33,6 @@
"MessageEmailActivationInstuctionsSentOnEmail": "The email activation instructions have been sent to the <1>{{email}}</1> email address",
"Search": "Search",
"SendInviteAgain": "Send invitation once again",
"SuccessSendInvitation": "The invitation was successfully sent",
"CountPerPage": "{{count}} per page",
"PageOfTotalPage": "{{page}} of {{totalPage}}",

View File

@ -23,8 +23,6 @@
"Administrator": "Администратор",
"LblOther": "Другое",
"DeleteButton": "Удалить",
"SuccessChangeUserStatus": "Статус пользователя успешно изменен",
"SuccessChangeUserType": "Тип пользователя успешно изменен",
"LblSelect": "Выберите",
"More": "Больше",
"CloseButton": "Закрыть",
@ -35,7 +33,6 @@
"MessageEmailActivationInstuctionsSentOnEmail": "Инструкции по активации электронной почты были отправлены на адрес <1>{{email}}</1>",
"Search": "Поиск",
"SendInviteAgain": "Отправить приглашение ещё раз",
"SuccessSendInvitation": "Приглашение успешно отправлено",
"PageOfTotalPage": "{{page}} из {{totalPage}}",
"CountPerPage": "{{count}} на странице",

View File

@ -67,6 +67,48 @@
"MessageSendPasswordChangeInstructionsOnEmail",
"SendButton"
]
},
"DeleteUsersDialog":{
"Resource": [
"OKButton",
"CancelButton",
"Warning",
"DeleteUserDataConfirmation"
],
"UserControlsCommonResource":[
"NotBeUndone"
]
},
"SendInviteDialog":{
"Resource": [
"OKButton",
"CancelButton",
"SendInviteAgain",
"DisabledEmployeeTitle"
],
"PeopleJSResource": [
"SuccessSendInvitation"
]
},
"ChangeUserStatusDialog":{
"PeopleJSResource": [
"SuccessChangeUserStatus"
],
"Resource": [
"CancelButton"
]
},
"ChangeUserTypeDialog":{
"PeopleJSResource": [
"SuccessChangeUserType"
],
"Resource": [
"CancelButton",
"GuestCaption"
],
"AuditResource": [
"UserCol"
]
}
},
"pages": {
@ -193,11 +235,6 @@
"LblOther",
"LblChangeEmail"
],
"PeopleJSResource": [
"SuccessChangeUserStatus",
"SuccessChangeUserType",
"SuccessSendInvitation"
],
"UserControlsCommonResource": [
"NextPage",
"PreviousPage",

View File

@ -122,10 +122,6 @@ export function getSelectedGroup(groups, selectedGroupId) {
return find(groups, (group) => group.id === selectedGroupId);
}
export function getSelectionIds(selections) {
return selections.map((user) => { return user.id });
}
export function toEmployeeWrapper(profile) {
const emptyData = {
id: "",
@ -171,4 +167,33 @@ export function filterGroupSelectorOptions(options, template) {
return options.filter(option => {
return template ? option.label.indexOf(template) > -1 : true;
})
}
}
export function getUserType(users, status, currentUserId) {
return users.filter(
x =>
!x.isAdmin &&
!x.isOwner &&
x.isVisitor === status &&
x.status !== 2 &&
x.id !== currentUserId
);
}
export function getUsersStatus(users, status, currentUserId) {
return users.filter(
x => !x.isOwner && x.status !== status && x.id !== currentUserId
);
}
export function getInactiveUsers(users) {
return users.filter(x => x.activationStatus === 2 && x.status === 1);
}
export function getDeleteUsers(users) {
return users.filter(x => x.status === 2);
}
export function getUsersIds(selections) {
return selections.map((user) => { return user.id });
}

View File

@ -99,7 +99,7 @@ describe('<GroupButtonsMenu />', () => {
instance.groupButtonClick(item);
expect(wrapper.state('visible')).toBe(false);
//expect(wrapper.state('visible')).toBe(false);
expect(onClick).toBeCalled();
});

View File

@ -85,7 +85,7 @@ class GroupButtonsMenu extends React.PureComponent {
groupButtonClick = (item) => {
if (item.disabled) return;
item.onClick();
this.closeMenu();
//this.closeMenu();
};
componentDidMount() {