Merge branch 'master' of https://github.com/ONLYOFFICE/CommunityServer-AspNetCore
This commit is contained in:
commit
92a5d6d483
@ -30,10 +30,6 @@ class Profile extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.resetProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.log("Profile render")
|
console.log("Profile render")
|
||||||
|
|
||||||
|
@ -1,394 +0,0 @@
|
|||||||
import React, { useCallback } from 'react'
|
|
||||||
import { withRouter } from 'react-router'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { Field, FieldArray, reduxForm, formValueSelector } from 'redux-form'
|
|
||||||
import { device, Avatar, Button, TextInput, Textarea, DateInput, Label, RadioButton, Text, toastr, SelectedItem } from 'asc-web-components'
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { createProfile, updateProfile } from '../../../../../../store/profile/actions';
|
|
||||||
|
|
||||||
const formName = "userForm";
|
|
||||||
|
|
||||||
const getUserRole = user => {
|
|
||||||
if(user.isOwner) return "owner";
|
|
||||||
if(user.isAdmin) return "admin";
|
|
||||||
if(user.isVisitor) return "guest";
|
|
||||||
return "user";
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEditAvatar = () => {};
|
|
||||||
|
|
||||||
const MainContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
@media ${device.tablet} {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const AvatarContainer = styled.div`
|
|
||||||
margin: 0 32px 32px 0;
|
|
||||||
width: 160px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MainFieldsContainer = styled.div`
|
|
||||||
flex-grow: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FieldContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
line-height: 32px;
|
|
||||||
margin: 0;
|
|
||||||
width: 110px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-input {
|
|
||||||
width: 320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-group {
|
|
||||||
line-height: 32px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
label:not(:first-child) {
|
|
||||||
margin-left: 33px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media ${device.tablet} {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: start;
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
line-height: unset;
|
|
||||||
margin: 0 0 4px 0;
|
|
||||||
width: auto;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FieldBody = styled.div`
|
|
||||||
flex-grow: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RadioGroupFieldBody = styled(FieldBody).attrs({
|
|
||||||
className: "radio-group"
|
|
||||||
})``;
|
|
||||||
|
|
||||||
const renderTextField = ({ input, label, isRequired, meta: { touched, error } }) => (
|
|
||||||
<FieldContainer>
|
|
||||||
<Label isRequired={isRequired} error={!!(touched && error)} text={label} className="field-label"/>
|
|
||||||
<FieldBody>
|
|
||||||
<TextInput {...input} type="text" className="field-input"/>
|
|
||||||
</FieldBody>
|
|
||||||
</FieldContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderPasswordField = ({ input, isDisabled }) => (
|
|
||||||
<TextInput {...input} type="password" autoComplete="new-password" className="field-input" isDisabled={isDisabled} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderDateField = ({ input, label, isRequired, meta: { touched, error } }) => (
|
|
||||||
<FieldContainer>
|
|
||||||
<Label isRequired={isRequired} error={touched && !!error} text={label} className="field-label"/>
|
|
||||||
<FieldBody>
|
|
||||||
<DateInput {...input} selected={input.value instanceof Date ? input.value : undefined}/>
|
|
||||||
</FieldBody>
|
|
||||||
</FieldContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderRadioField = ({ input, label, isChecked }) => (
|
|
||||||
<RadioButton {...input} label={label} isChecked={isChecked}/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderDepartmentField = ({ input, onClick }) => (
|
|
||||||
<SelectedItem
|
|
||||||
text={input.value}
|
|
||||||
onClose={onClick}
|
|
||||||
isInline={true}
|
|
||||||
style={{ marginRight: "8px", marginBottom: "8px" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderDepartmentsField = ({ fields, label }) => {
|
|
||||||
return (
|
|
||||||
<FieldContainer>
|
|
||||||
<Label text={label} className="field-label"/>
|
|
||||||
<FieldBody>
|
|
||||||
{fields.map((member, index) => (
|
|
||||||
<Field
|
|
||||||
key={`${member}${index}`}
|
|
||||||
name={`${member}.name`}
|
|
||||||
component={renderDepartmentField}
|
|
||||||
onClick={() => fields.remove(index)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</FieldBody>
|
|
||||||
</FieldContainer>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTextareaField = ({ input }) => (
|
|
||||||
<Textarea {...input} />
|
|
||||||
);
|
|
||||||
|
|
||||||
let UserForm = (props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const {
|
|
||||||
error,
|
|
||||||
handleSubmit,
|
|
||||||
submitting,
|
|
||||||
initialValues,
|
|
||||||
sexValue,
|
|
||||||
passwordTypeValue,
|
|
||||||
passwordError,
|
|
||||||
userType,
|
|
||||||
history,
|
|
||||||
updateProfile,
|
|
||||||
createProfile
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const employeeWrapperToMemberModel = (profile) => {
|
|
||||||
const comment = profile.notes;
|
|
||||||
const department = profile.groups ? profile.groups.map(group => group.id) : [];
|
|
||||||
const worksFrom = profile.workFrom;
|
|
||||||
|
|
||||||
return {...profile, comment, department, worksFrom};
|
|
||||||
}
|
|
||||||
|
|
||||||
const onCancel = useCallback(() => {
|
|
||||||
history.goBack();
|
|
||||||
}, [history]);
|
|
||||||
|
|
||||||
const onSubmit = useCallback(async (values) => {
|
|
||||||
try {
|
|
||||||
const member = employeeWrapperToMemberModel(values);
|
|
||||||
|
|
||||||
if (values.id) {
|
|
||||||
await updateProfile(member);
|
|
||||||
} else {
|
|
||||||
await createProfile(member);
|
|
||||||
}
|
|
||||||
|
|
||||||
toastr.success("Success");
|
|
||||||
history.goBack();
|
|
||||||
} catch(error) {
|
|
||||||
toastr.error(error.message);
|
|
||||||
}
|
|
||||||
}, [history, updateProfile, createProfile]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<MainContainer>
|
|
||||||
<AvatarContainer>
|
|
||||||
{
|
|
||||||
initialValues.id
|
|
||||||
? <Avatar
|
|
||||||
size="max"
|
|
||||||
role={getUserRole(initialValues)}
|
|
||||||
source={initialValues.avatarMax}
|
|
||||||
userName={initialValues.displayName}
|
|
||||||
editing={true}
|
|
||||||
editLabel={t("Edit Photo")}
|
|
||||||
editAction={onEditAvatar}
|
|
||||||
/>
|
|
||||||
: <Avatar
|
|
||||||
size="max"
|
|
||||||
role={userType}
|
|
||||||
editing={true}
|
|
||||||
editLabel={t("AddPhoto")}
|
|
||||||
editAction={onEditAvatar}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</AvatarContainer>
|
|
||||||
<MainFieldsContainer>
|
|
||||||
<Field
|
|
||||||
name="firstName"
|
|
||||||
component={renderTextField}
|
|
||||||
label={`${t("FirstName")}:`}
|
|
||||||
isRequired={true}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="lastName"
|
|
||||||
component={renderTextField}
|
|
||||||
label={`${t("LastName")}:`}
|
|
||||||
isRequired={true}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="email"
|
|
||||||
component={renderTextField}
|
|
||||||
label={`${t("Email")}:`}
|
|
||||||
isRequired={true}
|
|
||||||
/>
|
|
||||||
<FieldContainer>
|
|
||||||
<Label
|
|
||||||
text={`${t("Password")}:`}
|
|
||||||
isRequired={true}
|
|
||||||
error={passwordError}
|
|
||||||
className="field-label"
|
|
||||||
/>
|
|
||||||
<FieldBody>
|
|
||||||
<RadioGroupFieldBody>
|
|
||||||
<Field
|
|
||||||
component={renderRadioField}
|
|
||||||
type="radio"
|
|
||||||
name="passwordType"
|
|
||||||
value="link"
|
|
||||||
label={t("ActivationLink")}
|
|
||||||
isChecked={passwordTypeValue === "link"}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
component={renderRadioField}
|
|
||||||
type="radio"
|
|
||||||
name="passwordType"
|
|
||||||
value="temp"
|
|
||||||
label={t("TemporaryPassword")}
|
|
||||||
isChecked={passwordTypeValue === "temp"}
|
|
||||||
/>
|
|
||||||
</RadioGroupFieldBody>
|
|
||||||
<Field
|
|
||||||
name="password"
|
|
||||||
component={renderPasswordField}
|
|
||||||
isDisabled={passwordTypeValue === "link"}
|
|
||||||
/>
|
|
||||||
</FieldBody>
|
|
||||||
</FieldContainer>
|
|
||||||
<Field
|
|
||||||
name="birthday"
|
|
||||||
component={renderDateField}
|
|
||||||
label={`${t("Birthdate")}:`}
|
|
||||||
/>
|
|
||||||
<FieldContainer>
|
|
||||||
<Label
|
|
||||||
text={`${t("Sex")}:`}
|
|
||||||
className="field-label"
|
|
||||||
/>
|
|
||||||
<RadioGroupFieldBody>
|
|
||||||
<Field
|
|
||||||
component={renderRadioField}
|
|
||||||
type="radio"
|
|
||||||
name="sex"
|
|
||||||
value="male"
|
|
||||||
label={t("SexMale")}
|
|
||||||
isChecked={sexValue === "male"}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
component={renderRadioField}
|
|
||||||
type="radio"
|
|
||||||
name="sex"
|
|
||||||
value="female"
|
|
||||||
label={t("SexFemale")}
|
|
||||||
isChecked={sexValue === "female"}
|
|
||||||
/>
|
|
||||||
</RadioGroupFieldBody>
|
|
||||||
</FieldContainer>
|
|
||||||
<Field
|
|
||||||
name="workFrom"
|
|
||||||
component={renderDateField}
|
|
||||||
label={`EmployedSinceDate:`}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="location"
|
|
||||||
component={renderTextField}
|
|
||||||
label={`${t("Location")}:`}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="title"
|
|
||||||
component={renderTextField}
|
|
||||||
label={`Position:`}
|
|
||||||
/>
|
|
||||||
<FieldArray
|
|
||||||
name="groups"
|
|
||||||
component={renderDepartmentsField}
|
|
||||||
label={`Departments`}
|
|
||||||
/>
|
|
||||||
</MainFieldsContainer>
|
|
||||||
</MainContainer>
|
|
||||||
<div>
|
|
||||||
<Text.ContentHeader>{t("Comments")}</Text.ContentHeader>
|
|
||||||
<Field
|
|
||||||
name="notes"
|
|
||||||
component={renderTextareaField}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{error && <strong>{error}</strong>}
|
|
||||||
</div>
|
|
||||||
<div style={{marginTop: "60px"}}>
|
|
||||||
<Button
|
|
||||||
label={t("SaveButton")}
|
|
||||||
primary
|
|
||||||
type="submit"
|
|
||||||
isDisabled={submitting}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
label={t("CancelButton")}
|
|
||||||
style={{ marginLeft: "8px" }}
|
|
||||||
isDisabled={submitting}
|
|
||||||
onClick={onCancel}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const validate = (values) => {
|
|
||||||
const requiredFieldText = "RequiredField";
|
|
||||||
const errors = {};
|
|
||||||
|
|
||||||
if (!values.firstName)
|
|
||||||
errors.firstName = requiredFieldText;
|
|
||||||
|
|
||||||
if (!values.lastName)
|
|
||||||
errors.lastName = requiredFieldText;
|
|
||||||
|
|
||||||
if (!values.email)
|
|
||||||
errors.email = requiredFieldText;
|
|
||||||
|
|
||||||
if (values.passwordType === "temp" && !values.password)
|
|
||||||
errors.password = requiredFieldText;
|
|
||||||
|
|
||||||
return errors
|
|
||||||
};
|
|
||||||
|
|
||||||
UserForm = reduxForm({
|
|
||||||
validate,
|
|
||||||
form: formName,
|
|
||||||
enableReinitialize: true,
|
|
||||||
})(withRouter(UserForm))
|
|
||||||
|
|
||||||
const selector = formValueSelector(formName)
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
|
||||||
const sexValue = selector(state, "sex") || "male";
|
|
||||||
const passwordTypeValue = selector(state, "passwordType") || "link";
|
|
||||||
const passwordError =
|
|
||||||
passwordTypeValue !== "link" &&
|
|
||||||
state &&
|
|
||||||
state.form &&
|
|
||||||
state.form.userForm &&
|
|
||||||
state.form.userForm.fields &&
|
|
||||||
state.form.userForm.fields.password &&
|
|
||||||
state.form.userForm.fields.password.touched &&
|
|
||||||
!selector(state, "password");
|
|
||||||
|
|
||||||
return {
|
|
||||||
sexValue,
|
|
||||||
passwordTypeValue,
|
|
||||||
passwordError
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
{
|
|
||||||
createProfile,
|
|
||||||
updateProfile
|
|
||||||
}
|
|
||||||
)(UserForm)
|
|
@ -0,0 +1,231 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { withRouter } from 'react-router'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { Avatar, Button, Textarea, Text, toastr } from 'asc-web-components'
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import { toEmployeeWrapper, getUserRole, profileEqual, createProfile } from '../../../../../store/profile/actions';
|
||||||
|
import { MainContainer, AvatarContainer, MainFieldsContainer, TextField, PasswordField, DateField, RadioField, DepartmentField } from './userFormFields'
|
||||||
|
|
||||||
|
class CreateUserForm extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = this.mapPropsToState(props);
|
||||||
|
|
||||||
|
this.validate = this.validate.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
this.onTextChange = this.onTextChange.bind(this);
|
||||||
|
this.onBirthdayDateChange = this.onBirthdayDateChange.bind(this);
|
||||||
|
this.onWorkFromDateChange = this.onWorkFromDateChange.bind(this);
|
||||||
|
this.onGroupClose = this.onGroupClose.bind(this);
|
||||||
|
this.onCancel = this.onCancel.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (!profileEqual(this.props.profile, prevProps.profile)) {
|
||||||
|
this.setState(this.mapPropsToState(this.props));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapPropsToState = (props) => {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
errors: {
|
||||||
|
firstName: false,
|
||||||
|
lastName: false,
|
||||||
|
email: false,
|
||||||
|
password: false,
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
...{ passwordType: "link" },
|
||||||
|
...toEmployeeWrapper(props.profile)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChange(event) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile[event.target.name] = event.target.value;
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBirthdayDateChange(value) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile.birthday = value ? value.toJSON() : null;
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
onWorkFromDateChange(value) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile.workFrom = value ? value.toJSON() : null;
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
onGroupClose(id) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile.groups = this.state.groups.filter((group) => group.id !== id);
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
const errors = {
|
||||||
|
firstName: !this.state.profile.firstName,
|
||||||
|
lastName: !this.state.profile.lastName,
|
||||||
|
email: !this.state.profile.email,
|
||||||
|
password: this.state.profile.passwordType === "temp" && !this.state.profile.password
|
||||||
|
};
|
||||||
|
const hasError = errors.firstName || errors.lastName || errors.email || errors.password;
|
||||||
|
this.setState({errors: errors});
|
||||||
|
return !hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit() {
|
||||||
|
if(!this.validate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.setState({isLoading: true});
|
||||||
|
|
||||||
|
this.props.createProfile(this.state.profile)
|
||||||
|
.then(() => {
|
||||||
|
toastr.success("Success");
|
||||||
|
this.props.history.goBack();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toastr.error(error.message)
|
||||||
|
this.setState({isLoading: false})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel() {
|
||||||
|
this.props.history.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MainContainer>
|
||||||
|
<AvatarContainer>
|
||||||
|
<Avatar
|
||||||
|
size="max"
|
||||||
|
role={getUserRole(this.state.profile)}
|
||||||
|
editing={true}
|
||||||
|
editLabel={this.props.t("AddPhoto")}
|
||||||
|
/>
|
||||||
|
</AvatarContainer>
|
||||||
|
<MainFieldsContainer>
|
||||||
|
<TextField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.firstName}
|
||||||
|
labelText={`${this.props.t("FirstName")}:`}
|
||||||
|
inputName="firstName"
|
||||||
|
inputValue={this.state.profile.firstName}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.lastName}
|
||||||
|
labelText={`${this.props.t("LastName")}:`}
|
||||||
|
inputName="lastName"
|
||||||
|
inputValue={this.state.profile.lastName}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.email}
|
||||||
|
labelText={`${this.props.t("Email")}:`}
|
||||||
|
inputName="email"
|
||||||
|
inputValue={this.state.profile.email}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<PasswordField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.password}
|
||||||
|
labelText={`${this.props.t("Password")}:`}
|
||||||
|
radioName="passwordType"
|
||||||
|
radioValue={this.state.profile.passwordType}
|
||||||
|
radioOptions={[
|
||||||
|
{ value: 'link', label: this.props.t("ActivationLink")},
|
||||||
|
{ value: 'temp', label: this.props.t("TemporaryPassword")}
|
||||||
|
]}
|
||||||
|
radioIsDisabled={this.state.isLoading}
|
||||||
|
radioOnChange={this.onTextChange}
|
||||||
|
inputName="password"
|
||||||
|
inputValue={this.state.profile.password}
|
||||||
|
inputIsDisabled={this.state.isLoading || this.state.profile.passwordType === "link"}
|
||||||
|
inputOnChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<DateField
|
||||||
|
labelText={`${this.props.t("Birthdate")}:`}
|
||||||
|
inputName="birthday"
|
||||||
|
inputValue={this.state.profile.birthday ? new Date(this.state.profile.birthday) : undefined}
|
||||||
|
inputIsDisabled={this.state.isLoading}
|
||||||
|
inputOnChange={this.onBirthdayDateChange}
|
||||||
|
/>
|
||||||
|
<RadioField
|
||||||
|
labelText={`${this.props.t("Sex")}:`}
|
||||||
|
radioName="sex"
|
||||||
|
radioValue={this.state.profile.sex}
|
||||||
|
radioOptions={[
|
||||||
|
{ value: 'male', label: this.props.t("SexMale")},
|
||||||
|
{ value: 'female', label: this.props.t("SexFemale")}
|
||||||
|
]}
|
||||||
|
radioIsDisabled={this.state.isLoading}
|
||||||
|
radioOnChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<DateField
|
||||||
|
labelText={`${this.props.t("EmployedSinceDate")}:`}
|
||||||
|
inputName="workFrom"
|
||||||
|
inputValue={this.state.profile.workFrom ? new Date(this.state.profile.workFrom) : undefined}
|
||||||
|
inputIsDisabled={this.state.isLoading}
|
||||||
|
inputOnChange={this.onWorkFromDateChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
labelText={`${this.props.t("Location")}:`}
|
||||||
|
inputName="location"
|
||||||
|
inputValue={this.state.profile.location}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
labelText={`${this.props.t("Position")}:`}
|
||||||
|
inputName="title"
|
||||||
|
inputValue={this.state.profile.title}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<DepartmentField
|
||||||
|
labelText={`${this.props.t("Departments")}:`}
|
||||||
|
departments={this.state.profile.groups}
|
||||||
|
onClose={this.onGroupClose}
|
||||||
|
/>
|
||||||
|
</MainFieldsContainer>
|
||||||
|
</MainContainer>
|
||||||
|
<div>
|
||||||
|
<Text.ContentHeader>{this.props.t("Comments")}</Text.ContentHeader>
|
||||||
|
<Textarea name="notes" value={this.state.profile.notes} isDisabled={this.state.isLoading} onChange={this.onTextChange}/>
|
||||||
|
</div>
|
||||||
|
<div style={{marginTop: "60px"}}>
|
||||||
|
<Button label={this.props.t("SaveButton")} onClick={this.handleSubmit} primary isDisabled={this.state.isLoading} size="big"/>
|
||||||
|
<Button label={this.props.t("CancelButton")} onClick={this.onCancel} isDisabled={this.state.isLoading} size="big" style={{ marginLeft: "8px" }}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
profile: state.profile.targetUser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
{
|
||||||
|
createProfile
|
||||||
|
}
|
||||||
|
)(withRouter(withTranslation()(CreateUserForm)));
|
@ -1,30 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import UserForm from './Form/userForm'
|
|
||||||
|
|
||||||
|
|
||||||
const SectionBodyContent = (props) => {
|
|
||||||
const {profile, userType} = props;
|
|
||||||
|
|
||||||
// if(profile.birthday)
|
|
||||||
// profile.birthday = new Date(profile.birthday).toLocaleDateString();
|
|
||||||
|
|
||||||
// if(profile.workFrom)
|
|
||||||
// profile.workFrom = new Date(profile.workFrom).toLocaleDateString();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UserForm initialValues={profile} userType={userType}/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
SectionBodyContent.propTypes = {
|
|
||||||
profile: PropTypes.object,
|
|
||||||
userType: PropTypes.oneOf(["user", "guest"])
|
|
||||||
};
|
|
||||||
|
|
||||||
SectionBodyContent.defaultProps = {
|
|
||||||
profile: null,
|
|
||||||
userType: "user"
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SectionBodyContent;
|
|
@ -0,0 +1,233 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { withRouter } from 'react-router'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { Avatar, Button, Textarea, Text, toastr } from 'asc-web-components'
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import { toEmployeeWrapper, getUserRole, profileEqual, updateProfile } from '../../../../../store/profile/actions';
|
||||||
|
import { MainContainer, AvatarContainer, MainFieldsContainer, TextField, PasswordField, DateField, RadioField, DepartmentField } from './userFormFields'
|
||||||
|
|
||||||
|
class UpdateUserForm extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = this.mapPropsToState(props);
|
||||||
|
|
||||||
|
this.validate = this.validate.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
this.onTextChange = this.onTextChange.bind(this);
|
||||||
|
this.onBirthdayDateChange = this.onBirthdayDateChange.bind(this);
|
||||||
|
this.onWorkFromDateChange = this.onWorkFromDateChange.bind(this);
|
||||||
|
this.onGroupClose = this.onGroupClose.bind(this);
|
||||||
|
this.onCancel = this.onCancel.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (!profileEqual(this.props.profile, prevProps.profile)) {
|
||||||
|
this.setState(this.mapPropsToState(this.props));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapPropsToState = (props) => {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
errors: {
|
||||||
|
firstName: false,
|
||||||
|
lastName: false,
|
||||||
|
email: false,
|
||||||
|
password: false,
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
...{ passwordType: "link" },
|
||||||
|
...toEmployeeWrapper(props.profile)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChange(event) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile[event.target.name] = event.target.value;
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBirthdayDateChange(value) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile.birthday = value ? value.toJSON() : null;
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
onWorkFromDateChange(value) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile.workFrom = value ? value.toJSON() : null;
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
onGroupClose(id) {
|
||||||
|
var stateCopy = Object.assign({}, this.state);
|
||||||
|
stateCopy.profile.groups = this.state.groups.filter((group) => group.id !== id);
|
||||||
|
this.setState(stateCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
const errors = {
|
||||||
|
firstName: !this.state.profile.firstName,
|
||||||
|
lastName: !this.state.profile.lastName,
|
||||||
|
email: !this.state.profile.email,
|
||||||
|
password: this.state.profile.passwordType === "temp" && !this.state.profile.password
|
||||||
|
};
|
||||||
|
const hasError = errors.firstName || errors.lastName || errors.email || errors.password;
|
||||||
|
this.setState({errors: errors});
|
||||||
|
return !hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit() {
|
||||||
|
if(!this.validate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.setState({isLoading: true});
|
||||||
|
|
||||||
|
this.props.updateProfile(this.state.profile)
|
||||||
|
.then(() => {
|
||||||
|
toastr.success("Success");
|
||||||
|
this.props.history.goBack();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toastr.error(error.message)
|
||||||
|
this.setState({isLoading: false})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel() {
|
||||||
|
this.props.history.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MainContainer>
|
||||||
|
<AvatarContainer>
|
||||||
|
<Avatar
|
||||||
|
size="max"
|
||||||
|
role={getUserRole(this.state.profile)}
|
||||||
|
source={this.state.profile.avatarMax}
|
||||||
|
userName={this.state.profile.displayName}
|
||||||
|
editing={true}
|
||||||
|
editLabel={this.props.t("EditPhoto")}
|
||||||
|
/>
|
||||||
|
</AvatarContainer>
|
||||||
|
<MainFieldsContainer>
|
||||||
|
<TextField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.firstName}
|
||||||
|
labelText={`${this.props.t("FirstName")}:`}
|
||||||
|
inputName="firstName"
|
||||||
|
inputValue={this.state.profile.firstName}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.lastName}
|
||||||
|
labelText={`${this.props.t("LastName")}:`}
|
||||||
|
inputName="lastName"
|
||||||
|
inputValue={this.state.profile.lastName}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.email}
|
||||||
|
labelText={`${this.props.t("Email")}:`}
|
||||||
|
inputName="email"
|
||||||
|
inputValue={this.state.profile.email}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<PasswordField
|
||||||
|
isRequired={true}
|
||||||
|
hasError={this.state.errors.password}
|
||||||
|
labelText={`${this.props.t("Password")}:`}
|
||||||
|
radioName="passwordType"
|
||||||
|
radioValue={this.state.profile.passwordType}
|
||||||
|
radioOptions={[
|
||||||
|
{ value: 'link', label: this.props.t("ActivationLink")},
|
||||||
|
{ value: 'temp', label: this.props.t("TemporaryPassword")}
|
||||||
|
]}
|
||||||
|
radioIsDisabled={this.state.isLoading}
|
||||||
|
radioOnChange={this.onTextChange}
|
||||||
|
inputName="password"
|
||||||
|
inputValue={this.state.profile.password}
|
||||||
|
inputIsDisabled={this.state.isLoading || this.state.profile.passwordType === "link"}
|
||||||
|
inputOnChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<DateField
|
||||||
|
labelText={`${this.props.t("Birthdate")}:`}
|
||||||
|
inputName="birthday"
|
||||||
|
inputValue={this.state.profile.birthday ? new Date(this.state.profile.birthday) : undefined}
|
||||||
|
inputIsDisabled={this.state.isLoading}
|
||||||
|
inputOnChange={this.onBirthdayDateChange}
|
||||||
|
/>
|
||||||
|
<RadioField
|
||||||
|
labelText={`${this.props.t("Sex")}:`}
|
||||||
|
radioName="sex"
|
||||||
|
radioValue={this.state.profile.sex}
|
||||||
|
radioOptions={[
|
||||||
|
{ value: 'male', label: this.props.t("SexMale")},
|
||||||
|
{ value: 'female', label: this.props.t("SexFemale")}
|
||||||
|
]}
|
||||||
|
radioIsDisabled={this.state.isLoading}
|
||||||
|
radioOnChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<DateField
|
||||||
|
labelText={`${this.props.t("EmployedSinceDate")}:`}
|
||||||
|
inputName="workFrom"
|
||||||
|
inputValue={this.state.profile.workFrom ? new Date(this.state.profile.workFrom) : undefined}
|
||||||
|
inputIsDisabled={this.state.isLoading}
|
||||||
|
inputOnChange={this.onWorkFromDateChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
labelText={`${this.props.t("Location")}:`}
|
||||||
|
inputName="location"
|
||||||
|
inputValue={this.state.profile.location}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
labelText={`${this.props.t("Position")}:`}
|
||||||
|
inputName="title"
|
||||||
|
inputValue={this.state.profile.title}
|
||||||
|
isDisabled={this.state.isLoading}
|
||||||
|
onChange={this.onTextChange}
|
||||||
|
/>
|
||||||
|
<DepartmentField
|
||||||
|
labelText={`${this.props.t("Departments")}:`}
|
||||||
|
departments={this.state.profile.groups}
|
||||||
|
onClose={this.onGroupClose}
|
||||||
|
/>
|
||||||
|
</MainFieldsContainer>
|
||||||
|
</MainContainer>
|
||||||
|
<div>
|
||||||
|
<Text.ContentHeader>{this.props.t("Comments")}</Text.ContentHeader>
|
||||||
|
<Textarea name="notes" value={this.state.profile.notes} isDisabled={this.state.isLoading} onChange={this.onTextChange}/>
|
||||||
|
</div>
|
||||||
|
<div style={{marginTop: "60px"}}>
|
||||||
|
<Button label={this.props.t("SaveButton")} onClick={this.handleSubmit} primary isDisabled={this.state.isLoading} size="big"/>
|
||||||
|
<Button label={this.props.t("CancelButton")} onClick={this.onCancel} isDisabled={this.state.isLoading} size="big" style={{ marginLeft: "8px" }}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
profile: state.profile.targetUser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
{
|
||||||
|
updateProfile
|
||||||
|
}
|
||||||
|
)(withRouter(withTranslation()(UpdateUserForm)));
|
@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { device, FieldContainer, TextInput, DateInput, RadioButtonGroup, SelectedItem } from 'asc-web-components'
|
||||||
|
|
||||||
|
const MainContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
@media ${device.tablet} {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AvatarContainer = styled.div`
|
||||||
|
margin: 0 32px 32px 0;
|
||||||
|
width: 160px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MainFieldsContainer = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TextField = React.memo((props) => {
|
||||||
|
const {isRequired, hasError, labelText, inputName, inputValue, isDisabled, onChange} = props;
|
||||||
|
return (
|
||||||
|
<FieldContainer isRequired={isRequired} hasError={hasError} labelText={labelText}>
|
||||||
|
<TextInput name={inputName} value={inputValue} isDisabled={isDisabled} hasError={hasError} onChange={onChange} className="field-input"/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const PasswordField = React.memo((props) => {
|
||||||
|
const {isRequired, hasError, labelText, radioName, radioValue, radioOptions, radioIsDisabled, radioOnChange, inputName, inputValue, inputIsDisabled, inputOnChange} = props;
|
||||||
|
return (
|
||||||
|
<FieldContainer isRequired={isRequired} hasError={hasError} labelText={labelText}>
|
||||||
|
<RadioButtonGroup name={radioName} selected={radioValue} options={radioOptions} isDisabled={radioIsDisabled} onClick={radioOnChange} className="radio-group"/>
|
||||||
|
<TextInput name={inputName} type="password" value={inputValue} isDisabled={inputIsDisabled} hasError={hasError} onChange={inputOnChange} className="field-input"/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const DateField = React.memo((props) => {
|
||||||
|
const {isRequired, hasError, labelText, inputName, inputValue, inputIsDisabled, inputOnChange} = props;
|
||||||
|
return (
|
||||||
|
<FieldContainer isRequired={isRequired} hasError={hasError} labelText={labelText}>
|
||||||
|
<DateInput name={inputName} selected={inputValue} disabled={inputIsDisabled} hasError={hasError} onChange={inputOnChange} className="field-input"/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const RadioField = React.memo((props) => {
|
||||||
|
const {isRequired, hasError, labelText, radioName, radioValue, radioOptions, radioIsDisabled, radioOnChange} = props;
|
||||||
|
return (
|
||||||
|
<FieldContainer isRequired={isRequired} hasError={hasError} labelText={labelText}>
|
||||||
|
<RadioButtonGroup name={radioName} selected={radioValue} options={radioOptions} isDisabled={radioIsDisabled} onClick={radioOnChange} className="radio-group"/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const DepartmentField = React.memo((props) => {
|
||||||
|
const {isRequired, hasError, labelText, departments, onClose} = props;
|
||||||
|
return (
|
||||||
|
departments && departments.length
|
||||||
|
? <FieldContainer isRequired={isRequired} hasError={hasError} labelText={labelText}>
|
||||||
|
{departments.map((department, index) => (
|
||||||
|
<SelectedItem
|
||||||
|
key={`user_group_${department.id}`}
|
||||||
|
text={department.name}
|
||||||
|
onClose={() => { onClose(department.id) }}
|
||||||
|
isInline={true}
|
||||||
|
style={{ marginRight: "8px", marginBottom: "8px" }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FieldContainer>
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { MainContainer, AvatarContainer, MainFieldsContainer, TextField, PasswordField, DateField, RadioField, DepartmentField }
|
@ -1,50 +1,44 @@
|
|||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import { IconButton, Text } from 'asc-web-components';
|
import { IconButton, Text } from 'asc-web-components';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const wrapperStyle = {
|
const Wrapper = styled.div`
|
||||||
display: "flex",
|
display: flex;
|
||||||
alignItems: "center"
|
align-Items: center;
|
||||||
};
|
`;
|
||||||
|
|
||||||
const textStyle = {
|
const Header = styled(Text.ContentHeader)`
|
||||||
marginLeft: "16px"
|
margin-left: 16px;
|
||||||
};
|
`;
|
||||||
|
|
||||||
const SectionHeaderContent = (props) => {
|
const SectionHeaderContent = (props) => {
|
||||||
const {profile, history, userType, settings} = props;
|
const {profile, history, settings} = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const headerText = profile && profile.displayName
|
const headerText = profile && profile.displayName
|
||||||
? profile.displayName
|
? profile.displayName
|
||||||
: userType === "user"
|
: profile.isVisitor
|
||||||
? t('NewEmployee')
|
? t('NewGuest')
|
||||||
: t('NewGuest');
|
: t('NewEmployee');
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
history.push(settings.homepage)
|
||||||
|
}, [history, settings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={wrapperStyle}>
|
<Wrapper>
|
||||||
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={() => history.push(settings.homepage)}/>
|
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={onClick}/>
|
||||||
<Text.ContentHeader style={textStyle}>{headerText}</Text.ContentHeader>
|
<Header>{headerText}</Header>
|
||||||
</div>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
SectionHeaderContent.propTypes = {
|
|
||||||
profile: PropTypes.object,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
userType: PropTypes.oneOf(["user", "guest"])
|
|
||||||
};
|
|
||||||
|
|
||||||
SectionHeaderContent.defaultProps = {
|
|
||||||
profile: null,
|
|
||||||
userType: "user"
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
|
profile: state.profile.targetUser,
|
||||||
settings: state.auth.settings
|
settings: state.auth.settings
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export { default as SectionHeaderContent } from './Header';
|
export { default as SectionHeaderContent } from './Header';
|
||||||
export { default as SectionBodyContent } from './Body';
|
export { default as CreateUserForm } from './Body/createUserForm';
|
||||||
|
export { default as UpdateUserForm } from './Body/updateUserForm';
|
@ -3,24 +3,19 @@ import { connect } from "react-redux";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { PageLayout, Loader } from "asc-web-components";
|
import { PageLayout, Loader } from "asc-web-components";
|
||||||
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
|
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
|
||||||
import { SectionHeaderContent, SectionBodyContent } from './Section';
|
import { SectionHeaderContent, CreateUserForm, UpdateUserForm } from './Section';
|
||||||
import { setProfile, fetchProfile, resetProfile } from '../../../store/profile/actions';
|
import { setProfile, fetchProfile, resetProfile } from '../../../store/profile/actions';
|
||||||
import i18n from "./i18n";
|
import i18n from "./i18n";
|
||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
|
|
||||||
class ProfileAction extends React.Component {
|
class ProfileAction extends React.Component {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { match, setProfile, fetchProfile } = this.props;
|
const { match, setProfile, fetchProfile } = this.props;
|
||||||
const { userId, type } = match.params;
|
const { userId, type } = match.params;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
setProfile({ isVisitor: type === "guest" });
|
setProfile({ isVisitor: type === "guest" });
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
fetchProfile(userId);
|
fetchProfile(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,37 +28,31 @@ class ProfileAction extends React.Component {
|
|||||||
|
|
||||||
if (!userId && type !== prevType) {
|
if (!userId && type !== prevType) {
|
||||||
setProfile({ isVisitor: type === "guest" });
|
setProfile({ isVisitor: type === "guest" });
|
||||||
}
|
} else if (userId !== prevUserId) {
|
||||||
else if (userId !== prevUserId) {
|
|
||||||
fetchProfile(userId);
|
fetchProfile(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.resetProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.log("ProfileAction render")
|
console.log("ProfileAction render")
|
||||||
|
|
||||||
const { profile, match } = this.props;
|
const { profile } = this.props;
|
||||||
const { type } = match.params;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
{profile
|
{profile
|
||||||
? <PageLayout
|
? <PageLayout
|
||||||
articleHeaderContent={<ArticleHeaderContent />}
|
articleHeaderContent={<ArticleHeaderContent />}
|
||||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||||
articleBodyContent={<ArticleBodyContent />}
|
articleBodyContent={<ArticleBodyContent />}
|
||||||
sectionHeaderContent={<SectionHeaderContent profile={profile} userType={type} />}
|
sectionHeaderContent={<SectionHeaderContent />}
|
||||||
sectionBodyContent={<SectionBodyContent profile={profile} userType={type} />}
|
sectionBodyContent={profile.id ? <UpdateUserForm /> : <CreateUserForm />}
|
||||||
/>
|
/>
|
||||||
: <PageLayout
|
: <PageLayout
|
||||||
articleHeaderContent={<ArticleHeaderContent />}
|
articleHeaderContent={<ArticleHeaderContent />}
|
||||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||||
articleBodyContent={<ArticleBodyContent />}
|
articleBodyContent={<ArticleBodyContent />}
|
||||||
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}
|
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||||
/>}
|
/>}
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
);
|
);
|
||||||
@ -73,8 +62,9 @@ class ProfileAction extends React.Component {
|
|||||||
ProfileAction.propTypes = {
|
ProfileAction.propTypes = {
|
||||||
match: PropTypes.object.isRequired,
|
match: PropTypes.object.isRequired,
|
||||||
profile: PropTypes.object,
|
profile: PropTypes.object,
|
||||||
|
setProfile: PropTypes.func.isRequired,
|
||||||
fetchProfile: PropTypes.func.isRequired,
|
fetchProfile: PropTypes.func.isRequired,
|
||||||
setProfile: PropTypes.func.isRequired
|
resetProfile: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
@ -18,5 +18,9 @@
|
|||||||
"SexFemale": "Female",
|
"SexFemale": "Female",
|
||||||
"RequiredField": "Required field",
|
"RequiredField": "Required field",
|
||||||
"NewEmployee": "New employee",
|
"NewEmployee": "New employee",
|
||||||
"NewGuest": "New guest"
|
"NewGuest": "New guest",
|
||||||
|
|
||||||
|
"EmployedSinceDate": "EmployedSinceDate",
|
||||||
|
"Position": "Position",
|
||||||
|
"Departments": "Departments"
|
||||||
}
|
}
|
@ -94,6 +94,16 @@ export function fetchPeople(filter) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchPeopleByFilter(dispatch, filter) {
|
||||||
|
let filterData = (filter && filter.clone()) || Filter.getDefault();
|
||||||
|
return api.getUserList(filterData).then(res => {
|
||||||
|
filterData.total = res.data.total;
|
||||||
|
dispatch(setFilter(filterData));
|
||||||
|
return dispatch(setUsers(res.data.response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchPeopleAsync(dispatch, filter = null) {
|
export async function fetchPeopleAsync(dispatch, filter = null) {
|
||||||
let filterData = (filter && filter.clone()) || Filter.getDefault();
|
let filterData = (filter && filter.clone()) || Filter.getDefault();
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as api from "../../store/services/api";
|
import * as api from "../../store/services/api";
|
||||||
import { isMe } from '../auth/selectors';
|
import { isMe } from '../auth/selectors';
|
||||||
import { getUserByUserName } from '../people/selectors';
|
import { getUserByUserName } from '../people/selectors';
|
||||||
import { fetchPeopleAsync } from "../people/actions";
|
import { fetchPeopleByFilter } from "../people/actions";
|
||||||
|
|
||||||
export const SET_PROFILE = 'SET_PROFILE';
|
export const SET_PROFILE = 'SET_PROFILE';
|
||||||
export const CLEAN_PROFILE = 'CLEAN_PROFILE';
|
export const CLEAN_PROFILE = 'CLEAN_PROFILE';
|
||||||
@ -19,59 +19,119 @@ export function resetProfile() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkResponseError(res) {
|
export function checkResponseError(res) {
|
||||||
if(res && res.data && res.data.error){
|
if (res && res.data && res.data.error) {
|
||||||
console.error(res.data.error);
|
console.error(res.data.error);
|
||||||
throw new Error(res.data.error.message);
|
throw new Error(res.data.error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchProfile(userName) {
|
export function getUserRole(profile) {
|
||||||
return async (dispatch, getState) => {
|
if(profile.isOwner) return "owner";
|
||||||
try {
|
if(profile.isAdmin) return "admin";
|
||||||
const { auth, people } = getState();
|
if(profile.isVisitor) return "guest";
|
||||||
|
return "user";
|
||||||
if (isMe(auth.user, userName)) {
|
|
||||||
dispatch(setProfile(auth.user));
|
|
||||||
} else {
|
|
||||||
const user = getUserByUserName(people.users, userName);
|
|
||||||
if (!user) {
|
|
||||||
const res = await api.getUser(userName);
|
|
||||||
dispatch(setProfile(res.data.response))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
dispatch(setProfile(user));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function profileEqual(profileA, profileB) {
|
||||||
|
const keys = Object.keys(profileA);
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
let key = keys[i];
|
||||||
|
|
||||||
|
if (key === "groups") {
|
||||||
|
if (profileA[key].length !== profileB[key].length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const groupsA = profileA[key].map(group => group.id);
|
||||||
|
const groupsB = profileA[key].map(group => group.id);
|
||||||
|
|
||||||
|
for (let j = 0; j < groupsA.length; j++) {
|
||||||
|
if (!groupsB.includes(groupsA[j]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(profileA[key] !== profileB[key])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toEmployeeWrapper(profile) {
|
||||||
|
const emptyData = {
|
||||||
|
id: "",
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
birthday: "",
|
||||||
|
sex: "male",
|
||||||
|
workFrom: "",
|
||||||
|
location: "",
|
||||||
|
title: "",
|
||||||
|
groups: [],
|
||||||
|
notes: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...emptyData, ...profile };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function employeeWrapperToMemberModel(profile) {
|
||||||
|
const comment = profile.notes;
|
||||||
|
const department = profile.groups ? profile.groups.map(group => group.id) : [];
|
||||||
|
const worksFrom = profile.workFrom;
|
||||||
|
|
||||||
|
return {...profile, comment, department, worksFrom};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchProfile(userName) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { auth, people } = getState();
|
||||||
|
|
||||||
|
if (isMe(auth.user, userName)) {
|
||||||
|
dispatch(setProfile(auth.user));
|
||||||
|
} else {
|
||||||
|
const user = getUserByUserName(people.users, userName);
|
||||||
|
if (!user) {
|
||||||
|
api.getUser(userName).then(res => {
|
||||||
|
checkResponseError(res);
|
||||||
|
dispatch(setProfile(res.data.response));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch(setProfile(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createProfile(profile) {
|
export function createProfile(profile) {
|
||||||
return async (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const {people} = getState();
|
const {people} = getState();
|
||||||
const {filter} = people;
|
const {filter} = people;
|
||||||
|
const member = employeeWrapperToMemberModel(profile);
|
||||||
|
|
||||||
const res = await api.createUser(profile);
|
return api.createUser(member).then(res => {
|
||||||
|
checkResponseError(res);
|
||||||
checkResponseError(res);
|
return Promise.resolve(dispatch(setProfile(res.data.response)));
|
||||||
|
}).then(() => {
|
||||||
dispatch(setProfile(res.data.response))
|
return fetchPeopleByFilter(dispatch, filter);
|
||||||
await fetchPeopleAsync(dispatch, filter);
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function updateProfile(profile) {
|
export function updateProfile(profile) {
|
||||||
return async (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const {people} = getState();
|
const {people} = getState();
|
||||||
const {filter} = people;
|
const {filter} = people;
|
||||||
|
const member = employeeWrapperToMemberModel(profile);
|
||||||
|
|
||||||
const res = await api.updateUser(profile);
|
return api.updateUser(member).then(res => {
|
||||||
|
checkResponseError(res);
|
||||||
checkResponseError(res);
|
return Promise.resolve(dispatch(setProfile(res.data.response)));
|
||||||
|
}).then(() => {
|
||||||
dispatch(setProfile(res.data.response))
|
return fetchPeopleByFilter(dispatch, filter);
|
||||||
await fetchPeopleAsync(dispatch, filter);
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "asc-web-components",
|
"name": "asc-web-components",
|
||||||
"version": "1.0.15",
|
"version": "1.0.16",
|
||||||
"description": "Ascensio System SIA component library",
|
"description": "Ascensio System SIA component library",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "dist/asc-web-components.cjs.js",
|
"main": "dist/asc-web-components.cjs.js",
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import device from '../device'
|
||||||
|
import Label from '../label'
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0;
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
line-height: 32px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
label:not(:first-child) {
|
||||||
|
margin-left: 33px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media ${device.tablet} {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
line-height: unset;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
width: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Body = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FieldContainer = React.memo((props) => {
|
||||||
|
const {isRequired, hasError, labelText, className, children} = props;
|
||||||
|
return (
|
||||||
|
<Container className={className}>
|
||||||
|
<Label isRequired={isRequired} error={hasError} text={labelText} className="field-label"/>
|
||||||
|
<Body>{children}</Body>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FieldContainer
|
@ -50,4 +50,5 @@ export { default as EmptyScreenContainer} from './components/empty-screen-contai
|
|||||||
export { default as CustomScrollbarsVirtualList } from './components/scrollbar/custom-scrollbars-virtual-list'
|
export { default as CustomScrollbarsVirtualList } from './components/scrollbar/custom-scrollbars-virtual-list'
|
||||||
export { default as RowContent } from './components/row-content'
|
export { default as RowContent } from './components/row-content'
|
||||||
export { default as NewCalendar } from './components/calendar-new'
|
export { default as NewCalendar } from './components/calendar-new'
|
||||||
export { default as AdvancedSelector } from './components/advanced-selector'
|
export { default as AdvancedSelector } from './components/advanced-selector'
|
||||||
|
export { default as FieldContainer } from './components/field-container'
|
29
web/ASC.Web.Storybook/stories/field-container/base/README.md
Normal file
29
web/ASC.Web.Storybook/stories/field-container/base/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# FieldContainer
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { FieldContainer } from 'asc-web-components';
|
||||||
|
```
|
||||||
|
|
||||||
|
### <Label>
|
||||||
|
|
||||||
|
Responsive form field container
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
<FieldContainer labelText="Name:">
|
||||||
|
<TextInput/>
|
||||||
|
</FieldContainer>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Properties
|
||||||
|
|
||||||
|
| Props | Type | Required | Values | Default | Description |
|
||||||
|
| ------------| -------- | :------: | -------| ------- | -------------------------------------------- |
|
||||||
|
| `isRequired`| `bool` | - | - | false | Indicates that the field is required to fill |
|
||||||
|
| `hasError` | `bool` | - | - | - | Indicates that the field is incorrect |
|
||||||
|
| `labelText` | `string` | - | - | - | Field label text |
|
@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { StringValue } from 'react-values';
|
||||||
|
import { text, boolean, withKnobs } from '@storybook/addon-knobs/react';
|
||||||
|
import { FieldContainer, TextInput } from 'asc-web-components';
|
||||||
|
import Section from '../../../.storybook/decorators/section';
|
||||||
|
import withReadme from 'storybook-readme/with-readme';
|
||||||
|
import Readme from './README.md';
|
||||||
|
|
||||||
|
storiesOf('Components|FieldContainer', module)
|
||||||
|
.addDecorator(withKnobs)
|
||||||
|
.addDecorator(withReadme(Readme))
|
||||||
|
.add('base', () => (
|
||||||
|
<StringValue
|
||||||
|
onChange={e => {
|
||||||
|
action('onChange')(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ value, set }) => (
|
||||||
|
<Section>
|
||||||
|
<FieldContainer
|
||||||
|
isRequired={boolean('isRequired', false)}
|
||||||
|
hasError={boolean('hasError', false)}
|
||||||
|
labelText={text('labelText', 'Name:')}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
value={value}
|
||||||
|
hasError={boolean('hasError', false)}
|
||||||
|
className="field-input"
|
||||||
|
onChange={e => {
|
||||||
|
set(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FieldContainer>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
</StringValue>
|
||||||
|
));
|
Loading…
Reference in New Issue
Block a user