Merge branch 'feature/files' into feature/avatar-editor-redesign

This commit is contained in:
Alexey Kostenko 2020-10-08 14:00:50 +03:00
commit 1129a00d21
18 changed files with 535 additions and 184 deletions

View File

@ -1,7 +1,10 @@
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { utils, TreeMenu, TreeNode, Icons, Link } from "asc-web-components";
import { selectGroup } from "../../../store/people/actions";
import {
selectGroup,
setIsVisibleDataLossDialog,
} from "../../../store/people/actions";
import { getSelectedGroup } from "../../../store/people/selectors";
import { withTranslation, I18nextProvider } from "react-i18next";
import {
@ -100,8 +103,17 @@ class ArticleBodyContent extends React.Component {
return false;
}
onSelectHandler = (data) => {
const { editingForm, setIsVisibleDataLossDialog } = this.props;
if (editingForm.isEdit) {
setIsVisibleDataLossDialog(true, this.onSelect(data));
} else {
this.onSelect(data)();
}
};
onSelect = (data) => {
return () => {
const { selectGroup } = this.props;
this.changeTitleDocument(data);
@ -109,7 +121,7 @@ class ArticleBodyContent extends React.Component {
data && data.length === 1 && data[0] !== "root" ? data[0] : null
);
};
};
switcherIcon = (obj) => {
if (obj.isLeaf) {
return null;
@ -141,7 +153,7 @@ class ArticleBodyContent extends React.Component {
showIcon={true}
defaultExpandAll={true}
switcherIcon={this.switcherIcon}
onSelect={this.onSelect}
onSelect={this.onSelectHandler}
selectedKeys={selectedKeys}
isFullFillSelection={false}
gapBetweenNodes="22"
@ -220,6 +232,7 @@ function mapStateToProps(state) {
const { isLoaded, settings } = state.auth;
const { customNames } = settings;
const { groupsCaption } = customNames;
const { editingForm } = state.people;
return {
data: getTreeGroups(groups, groupsCaption),
@ -229,7 +242,11 @@ function mapStateToProps(state) {
groups,
isAdmin: isAdmin(state),
isLoaded,
editingForm
};
}
export default connect(mapStateToProps, { selectGroup })(BodyContent);
export default connect(mapStateToProps, {
selectGroup,
setIsVisibleDataLossDialog,
})(BodyContent);

View File

@ -0,0 +1,106 @@
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { ModalDialog, Button, Text } from "asc-web-components";
import { withTranslation } from "react-i18next";
import { utils } from "asc-web-common";
import ModalDialogContainer from "../ModalDialogContainer";
import { createI18N } from "../../../helpers/i18n";
import {
setIsVisibleDataLossDialog,
setIsEditingForm,
} from "../../../store/people/actions";
const i18n = createI18N({
page: "DataLossWarningDialog",
localesPath: "dialogs/DataLossWarningDialog",
});
const { changeLanguage } = utils;
class DataLossWarningDialogComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
}
onClose = () => {
const { setIsVisibleDataLossDialog } = this.props;
setIsVisibleDataLossDialog(false);
};
onSubmit = () => {
const {
onContinue,
setIsVisibleDataLossDialog,
setIsEditingForm,
editingForm,
} = this.props;
setIsVisibleDataLossDialog(false, null);
setIsEditingForm(false);
if (editingForm.callback) {
editingForm.callback();
} else {
onContinue && onContinue();
}
};
render() {
const { t, editingForm } = this.props;
return (
<ModalDialogContainer>
<ModalDialog
visible={editingForm.isVisibleDataLossDialog}
onClose={this.onClose}
>
<ModalDialog.Header>{t("DataLossWarningHeader")}</ModalDialog.Header>
<ModalDialog.Body>
<Text fontSize="13px">{t("DataLossWarningBody")}</Text>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="LeaveForm"
label={t("DataLossWarningLeaveBtn")}
size="medium"
primary={true}
onClick={this.onSubmit}
/>
<Button
className="button-dialog"
key="StayOnPage"
label={t("DataLossWarningCancelBtn")}
size="medium"
onClick={this.onClose}
/>
</ModalDialog.Footer>
</ModalDialog>
</ModalDialogContainer>
);
}
}
const DataLossWarningDialogTranslated = withTranslation()(
DataLossWarningDialogComponent
);
const DataLossWarningDialog = (props) => (
<DataLossWarningDialogTranslated i18n={i18n} {...props} />
);
DataLossWarningDialog.propTypes = {
editingForm: PropTypes.object.isRequired,
onContinue: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
return {
editingForm: state.people.editingForm,
};
}
export default connect(mapStateToProps, {
setIsVisibleDataLossDialog,
setIsEditingForm,
})(DataLossWarningDialog);

View File

@ -0,0 +1,6 @@
{
"DataLossWarningHeader": "Leave the page?",
"DataLossWarningBody": "Changes you made may not be saved.",
"DataLossWarningLeaveBtn": "Leave",
"DataLossWarningCancelBtn": "Cancel"
}

View File

@ -0,0 +1,6 @@
{
"DataLossWarningHeader": "Покинуть страницу?",
"DataLossWarningBody": "Внесенные вами изменения могут быть не сохранены.",
"DataLossWarningLeaveBtn": "Покинуть",
"DataLossWarningCancelBtn": "Отмена"
}

View File

@ -8,6 +8,7 @@ import InviteDialog from "./InviteDialog";
import SendInviteDialog from "./SendInviteDialog";
import ChangeUserStatusDialog from "./ChangeUserStatusDialog";
import ChangeUserTypeDialog from "./ChangeUserTypeDialog";
import DataLossWarningDialog from "./DataLossWarningDialog";
export {
ChangeEmailDialog,
@ -19,5 +20,6 @@ export {
InviteDialog,
SendInviteDialog,
ChangeUserStatusDialog,
ChangeUserTypeDialog
ChangeUserTypeDialog,
DataLossWarningDialog,
};

View File

@ -14,7 +14,7 @@ import {
resetGroup,
updateGroup,
} from "../../../../../store/group/actions";
import { selectGroup } from "../../../../../store/people/actions";
import { selectGroup, setFilter } from "../../../../../store/people/actions";
import { GUID_EMPTY } from "../../../../../helpers/constants";
import PropTypes from "prop-types";
@ -241,10 +241,10 @@ class SectionBodyContent extends React.Component {
};
onCancel = () => {
const { history, resetGroup, settings } = this.props;
const { resetGroup, filter, setFilter } = this.props;
resetGroup();
history.push(`${settings.homepage}/`);
setFilter(filter);
};
onSelectedItemClose = (member) => {
@ -490,6 +490,7 @@ function mapStateToProps(state) {
groupCaption,
me: getCurrentUser(state),
currentModuleName,
filter: state.people.filter,
};
}
@ -498,4 +499,5 @@ export default connect(mapStateToProps, {
createGroup,
updateGroup,
selectGroup,
setFilter,
})(withRouter(withTranslation()(SectionBodyContent)));

View File

@ -6,6 +6,7 @@ import { IconButton } from "asc-web-components";
import { Headline } from "asc-web-common";
import { withTranslation } from "react-i18next";
import { resetGroup } from "../../../../../store/group/actions";
import { setFilter } from "../../../../../store/people/actions";
import styled from "styled-components";
const Wrapper = styled.div`
@ -23,7 +24,6 @@ const Wrapper = styled.div`
`;
class SectionHeaderContent extends React.Component {
constructor(props) {
super(props);
const { group, t, groupCaption } = props;
@ -32,14 +32,14 @@ class SectionHeaderContent extends React.Component {
: t("CustomNewDepartment", { groupCaption });
this.state = {
headerText
}
headerText,
};
}
onClickBack = () => {
const { history, settings, resetGroup } = this.props;
const { filter, resetGroup, setFilter } = this.props;
resetGroup();
history.push(settings.homepage);
setFilter(filter);
};
render() {
@ -65,22 +65,22 @@ class SectionHeaderContent extends React.Component {
SectionHeaderContent.propTypes = {
group: PropTypes.object,
history: PropTypes.object.isRequired
history: PropTypes.object.isRequired,
};
SectionHeaderContent.defaultProps = {
group: null
group: null,
};
function mapStateToProps(state) {
return {
settings: state.auth.settings,
group: state.group.targetGroup,
groupCaption: state.auth.settings.customNames.groupCaption
groupCaption: state.auth.settings.customNames.groupCaption,
filter: state.people.filter,
};
}
export default connect(
mapStateToProps,
{ resetGroup }
)(withTranslation()(withRouter(SectionHeaderContent)));
export default connect(mapStateToProps, { resetGroup, setFilter })(
withTranslation()(withRouter(SectionHeaderContent))
);

View File

@ -12,7 +12,10 @@ import {
toEmployeeWrapper,
} from "../../../../../store/people/selectors";
import { withTranslation, Trans } from "react-i18next";
import { updateUserStatus } from "../../../../../store/people/actions";
import {
updateUserStatus,
setFilter,
} from "../../../../../store/people/actions";
import { updateProfile } from "../../../../../store/profile/actions";
import {
fetchProfile,
@ -390,9 +393,9 @@ class SectionHeaderContent extends React.PureComponent {
};
onClickBack = () => {
const { history, settings } = this.props;
history.push(settings.homepage);
const { filter, setFilter } = this.props;
setFilter(filter);
//history.push(settings.homepage);
};
render() {
@ -504,4 +507,5 @@ export default connect(mapStateToProps, {
updateUserStatus,
fetchProfile,
updateProfile,
setFilter,
})(withRouter(withTranslation()(SectionHeaderContent)));

View File

@ -24,7 +24,7 @@ const { isAdmin, isVisitor, getLanguage } = store.auth.selectors;
class PureProfile extends React.Component {
componentDidMount() {
const { match, fetchProfile, t, location } = this.props;
const { match, fetchProfile, profile, location, t } = this.props;
const { userId } = match.params;
setDocumentTitle(t("Profile"));
@ -39,9 +39,10 @@ class PureProfile extends React.Component {
if (linkParams.email_change && linkParams.email_change === "success") {
toastr.success(t("ChangeEmailSuccess"));
}
if (!profile) {
fetchProfile(userId);
}
}
componentDidUpdate(prevProps) {
const { match, fetchProfile } = this.props;

View File

@ -7,8 +7,7 @@ import {
Textarea,
AvatarEditor,
Text,
utils
} from "asc-web-components";
utils} from "asc-web-components";
import { withTranslation, Trans } from "react-i18next";
import {
toEmployeeWrapper,
@ -17,13 +16,22 @@ import {
getUserContacts,
mapGroupsToGroupSelectorOptions,
mapGroupSelectorOptionsToGroups,
filterGroupSelectorOptions
filterGroupSelectorOptions,
} from "../../../../../store/people/selectors";
import { createProfile } from "../../../../../store/profile/actions";
import {
createProfile,
updateCreatedAvatar,
} from "../../../../../store/profile/actions";
import {
setFilter,
updateProfileInUsers,
setIsVisibleDataLossDialog,
setIsEditingForm,
} from "../../../../../store/people/actions";
import {
MainContainer,
AvatarContainer,
MainFieldsContainer
MainFieldsContainer,
} from "./FormFields/Form";
import TextField from "./FormFields/TextField";
import PasswordField from "./FormFields/PasswordField";
@ -33,6 +41,7 @@ import RadioField from "./FormFields/RadioField";
import DepartmentField from "./FormFields/DepartmentField";
import ContactsField from "./FormFields/ContactsField";
import InfoFieldContainer from "./FormFields/InfoFieldContainer";
import { DataLossWarningDialog } from "../../../../dialogs";
import { api, toastr } from "asc-web-common";
import { isMobile } from "react-device-detect";
const { createThumbnailsAvatar, loadAvatar } = api.people;
@ -50,6 +59,7 @@ class CreateUserForm extends React.Component {
this.onBirthdayDateChange = this.onBirthdayDateChange.bind(this);
this.onWorkFromDateChange = this.onWorkFromDateChange.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onCancelHandler = this.onCancelHandler.bind(this);
this.onContactsItemAdd = this.onContactsItemAdd.bind(this);
this.onContactsItemTypeChange = this.onContactsItemTypeChange.bind(this);
@ -78,15 +88,17 @@ class CreateUserForm extends React.Component {
y: this.state.avatar.y,
width: this.state.avatar.width,
height: this.state.avatar.height,
tmpFile: this.state.avatar.tmpFile
tmpFile: this.state.avatar.tmpFile,
})
.then(() => {
.then((res) => {
this.props.updateCreatedAvatar(res);
this.props.updateProfileInUsers();
toastr.success(this.props.t("ChangesSavedSuccessfully"));
this.props.history.push(
`${this.props.settings.homepage}/view/${userName}`
);
})
.catch(error => toastr.error(error));
.catch((error) => toastr.error(error));
}
openAvatarEditor() {
@ -104,17 +116,19 @@ class CreateUserForm extends React.Component {
tmpFile: this.state.avatar.tmpFile,
image: this.state.avatar.image,
defaultWidth: avatarDefaultSizes[1],
defaultHeight: avatarDefaultSizes[2]
}
defaultHeight: avatarDefaultSizes[2],
},
});
}
this.setState({
visibleAvatarEditor: true
visibleAvatarEditor: true,
});
}
openAvatarEditorPage() {
this.props.history.push(`${this.props.settings.homepage}/edit-avatar/${this.props.profile.userName}`);
this.props.history.push(
`${this.props.settings.homepage}/edit-avatar/${this.props.profile.userName}`
);
}
onLoadFileAvatar(file) {
@ -124,7 +138,7 @@ class CreateUserForm extends React.Component {
data.append("Autosave", false);
loadAvatar(0, data)
.then(response => {
.then((response) => {
var img = new Image();
img.onload = function () {
var stateCopy = Object.assign({}, _this.state);
@ -132,13 +146,14 @@ class CreateUserForm extends React.Component {
tmpFile: response.data,
image: response.data,
defaultWidth: img.width,
defaultHeight: img.height
defaultHeight: img.height,
};
_this.setState(stateCopy);
//if (typeof callback === "function") callback();
};
img.src = response.data;
})
.catch(error => toastr.error(error));
.catch((error) => toastr.error(error));
}
onSaveAvatar(isUpdate, result, file) {
@ -157,6 +172,7 @@ class CreateUserForm extends React.Component {
stateCopy.avatar.height = result.height;
}
this.setState(stateCopy);
this.setIsEdit();
}
onCloseAvatarEditor() {
@ -164,8 +180,8 @@ class CreateUserForm extends React.Component {
visibleAvatarEditor: false,
croppedAvatarImage: "",
avatar: {
tmpFile: ""
}
tmpFile: "",
},
});
}
@ -174,17 +190,17 @@ class CreateUserForm extends React.Component {
this.setState(this.mapPropsToState(this.props));
}
const isMobileDevice = isMobile || isTablet()
const isMobileDevice = isMobile || isTablet();
if (prevState.isMobile !== isMobileDevice) {
this.setState({ isMobile: isMobileDevice });
}
}
mapPropsToState = props => {
mapPropsToState = (props) => {
var profile = toEmployeeWrapper({
isVisitor: props.match.params.type === "guest",
passwordType: "link"
passwordType: "link",
});
var allOptions = mapGroupsToGroupSelectorOptions(props.groups);
var selected = mapGroupsToGroupSelectorOptions(profile.groups);
@ -196,14 +212,14 @@ class CreateUserForm extends React.Component {
firstName: false,
lastName: false,
email: false,
password: false
password: false,
},
profile: profile,
selector: {
visible: false,
allOptions: allOptions,
options: [...allOptions],
selected: selected
selected: selected,
},
avatar: {
tmpFile: "",
@ -213,28 +229,35 @@ class CreateUserForm extends React.Component {
x: 0,
y: 0,
width: 0,
height: 0
height: 0,
},
isMobile: isMobile || isTablet
isMobile: isMobile || isTablet,
};
};
setIsEdit() {
const { editingForm, setIsEditingForm } = this.props;
if (!editingForm.isEdit) setIsEditingForm(true);
}
onInputChange(event) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile[event.target.name] = event.target.value;
this.setState(stateCopy);
this.setIsEdit();
}
onBirthdayDateChange(value) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.birthday = value ? value.toJSON() : null;
this.setState(stateCopy);
this.setIsEdit();
}
onWorkFromDateChange(value) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.workFrom = value ? value.toJSON() : null;
this.setState(stateCopy);
this.setIsEdit();
}
validate() {
@ -243,7 +266,7 @@ class CreateUserForm extends React.Component {
firstName: !profile.firstName.trim(),
lastName: !profile.lastName.trim(),
email: stateErrors.email || !profile.email.trim(),
password: profile.passwordType === "temp" && !profile.password.trim()
password: profile.passwordType === "temp" && !profile.password.trim(),
};
const hasError =
errors.firstName || errors.lastName || errors.email || errors.password;
@ -265,7 +288,7 @@ class CreateUserForm extends React.Component {
this.props
.createProfile(this.state.profile)
.then(profile => {
.then((profile) => {
if (this.state.avatar.tmpFile !== "") {
this.createAvatar(profile.id, profile.userName);
} else {
@ -275,14 +298,25 @@ class CreateUserForm extends React.Component {
);
}
})
.catch(error => {
.catch((error) => {
toastr.error(error);
this.setState({ isLoading: false });
});
}
onCancelHandler() {
const { editingForm, setIsVisibleDataLossDialog } = this.props;
if (editingForm.isEdit) {
setIsVisibleDataLossDialog(true);
} else {
this.onCancel();
}
}
onCancel() {
this.props.history.push(this.props.settings.homepage);
const { filter, setFilter } = this.props;
setFilter(filter);
}
onContactsItemAdd(item) {
@ -290,37 +324,41 @@ class CreateUserForm extends React.Component {
stateCopy.profile.contacts.push({
id: new Date().getTime().toString(),
type: item.value,
value: ""
value: "",
});
this.setState(stateCopy);
this.setIsEdit();
}
onContactsItemTypeChange(item) {
const id = item.key.split("_")[0];
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.contacts.forEach(element => {
stateCopy.profile.contacts.forEach((element) => {
if (element.id === id) element.type = item.value;
});
this.setState(stateCopy);
this.setIsEdit();
}
onContactsItemTextChange(event) {
const id = event.target.name.split("_")[0];
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.contacts.forEach(element => {
stateCopy.profile.contacts.forEach((element) => {
if (element.id === id) element.value = event.target.value;
});
this.setState(stateCopy);
this.setIsEdit();
}
onContactsItemRemove(event) {
const id = event.target.closest(".remove_icon").dataset.for.split("_")[0];
var stateCopy = Object.assign({}, this.state);
const filteredArray = stateCopy.profile.contacts.filter(element => {
const filteredArray = stateCopy.profile.contacts.filter((element) => {
return element.id !== id;
});
stateCopy.profile.contacts = filteredArray;
this.setState(stateCopy);
this.setIsEdit();
}
onShowGroupSelector() {
@ -350,20 +388,21 @@ class CreateUserForm extends React.Component {
stateCopy.selector.selected = selected;
stateCopy.selector.visible = false;
this.setState(stateCopy);
this.setIsEdit();
}
onRemoveGroup(id) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.groups = stateCopy.profile.groups.filter(
group => group.id !== id
(group) => group.id !== id
);
stateCopy.selector.selected = stateCopy.selector.selected.filter(
option => option.key !== id
(option) => option.key !== id
);
this.setState(stateCopy);
}
onValidateEmailField = value =>
onValidateEmailField = (value) =>
this.setState({ errors: { ...this.state.errors, email: !value.isValid } });
render() {
@ -372,7 +411,7 @@ class CreateUserForm extends React.Component {
const {
regDateCaption,
userPostCaption,
groupCaption
groupCaption,
} = settings.customNames;
const pattern = getUserContactsPattern();
@ -381,6 +420,7 @@ class CreateUserForm extends React.Component {
return (
<>
<MainContainer>
<DataLossWarningDialog onContinue={this.onCancel} />
<AvatarContainer>
<Avatar
size="max"
@ -388,7 +428,9 @@ class CreateUserForm extends React.Component {
editing={true}
source={this.state.croppedAvatarImage}
editLabel={t("AddButton")}
editAction={isMobile ? this.openAvatarEditorPage : this.openAvatarEditor}
editAction={
isMobile ? this.openAvatarEditorPage : this.openAvatarEditor
}
/>
<AvatarEditor
image={this.state.avatar.image}
@ -462,7 +504,7 @@ class CreateUserForm extends React.Component {
radioValue={profile.passwordType}
radioOptions={[
{ value: "link", label: t("ActivationLink") },
{ value: "temp", label: t("TemporaryPassword") }
{ value: "temp", label: t("TemporaryPassword") },
]}
radioIsDisabled={isLoading}
radioOnChange={this.onInputChange}
@ -493,7 +535,7 @@ class CreateUserForm extends React.Component {
radioValue={profile.sex}
radioOptions={[
{ value: "male", label: t("MaleSexStatus") },
{ value: "female", label: t("FemaleSexStatus") }
{ value: "female", label: t("FemaleSexStatus") },
]}
radioIsDisabled={isLoading}
radioOnChange={this.onInputChange}
@ -586,7 +628,7 @@ class CreateUserForm extends React.Component {
/>
<Button
label={t("CancelButton")}
onClick={this.onCancel}
onClick={this.onCancelHandler}
isDisabled={isLoading}
size="big"
style={{ marginLeft: "8px" }}
@ -598,16 +640,22 @@ class CreateUserForm extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state) => {
const { settings } = state.auth;
const { groups, filter, editingForm } = state.people;
return {
settings: state.auth.settings,
groups: state.people.groups
settings,
groups,
filter,
editingForm,
};
};
export default connect(
mapStateToProps,
{
createProfile
}
)(withRouter(withTranslation()(CreateUserForm)));
export default connect(mapStateToProps, {
createProfile,
updateCreatedAvatar,
setFilter,
updateProfileInUsers,
setIsVisibleDataLossDialog,
setIsEditingForm,
})(withRouter(withTranslation()(CreateUserForm)));

View File

@ -8,8 +8,7 @@ import {
Text,
AvatarEditor,
Link,
utils,
} from "asc-web-components";
utils,} from "asc-web-components";
import { withTranslation, Trans } from "react-i18next";
import {
toEmployeeWrapper,
@ -26,6 +25,12 @@ import {
fetchProfile,
} from "../../../../../store/profile/actions";
import { toggleAvatarEditor } from "../../../../../store/people/actions";
import {
setFilter,
updateProfileInUsers,
setIsVisibleDataLossDialog,
setIsEditingForm,
} from "../../../../../store/people/actions";
import {
MainContainer,
AvatarContainer,
@ -39,6 +44,7 @@ import DepartmentField from "./FormFields/DepartmentField";
import ContactsField from "./FormFields/ContactsField";
import InfoFieldContainer from "./FormFields/InfoFieldContainer";
import styled from "styled-components";
import { DataLossWarningDialog } from "../../../../dialogs";
import { api, toastr } from "asc-web-common";
import {
ChangeEmailDialog,
@ -80,6 +86,7 @@ class UpdateUserForm extends React.Component {
this.onBirthdayDateChange = this.onBirthdayDateChange.bind(this);
this.onWorkFromDateChange = this.onWorkFromDateChange.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onCancelHandler = this.onCancelHandler.bind(this);
this.onContactsItemAdd = this.onContactsItemAdd.bind(this);
this.onContactsItemTypeChange = this.onContactsItemTypeChange.bind(this);
@ -164,8 +171,7 @@ class UpdateUserForm extends React.Component {
[dialogsDataset.changeEmail]: false,
currentDialog: "",
},
isMobile: isMobile || isTablet,
};
isMobile: isMobile || isTablet, };
//Set unique contacts id
const now = new Date().getTime();
@ -177,10 +183,16 @@ class UpdateUserForm extends React.Component {
return newState;
};
setIsEdit() {
const { editingForm, setIsEditingForm } = this.props;
if (!editingForm.isEdit) setIsEditingForm(true);
}
onInputChange(event) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile[event.target.name] = event.target.value;
this.setState(stateCopy);
this.setIsEdit();
}
toggleDialogsVisible = (e) => {
@ -200,18 +212,21 @@ class UpdateUserForm extends React.Component {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.isVisitor = event.target.value === "true";
this.setState(stateCopy);
this.setIsEdit();
}
onBirthdayDateChange(value) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.birthday = value ? value.toJSON() : null;
this.setState(stateCopy);
this.setIsEdit();
}
onWorkFromDateChange(value) {
var stateCopy = Object.assign({}, this.state);
stateCopy.profile.workFrom = value ? value.toJSON() : null;
this.setState(stateCopy);
this.setIsEdit();
}
validate() {
@ -240,7 +255,7 @@ class UpdateUserForm extends React.Component {
this.props
.updateProfile(this.state.profile)
.then((profile) => {
toastr.success(this.props.t("ChangesSavedSuccessfully"));
this.props.updateProfileInUsers(profile); toastr.success(this.props.t("ChangesSavedSuccessfully"));
this.props.history.push(
`${this.props.settings.homepage}/view/${profile.userName}`
);
@ -250,9 +265,24 @@ class UpdateUserForm extends React.Component {
this.setState({ isLoading: false });
});
}
onCancelHandler() {
const { editingForm, setIsVisibleDataLossDialog } = this.props;
if (editingForm.isEdit) {
setIsVisibleDataLossDialog(true);
} else {
this.onCancel();
}
}
onCancel() {
const { filter, setFilter } = this.props;
if (document.referrer) {
this.props.history.goBack();
} else {
setFilter(filter);
}
}
onContactsItemAdd(item) {
@ -263,6 +293,7 @@ class UpdateUserForm extends React.Component {
value: "",
});
this.setState(stateCopy);
this.setIsEdit();
}
onContactsItemTypeChange(item) {
@ -272,6 +303,7 @@ class UpdateUserForm extends React.Component {
if (element.id === id) element.type = item.value;
});
this.setState(stateCopy);
this.setIsEdit();
}
onContactsItemTextChange(event) {
@ -281,6 +313,7 @@ class UpdateUserForm extends React.Component {
if (element.id === id) element.value = event.target.value;
});
this.setState(stateCopy);
this.setIsEdit();
}
onContactsItemRemove(event) {
@ -291,6 +324,7 @@ class UpdateUserForm extends React.Component {
});
stateCopy.profile.contacts = filteredArray;
this.setState(stateCopy);
this.setIsEdit();
}
openAvatarEditor() {
@ -337,11 +371,10 @@ class UpdateUserForm extends React.Component {
data.append("file", file);
data.append("Autosave", false);
loadAvatar(profile.id, data)
loadAvatar(this.state.profile.id, data)
.then((response) => {
var img = new Image();
img.onload = function () {
_this.setState({ isLoading: false });
if (fileData) {
fileData.avatar = {
@ -349,11 +382,15 @@ class UpdateUserForm extends React.Component {
image: response.data,
defaultWidth: img.width,
defaultHeight: img.height,
}
};
if (!fileData.existImage) {
_this.onSaveAvatar(fileData.existImage) // saving empty avatar
_this.onSaveAvatar(fileData.existImage); // saving empty avatar
} else {
_this.onSaveAvatar(fileData.existImage, fileData.position, fileData.avatar)
_this.onSaveAvatar(
fileData.existImage,
fileData.position,
fileData.avatar
);
}
}
};
@ -383,6 +420,7 @@ class UpdateUserForm extends React.Component {
.then(() => {
toastr.success(this.props.t("ChangesSavedSuccessfully"));
this.setState({ isLoading: false });
this.setIsEdit();
})
.catch((error) => {
toastr.error(error);
@ -390,20 +428,19 @@ class UpdateUserForm extends React.Component {
})
.then(() => {
this.props.updateProfile(this.props.profile);
this.setState({ isLoading: false });
})
.then(() => {
this.props.fetchProfile(profile.id);
});
} else {
}); } else {
deleteAvatar(profile.id)
.then(() => {
toastr.success(this.props.t("ChangesSavedSuccessfully"));
.then(() => { toastr.success(this.props.t("ChangesSavedSuccessfully"));
this.setState({ isLoading: false });
this.setIsEdit();
})
.catch((error) => toastr.error(error))
.then(() => this.props.updateProfile(this.props.profile))
.then(() => this.props.fetchProfile(profile.id));
}
.then(() => this.props.fetchProfile(profile.id)); }
};
onCloseAvatarEditor() {
@ -439,6 +476,7 @@ class UpdateUserForm extends React.Component {
stateCopy.selector.selected = selected;
stateCopy.selector.visible = false;
this.setState(stateCopy);
this.setIsEdit();
}
onRemoveGroup(id) {
@ -450,6 +488,7 @@ class UpdateUserForm extends React.Component {
(option) => option.key !== id
);
this.setState(stateCopy);
this.setIsEdit();
}
render() {
@ -551,6 +590,7 @@ class UpdateUserForm extends React.Component {
return (
<>
<MainContainer>
<DataLossWarningDialog onContinue={this.onCancel} />
<AvatarContainer>
<Avatar
size="max"
@ -779,7 +819,7 @@ class UpdateUserForm extends React.Component {
/>
<Button
label={t("CancelButton")}
onClick={this.onCancel}
onClick={this.onCancelHandler}
isDisabled={isLoading}
size="big"
style={{ marginLeft: "8px" }}
@ -820,11 +860,16 @@ const mapStateToProps = (state) => {
profile: state.profile.targetUser,
settings: state.auth.settings,
groups: state.people.groups,
};
editingForm: state.people.editingForm,
filter: state.people.filter, };
};
export default connect(mapStateToProps, {
updateProfile,
fetchProfile,
toggleAvatarEditor,
updateProfileInUsers,
setIsVisibleDataLossDialog,
setIsEditingForm,
setFilter,
toggleAvatarEditor
})(withRouter(withTranslation()(UpdateUserForm)));

View File

@ -4,9 +4,11 @@ import { connect } from "react-redux";
import { withRouter } from "react-router";
import { IconButton } from "asc-web-components";
import { Headline } from "asc-web-common";
import { useTranslation } from "react-i18next";
import { toggleAvatarEditor } from "../../../../../store/people/actions";
import { useTranslation } from "react-i18next";import {
setFilter,
setIsVisibleDataLossDialog,
toggleAvatarEditor
} from "../../../../../store/people/actions";
const Wrapper = styled.div`
display: flex;
align-items: center;
@ -29,10 +31,13 @@ const SectionHeaderContent = (props) => {
history,
match,
settings,
filter,
editingForm,
setFilter,
setIsVisibleDataLossDialog,
toggleAvatarEditor,
avatarEditorIsOpen,
} = props;
const { userCaption, guestCaption } = settings.customNames;
avatarEditorIsOpen
} = props; const { userCaption, guestCaption } = settings.customNames;
const { type } = match.params;
const { t } = useTranslation();
@ -46,14 +51,20 @@ const SectionHeaderContent = (props) => {
? `${t("EditUserDialogTitle")} (${profile.displayName})`
: "";
const onClickBackHandler = () => {
if (editingForm.isEdit) {
setIsVisibleDataLossDialog(true, onClickBack);
} else {
onClickBack();
}
};
const onClickBack = useCallback(() => {
avatarEditorIsOpen
? toggleAvatarEditor(false)
: !profile
? history.push(settings.homepage)
: history.push(`/products/people/view/${profile.userName}`);
}, [history, profile, settings.homepage, avatarEditorIsOpen]);
: !profile || !document.referrer
? setFilter(filter)
: history.goBack();
}, [history, profile,setFilter, filter, settings.homepage, avatarEditorIsOpen]);
return (
<Wrapper>
<IconButton
@ -62,7 +73,7 @@ const SectionHeaderContent = (props) => {
size="17"
hoverColor="#657077"
isFill={true}
onClick={onClickBack}
onClick={onClickBackHandler}
className="arrow-button"
/>
<Headline className="header-headline" type="content" truncate={true}>
@ -76,10 +87,13 @@ function mapStateToProps(state) {
return {
profile: state.profile.targetUser,
settings: state.auth.settings,
avatarEditorIsOpen: state.people.avatarEditorIsOpen,
};
filter: state.people.filter,
editingForm: state.people.editingForm,
avatarEditorIsOpen: state.people.avatarEditorIsOpen, };
}
export default connect(mapStateToProps, { toggleAvatarEditor })(
withRouter(SectionHeaderContent)
);
export default connect(mapStateToProps, {
setFilter,
setIsVisibleDataLossDialog,
toggleAvatarEditor
})(withRouter(SectionHeaderContent));

View File

@ -14,6 +14,7 @@ import {
UpdateUserForm,
AvatarEditorPage,} from "./Section";
import { fetchProfile } from "../../../store/profile/actions";
import { setIsEditingForm } from "../../../store/people/actions";
import { I18nextProvider, withTranslation } from "react-i18next";
import { createI18N } from "../../../helpers/i18n";
import { setDocumentTitle } from "../../../helpers/utils";
@ -27,12 +28,14 @@ const { isAdmin } = store.auth.selectors;
class ProfileAction extends React.Component {
componentDidMount() {
const { match, fetchProfile, t } = this.props;
const { match, fetchProfile, isEdit, setIsEditingForm, t } = this.props;
const { userId } = match.params;
setDocumentTitle(t("ProfileAction"));
changeLanguage(i18n);
if (isEdit) {
setIsEditingForm(false);
}
if (userId) {
fetchProfile(userId);
}
@ -137,10 +140,11 @@ function mapStateToProps(state) {
isVisitor: state.auth.user.isVisitor,
profile: state.profile.targetUser,
isAdmin: isAdmin(state),
isEdit: state.people.editingForm.isEdit,
avatarEditorIsOpen: state.people.avatarEditorIsOpen,
};
}
export default connect(mapStateToProps, { fetchProfile })(
export default connect(mapStateToProps, { fetchProfile, setIsEditingForm })(
ProfileActionContainer
);

View File

@ -9,8 +9,10 @@ import {
SORT_BY,
SORT_ORDER,
PAGE,
PAGE_COUNT
PAGE_COUNT,
} from "../../helpers/constants";
import { getUserByUserName } from "../people/selectors";
const { EmployeeStatus } = constants;
const { Filter } = api;
@ -24,40 +26,42 @@ export const SET_SELECTED = "SET_SELECTED";
export const SET_FILTER = "SET_FILTER";
export const SELECT_GROUP = "SELECT_GROUP";
export const SET_SELECTOR_USERS = "SET_SELECTOR_USERS";
export const SET_IS_VISIBLE_DATA_LOSS_DIALOG =
"SET_IS_VISIBLE_DATA_LOSS_DIALOG";
export const SET_IS_EDITING_FORM = "SET_IS_EDITING_FORM";
export const TOGGLE_AVATAR_EDITOR = "TOGGLE_AVATAR_EDITOR"
export function setUser(user) {
return {
type: SET_USER,
user
user,
};
}
export function setUsers(users) {
return {
type: SET_USERS,
users
users,
};
}
export function setGroups(groups) {
return {
type: SET_GROUPS,
groups
groups,
};
}
export function setSelection(selection) {
return {
type: SET_SELECTION,
selection
selection,
};
}
export function setSelected(selected) {
return {
type: SET_SELECTED,
selected
selected,
};
}
@ -76,14 +80,14 @@ export function selectGroup(groupId) {
export function selectUser(user) {
return {
type: SELECT_USER,
user
user,
};
}
export function deselectUser(user) {
return {
type: DESELECT_USER,
user
user,
};
}
@ -136,20 +140,35 @@ export function setFilter(filter) {
setFilterUrl(filter);
return {
type: SET_FILTER,
filter
filter,
};
}
export function setSelectorUsers(users) {
return {
type: SET_SELECTOR_USERS,
users
users,
};
}
export function setIsVisibleDataLossDialog(isVisible, callback) {
return {
type: SET_IS_VISIBLE_DATA_LOSS_DIALOG,
isVisible,
callback,
};
}
export function setIsEditingForm(isEdit) {
return {
type: SET_IS_EDITING_FORM,
isEdit,
};
}
export function fetchSelectorUsers() {
return dispatch => {
api.people.getSelectorUserList().then(data => {
return (dispatch) => {
api.people.getSelectorUserList().then((data) => {
const users = data.items;
return dispatch(setSelectorUsers(users));
});
@ -157,10 +176,10 @@ export function fetchSelectorUsers() {
}
export function fetchGroups(dispatchFunc = null) {
return api.groups.getGroupList().then(groups => {
return api.groups.getGroupList().then((groups) => {
return dispatchFunc
? dispatchFunc(setGroups(groups))
: Promise.resolve(dispatch => dispatch(setGroups(groups)));
: Promise.resolve((dispatch) => dispatch(setGroups(groups)));
});
}
@ -179,10 +198,22 @@ export function fetchPeople(filter, dispatchFunc = null) {
}
export function removeUser(userId, filter) {
return dispatch => {
return api.people.deleteUsers(userId)
.then(() => fetchPeople(filter, dispatch))
return (dispatch) => {
return api.people
.deleteUsers(userId)
.then(() => fetchPeople(filter, dispatch));
};
}
export function updateUserList(dispatch, filter) {
let filterData = filter && filter.clone();
if (!filterData) {
filterData = Filter.getDefault();
filterData.employeeStatus = EmployeeStatus.Active;
}
return api.people.getUserList(filterData).then((data) => {
return dispatch(setUsers(data.items));
});
}
function fetchPeopleByFilter(dispatch, filter) {
@ -193,12 +224,12 @@ function fetchPeopleByFilter(dispatch, filter) {
filterData.employeeStatus = EmployeeStatus.Active;
}
return api.people.getUserList(filterData).then(data => {
return api.people.getUserList(filterData).then((data) => {
filterData.total = data.total;
dispatch(setFilter(filterData));
dispatch({
type: SELECT_GROUP,
groupId: filterData.group
groupId: filterData.group,
});
return dispatch(setUsers(data.items));
});
@ -206,8 +237,7 @@ function fetchPeopleByFilter(dispatch, filter) {
export function updateUserStatus(status, userIds, isRefetchPeople = false) {
return (dispatch, getState) => {
return api.people.updateUserStatus(status, userIds)
.then(users => {
return api.people.updateUserStatus(status, userIds).then((users) => {
const { people } = getState();
const { filter } = people;
return isRefetchPeople
@ -218,9 +248,9 @@ export function updateUserStatus(status, userIds, isRefetchPeople = false) {
}
export function updateUserType(type, userIds) {
return dispatch => {
return api.people.updateUserType(type, userIds).then(users => {
users.forEach(user => {
return (dispatch) => {
return api.people.updateUserType(type, userIds).then((users) => {
users.forEach((user) => {
dispatch(setUser(user));
});
});
@ -237,3 +267,43 @@ export function resetFilter() {
return fetchPeople(newFilter, dispatch);
};
}
export function updateProfileInUsers(updatedProfile) {
return (dispatch, getState) => {
const { people } = getState();
const { users } = people;
if (!users) {
return updateUserList(dispatch);
}
if (!updatedProfile) {
const { profile } = getState();
updatedProfile = profile.targetUser;
}
const { userName } = updatedProfile;
const oldProfile = getUserByUserName(users, userName);
const newProfile = {};
for (let key in oldProfile) {
if (
updatedProfile.hasOwnProperty(key) &&
updatedProfile[key] !== oldProfile[key]
) {
newProfile[key] = updatedProfile[key];
} else {
newProfile[key] = oldProfile[key];
}
}
const updatedUsers = users.map((user) => {
if (user.id === newProfile.id) {
return newProfile;
} else {
return user;
}
});
return dispatch(setUsers(updatedUsers));
};
}

View File

@ -9,6 +9,8 @@ import {
SELECT_GROUP,
SET_USER,
SET_SELECTOR_USERS,
SET_IS_VISIBLE_DATA_LOSS_DIALOG,
SET_IS_EDITING_FORM,
TOGGLE_AVATAR_EDITOR,
} from "./actions";
import { isUserSelected, skipUser, getUsersBySelected } from "./selectors";
@ -26,7 +28,10 @@ const initialState = {
selector: {
users: [],
},
};
editingForm: {
isEdit: false,
isVisibleDataLossDialog: false,
},};
const peopleReducer = (state = initialState, action) => {
switch (action.type) {
@ -79,11 +84,25 @@ const peopleReducer = (state = initialState, action) => {
users: action.users,
}),
});
case SET_IS_VISIBLE_DATA_LOSS_DIALOG:
return Object.assign({}, state, {
editingForm: {
...state.editingForm,
isVisibleDataLossDialog: action.isVisible,
callback: action.callback,
},
});
case SET_IS_EDITING_FORM:
return Object.assign({}, state, {
editingForm: {
...state.editingForm,
isEdit: action.isEdit,
},
});
case TOGGLE_AVATAR_EDITOR:
return Object.assign({}, state, {
avatarEditorIsOpen: action.avatarEditorIsOpen,
});
default:
}); default:
return state;
}
};

View File

@ -1,5 +1,4 @@
import { getUserByUserName } from "../people/selectors";
import { fetchPeople } from "../people/actions";
import { updateUserList } from "../people/actions";
import { store, api } from "asc-web-common";
const { setCurrentUser } = store.auth.actions;
const { isMe } = store.auth.selectors;
@ -43,7 +42,6 @@ export function fetchProfile(userName) {
}
};
}
export function createProfile(profile) {
return (dispatch, getState) => {
const { people } = getState();
@ -58,7 +56,7 @@ export function createProfile(profile) {
return dispatch(setProfile(user));
})
.then(() => {
return fetchPeople(filter, dispatch);
return updateUserList(dispatch, filter);
})
.then(() => {
return Promise.resolve(result);
@ -67,9 +65,7 @@ export function createProfile(profile) {
}
export function updateProfile(profile) {
return (dispatch, getState) => {
const { people } = getState();
const { filter } = people;
return (dispatch) => {
const member = employeeWrapperToMemberModel(profile);
let result;
@ -79,15 +75,11 @@ export function updateProfile(profile) {
result = user;
return Promise.resolve(dispatch(setProfile(user)));
})
.then(() => {
return fetchPeople(filter, dispatch);
})
.then(() => {
return Promise.resolve(result);
});
};
}
export function updateProfileCulture(id, culture) {
return (dispatch) => {
return api.people.updateUserCulture(id, culture).then((user) => {
@ -100,3 +92,18 @@ export function updateProfileCulture(id, culture) {
export function getUserPhoto(id) {
return api.people.getUserPhoto(id);
}
export function updateCreatedAvatar(avatar) {
return (dispatch, getState) => {
const { big, max, medium, small } = avatar;
const { profile } = getState();
const newProfile = {
...profile.targetUser,
avatarMax: max,
avatarMedium: medium,
avatar: big,
avatarSmall: small,
};
return dispatch(setProfile(newProfile));
};
}

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-common",
"version": "1.0.248",
"version": "1.0.249",
"description": "Ascensio System SIA common components and solutions library",
"license": "AGPL-3.0",
"files": [

View File

@ -3,7 +3,7 @@ import styled from "styled-components";
const StyledMain = styled.main`
height: calc(100vh - 56px);
/*height: calc(var(--vh, 1vh) * 100 - 56px);*/
height: calc(var(--vh, 1vh) * 100 - 56px);
width: 100vw;
z-index: 0;
display: flex;