This commit is contained in:
Nikita Gopienko 2019-08-29 10:06:07 +03:00
commit 92a5d6d483
17 changed files with 826 additions and 526 deletions

View File

@ -30,10 +30,6 @@ class Profile extends React.Component {
}
}
componentWillUnmount() {
this.props.resetProfile();
}
render() {
console.log("Profile render")

View File

@ -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)

View File

@ -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)));

View File

@ -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;

View File

@ -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)));

View File

@ -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 }

View File

@ -1,50 +1,44 @@
import React from 'react';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import { connect } from 'react-redux';
import { withRouter } from "react-router";
import PropTypes from "prop-types";
import { IconButton, Text } from 'asc-web-components';
import { useTranslation } from 'react-i18next';
const wrapperStyle = {
display: "flex",
alignItems: "center"
};
const Wrapper = styled.div`
display: flex;
align-Items: center;
`;
const textStyle = {
marginLeft: "16px"
};
const Header = styled(Text.ContentHeader)`
margin-left: 16px;
`;
const SectionHeaderContent = (props) => {
const {profile, history, userType, settings} = props;
const {profile, history, settings} = props;
const { t } = useTranslation();
const headerText = profile && profile.displayName
? profile.displayName
: userType === "user"
? t('NewEmployee')
: t('NewGuest');
: profile.isVisitor
? t('NewGuest')
: t('NewEmployee');
const onClick = useCallback(() => {
history.push(settings.homepage)
}, [history, settings]);
return (
<div style={wrapperStyle}>
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={() => history.push(settings.homepage)}/>
<Text.ContentHeader style={textStyle}>{headerText}</Text.ContentHeader>
</div>
<Wrapper>
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={onClick}/>
<Header>{headerText}</Header>
</Wrapper>
);
};
SectionHeaderContent.propTypes = {
profile: PropTypes.object,
history: PropTypes.object.isRequired,
userType: PropTypes.oneOf(["user", "guest"])
};
SectionHeaderContent.defaultProps = {
profile: null,
userType: "user"
};
function mapStateToProps(state) {
return {
profile: state.profile.targetUser,
settings: state.auth.settings
};
};

View File

@ -1,2 +1,3 @@
export { default as SectionHeaderContent } from './Header';
export { default as SectionBodyContent } from './Body';
export { default as SectionHeaderContent } from './Header';
export { default as CreateUserForm } from './Body/createUserForm';
export { default as UpdateUserForm } from './Body/updateUserForm';

View File

@ -3,24 +3,19 @@ import { connect } from "react-redux";
import PropTypes from "prop-types";
import { PageLayout, Loader } from "asc-web-components";
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 i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
class ProfileAction extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { match, setProfile, fetchProfile } = this.props;
const { userId, type } = match.params;
if (!userId) {
setProfile({ isVisitor: type === "guest" });
}
else {
} else {
fetchProfile(userId);
}
}
@ -33,37 +28,31 @@ class ProfileAction extends React.Component {
if (!userId && type !== prevType) {
setProfile({ isVisitor: type === "guest" });
}
else if (userId !== prevUserId) {
} else if (userId !== prevUserId) {
fetchProfile(userId);
}
}
componentWillUnmount() {
this.props.resetProfile();
}
render() {
console.log("ProfileAction render")
const { profile, match } = this.props;
const { type } = match.params;
const { profile } = this.props;
return (
<I18nextProvider i18n={i18n}>
{profile
? <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent profile={profile} userType={type} />}
sectionBodyContent={<SectionBodyContent profile={profile} userType={type} />}
/>
: <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}
? <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent />}
sectionBodyContent={profile.id ? <UpdateUserForm /> : <CreateUserForm />}
/>
: <PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}
/>}
</I18nextProvider>
);
@ -73,8 +62,9 @@ class ProfileAction extends React.Component {
ProfileAction.propTypes = {
match: PropTypes.object.isRequired,
profile: PropTypes.object,
setProfile: PropTypes.func.isRequired,
fetchProfile: PropTypes.func.isRequired,
setProfile: PropTypes.func.isRequired
resetProfile: PropTypes.func.isRequired
};
function mapStateToProps(state) {

View File

@ -18,5 +18,9 @@
"SexFemale": "Female",
"RequiredField": "Required field",
"NewEmployee": "New employee",
"NewGuest": "New guest"
"NewGuest": "New guest",
"EmployedSinceDate": "EmployedSinceDate",
"Position": "Position",
"Departments": "Departments"
}

View File

@ -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) {
let filterData = (filter && filter.clone()) || Filter.getDefault();

View File

@ -1,7 +1,7 @@
import * as api from "../../store/services/api";
import { isMe } from '../auth/selectors';
import { getUserByUserName } from '../people/selectors';
import { fetchPeopleAsync } from "../people/actions";
import { fetchPeopleByFilter } from "../people/actions";
export const SET_PROFILE = 'SET_PROFILE';
export const CLEAN_PROFILE = 'CLEAN_PROFILE';
@ -19,59 +19,119 @@ export function resetProfile() {
};
};
function checkResponseError(res) {
if(res && res.data && res.data.error){
export function checkResponseError(res) {
if (res && res.data && res.data.error) {
console.error(res.data.error);
throw new Error(res.data.error.message);
}
}
export function fetchProfile(userName) {
return async (dispatch, getState) => {
try {
const { auth, people } = getState();
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 getUserRole(profile) {
if(profile.isOwner) return "owner";
if(profile.isAdmin) return "admin";
if(profile.isVisitor) return "guest";
return "user";
};
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) {
return async (dispatch, getState) => {
return (dispatch, getState) => {
const {people} = getState();
const {filter} = people;
const member = employeeWrapperToMemberModel(profile);
const res = await api.createUser(profile);
checkResponseError(res);
dispatch(setProfile(res.data.response))
await fetchPeopleAsync(dispatch, filter);
return api.createUser(member).then(res => {
checkResponseError(res);
return Promise.resolve(dispatch(setProfile(res.data.response)));
}).then(() => {
return fetchPeopleByFilter(dispatch, filter);
});
};
};
export function updateProfile(profile) {
return async (dispatch, getState) => {
return (dispatch, getState) => {
const {people} = getState();
const {filter} = people;
const member = employeeWrapperToMemberModel(profile);
const res = await api.updateUser(profile);
checkResponseError(res);
dispatch(setProfile(res.data.response))
await fetchPeopleAsync(dispatch, filter);
return api.updateUser(member).then(res => {
checkResponseError(res);
return Promise.resolve(dispatch(setProfile(res.data.response)));
}).then(() => {
return fetchPeopleByFilter(dispatch, filter);
});
};
};

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-components",
"version": "1.0.15",
"version": "1.0.16",
"description": "Ascensio System SIA component library",
"license": "AGPL-3.0",
"main": "dist/asc-web-components.cjs.js",

View File

@ -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

View File

@ -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 RowContent } from './components/row-content'
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'

View 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 |

View File

@ -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>
));