Merged in OS-118-add-staff-modal (pull request #90)

OS-118 add staff modal
This commit is contained in:
Ivan Diaz 2016-12-10 16:37:02 -03:00
commit 33fa5996d2
13 changed files with 313 additions and 22 deletions

View File

@ -64,7 +64,7 @@ class PeopleList extends React.Component {
renderAnimatedItem(style, index) { renderAnimatedItem(style, index) {
return ( return (
<div style={{transform: 'translateX('+style.offset+'px)', opacity: style.alpha}}> <div style={{transform: 'translateX('+style.offset+'px)', opacity: style.alpha}} key={index}>
{this.renderItem(index + this.props.pageSize * (this.props.page - 1))} {this.renderItem(index + this.props.pageSize * (this.props.page - 1))}
</div> </div>
); );

View File

@ -0,0 +1,105 @@
import React from 'react';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionStore from 'lib-app/session-store';
import Header from 'core-components/header'
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
class AddStaffModal extends React.Component {
static contextTypes = {
closeModal: React.PropTypes.func
};
state = {
loading: false,
errors: {},
error: null
};
render() {
return (
<div className="add-staff-modal">
<Header title={i18n('ADD_STAFF')} description={i18n('ADD_STAFF_DESCRIPTION')} />
<Form onSubmit={this.onSubmit.bind(this)} errors={this.getErrors()} onValidateErrors={errors => this.setState({errors})} loading={this.state.loading}>
<div className="row">
<div className="col-md-7">
<FormField name="name" label={i18n('NAME')} fieldProps={{size: 'large'}} validation="NAME" required />
<FormField name="email" label={i18n('EMAIL')} fieldProps={{size: 'large'}} validation="EMAIL" required />
<FormField name="password" label={i18n('PASSWORD')} fieldProps={{size: 'large', password: true}} validation="PASSWORD" required />
<div className="add-staff-modal__level-selector">
<FormField name="level" label={i18n('LEVEL')} field="select" fieldProps={{
items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}],
size: 'large'
}} />
</div>
</div>
<div className="col-md-5">
<div className="add-staff-modal__departments">
<div className="add-staff-modal__departments-title">{i18n('Departments')}</div>
<FormField name="departments" field="checkbox-group" fieldProps={{items: this.getDepartments()}} />
</div>
</div>
</div>
<SubmitButton type="secondary" size="small">
{i18n('SAVE')}
</SubmitButton>
<Button type="clean" onClick={this.onCancelClick.bind(this)}>
{i18n('CANCEL')}
</Button>
</Form>
</div>
);
}
getDepartments() {
return SessionStore.getDepartments().map(department => department.name);
}
onSubmit(form) {
let departments = _.filter(SessionStore.getDepartments(), (department, index) => {
return _.includes(form.departments, index);
}).map(department => department.id);
this.setState({loading: true});
API.call({
path: '/staff/add',
data: {
name: form.name,
email: form.email,
password: form.password,
level: form.level + 1,
department: JSON.stringify(departments)
}
}).then(this.context.closeModal).catch((result) => {
this.setState({
loading: false,
error: result.message
});
});
}
onCancelClick(event) {
event.preventDefault();
this.context.closeModal();
}
getErrors() {
let errors = _.extend({}, this.state.errors);
if (this.state.error === 'ALREADY_A_STAFF') {
errors.email = i18n('EMAIL_EXISTS');
}
return errors;
}
}
export default AddStaffModal;

View File

@ -0,0 +1,23 @@
@import "../../../../scss/vars";
.add-staff-modal {
width: 700px;
&__level-selector {
text-align: center;
}
&__departments {
@include scrollbars();
border: 1px solid $grey;
padding: 20px;
height: 320px;
overflow-y: auto;
}
&__departments-title {
font-size: $font-size--md;
text-align: center;
}
}

View File

@ -5,6 +5,9 @@ import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
import SessionStore from 'lib-app/session-store'; import SessionStore from 'lib-app/session-store';
import PeopleList from 'app-components/people-list'; import PeopleList from 'app-components/people-list';
import ModalContainer from 'app-components/modal-container';
import AddStaffModal from 'app/admin/panel/staff/add-staff-modal';
import Header from 'core-components/header'; import Header from 'core-components/header';
import DropDown from 'core-components/drop-down'; import DropDown from 'core-components/drop-down';
@ -34,7 +37,7 @@ class AdminPanelStaffMembers extends React.Component {
<Header title={i18n('STAFF_MEMBERS')} description={i18n('STAFF_MEMBERS_DESCRIPTION')} /> <Header title={i18n('STAFF_MEMBERS')} description={i18n('STAFF_MEMBERS_DESCRIPTION')} />
<div className="admin-panel-staff-members__wrapper"> <div className="admin-panel-staff-members__wrapper">
<DropDown {...this.getDepartmentDropdownProps()} className="admin-panel-staff-members__dropdown" /> <DropDown {...this.getDepartmentDropdownProps()} className="admin-panel-staff-members__dropdown" />
<Button size="medium" onClick={() => {}} type="secondary" className="admin-panel-staff-members__button"> <Button onClick={this.onAddNewStaff.bind(this)} size="medium" type="secondary" className="admin-panel-staff-members__button">
<Icon name="user-plus" className=""/> {i18n('ADD_NEW_STAFF')} <Icon name="user-plus" className=""/> {i18n('ADD_NEW_STAFF')}
</Button> </Button>
</div> </div>
@ -43,6 +46,10 @@ class AdminPanelStaffMembers extends React.Component {
); );
} }
onAddNewStaff() {
ModalContainer.openModal(<AddStaffModal />);
}
getDepartmentDropdownProps() { getDepartmentDropdownProps() {
return { return {
items: this.getDepartments(), items: this.getDepartments(),

View File

@ -0,0 +1,57 @@
import React from 'react';
import _ from 'lodash';
import Checkbox from 'core-components/checkbox';
class CheckboxGroup extends React.Component {
static propTypes = {
items: React.PropTypes.array.isRequired,
value: React.PropTypes.arrayOf(React.PropTypes.number),
onChange: React.PropTypes.func
};
state = {
value: []
};
render() {
return (
<ul className="checkbox-group">
{this.props.items.map(this.renderItem.bind(this))}
</ul>
);
}
renderItem(label, index) {
const checked = (_.includes(this.getValue(), index));
return (
<li className="checkbox-group__item" key={index}>
<Checkbox label={label} checked={checked} onChange={this.onCheckboxChange.bind(this, index)} wrapInLabel/>
</li>
);
}
onCheckboxChange(index) {
let value = _.clone(this.getValue());
if(_.includes(value, index)) {
_.pull(value, index);
} else {
value.push(index);
value.sort();
}
this.setState({value});
if(this.props.onChange) {
this.props.onChange({target: {value}});
}
}
getValue() {
return (this.props.value !== undefined) ? this.props.value : this.state.value;
}
}
export default CheckboxGroup;

View File

@ -0,0 +1,11 @@
@import "../scss/vars";
.checkbox-group {
list-style-type: none;
margin: 0;
padding: 0;
&__item {
margin: 10px 0;
}
}

View File

@ -11,28 +11,38 @@ class CheckBox extends React.Component {
static propTypes = { static propTypes = {
alignment: React.PropTypes.string, alignment: React.PropTypes.string,
label: React.PropTypes.string, label: React.PropTypes.string,
value: React.PropTypes.bool value: React.PropTypes.bool,
wrapInLabel: React.PropTypes.bool,
onChange: React.PropTypes.func
}; };
static defaultProps = { static defaultProps = {
wrapInLabel: false,
alignment: 'right' alignment: 'right'
}; };
constructor(props) { state = {
super(props); checked: false
};
this.state = {
checked: false
};
}
render() { render() {
let Wrapper = (this.props.wrapInLabel) ? 'label' : 'span';
return ( return (
<span className={this.getClass()}> <Wrapper className={this.getClass()}>
<span {...this.getIconProps()}> <span {...this.getIconProps()}>
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') } {getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
</span> </span>
<input {...this.getProps()}/> <input {...this.getProps()}/>
{(this.props.label) ? this.renderLabel() : null}
</Wrapper>
);
}
renderLabel() {
return (
<span className="checkbox__label">
{this.props.label}
</span> </span>
); );
} }
@ -43,7 +53,7 @@ class CheckBox extends React.Component {
props.type = 'checkbox'; props.type = 'checkbox';
props['aria-hidden'] = true; props['aria-hidden'] = true;
props.className = 'checkbox--box'; props.className = 'checkbox__box';
props.checked = this.getValue(); props.checked = this.getValue();
props.onChange = callback(this.handleChange.bind(this), this.props.onChange); props.onChange = callback(this.handleChange.bind(this), this.props.onChange);
@ -69,7 +79,7 @@ class CheckBox extends React.Component {
getIconProps() { getIconProps() {
return { return {
'aria-checked': this.getValue(), 'aria-checked': this.getValue(),
className: 'checkbox--icon', className: 'checkbox__icon',
onKeyDown: callback(this.handleIconKeyDown.bind(this), this.props.onKeyDown), onKeyDown: callback(this.handleIconKeyDown.bind(this), this.props.onKeyDown),
role: "checkbox", role: "checkbox",
tabIndex: 0 tabIndex: 0

View File

@ -5,11 +5,11 @@
border-radius: 5px; border-radius: 5px;
display: inline-block; display: inline-block;
&--box { &__box {
display: none; display: none;
} }
&--icon { &__icon {
color: $light-grey; color: $light-grey;
outline: none; outline: none;
@ -18,8 +18,12 @@
} }
} }
&__label {
margin-left: 10px;
}
&_checked { &_checked {
.checkbox--icon { .checkbox__icon {
color: $primary-red; color: $primary-red;
&:focus { &:focus {

View File

@ -49,4 +49,17 @@
border: 1px solid $light-grey; border: 1px solid $light-grey;
} }
} }
&_large {
width: 300px;
.drop-down__current-item {
border-radius: 4px;
}
.drop-down__list-container {
width: 300px;
border: 1px solid $light-grey;
}
}
} }

View File

@ -6,6 +6,7 @@ import _ from 'lodash';
import Input from 'core-components/input'; import Input from 'core-components/input';
import DropDown from 'core-components/drop-down'; import DropDown from 'core-components/drop-down';
import Checkbox from 'core-components/checkbox'; import Checkbox from 'core-components/checkbox';
import CheckboxGroup from 'core-components/checkbox-group';
import TextEditor from 'core-components/text-editor'; import TextEditor from 'core-components/text-editor';
class FormField extends React.Component { class FormField extends React.Component {
@ -21,7 +22,7 @@ class FormField extends React.Component {
required: React.PropTypes.bool, required: React.PropTypes.bool,
error: React.PropTypes.string, error: React.PropTypes.string,
value: React.PropTypes.any, value: React.PropTypes.any,
field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox']), field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox', 'checkbox-group']),
fieldProps: React.PropTypes.object fieldProps: React.PropTypes.object
}; };
@ -36,6 +37,9 @@ class FormField extends React.Component {
else if (field === 'checkbox') { else if (field === 'checkbox') {
return false; return false;
} }
else if (field === 'checkbox-group') {
return [];
}
else if (field === 'textarea') { else if (field === 'textarea') {
return RichTextEditor.createEmptyValue(); return RichTextEditor.createEmptyValue();
} }
@ -45,7 +49,7 @@ class FormField extends React.Component {
} }
render() { render() {
const Wrapper = (this.props.field === 'textarea') ? 'div' : 'label'; const Wrapper = (_.includes(this.getDivTypes(), this.props.field)) ? 'div' : 'label';
const fieldContent = [ const fieldContent = [
<span className="form-field__label" key="label">{this.props.label}</span>, <span className="form-field__label" key="label">{this.props.label}</span>,
this.renderField(), this.renderField(),
@ -67,7 +71,8 @@ class FormField extends React.Component {
'input': Input, 'input': Input,
'textarea': TextEditor, 'textarea': TextEditor,
'select': DropDown, 'select': DropDown,
'checkbox': Checkbox 'checkbox': Checkbox,
'checkbox-group': CheckboxGroup
}[this.props.field]; }[this.props.field];
if(this.props.decorator) { if(this.props.decorator) {
@ -122,6 +127,13 @@ class FormField extends React.Component {
return props; return props;
} }
getDivTypes() {
return [
'textarea',
'checkbox-group'
];
}
onChange(nativeEvent) { onChange(nativeEvent) {
let event = nativeEvent; let event = nativeEvent;

View File

@ -629,6 +629,17 @@ module.exports = [
] ]
}; };
} }
},
{
path: '/staff/add',
time: 100,
response: function () {
return {
status: 'success',
data: {
staffId: 5
}
};
}
} }
]; ];

View File

@ -97,8 +97,12 @@ export default {
'ASSIGNED_TICKETS': '{tickets} assigned tickets', 'ASSIGNED_TICKETS': '{tickets} assigned tickets',
'CLOSED_TICKETS': '{tickets} closed tickets', 'CLOSED_TICKETS': '{tickets} closed tickets',
'LAST_LOGIN': 'Last login', 'LAST_LOGIN': 'Last login',
'STAFF_MEMBERS': 'Staff members',
'ADD_NEW_STAFF': 'Add new staff', 'ADD_NEW_STAFF': 'Add new staff',
'ADD_STAFF': 'Add staff',
'LEVEL': 'Level',
'LEVEL_1': 'Level 1 (Tickets)',
'LEVEL_2': 'Level 2 (Tickets + Articles)',
'LEVEL_3': 'Level 2 (Tickets + Articles + Staff)',
//VIEW DESCRIPTIONS //VIEW DESCRIPTIONS
'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.', 'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.',
@ -122,6 +126,7 @@ export default {
'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.', 'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.',
'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.', 'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.',
'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.', 'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.',
'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams',
//ERRORS //ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid', 'EMAIL_OR_PASSWORD': 'Email or password invalid',

View File

@ -26,3 +26,36 @@ $font-size--md: 16px;
$font-size--bg: 19px; $font-size--bg: 19px;
$font-size--lg: 24px; $font-size--lg: 24px;
$font-size--xl: 32px; $font-size--xl: 32px;
@mixin scrollbars() {
$size: .4em;
$color: $grey;
&::-webkit-scrollbar {
width: $size;
height: $size;
}
&::-webkit-scrollbar-thumb {
background: transparent;
}
&::-webkit-scrollbar-track {
backgroundr: transparent;
}
&:hover {
&::-webkit-scrollbar {
width: $size;
height: $size;
}
&::-webkit-scrollbar-thumb {
background: $color;
}
&::-webkit-scrollbar-track {
background: lighten($color, 50%);
}
}
}