mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-30 01:05:18 +02:00
Merged in OS-118-add-staff-modal (pull request #90)
OS-118 add staff modal
This commit is contained in:
commit
33fa5996d2
@ -64,7 +64,7 @@ class PeopleList extends React.Component {
|
||||
|
||||
renderAnimatedItem(style, index) {
|
||||
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))}
|
||||
</div>
|
||||
);
|
||||
|
105
client/src/app/admin/panel/staff/add-staff-modal.js
Normal file
105
client/src/app/admin/panel/staff/add-staff-modal.js
Normal 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;
|
23
client/src/app/admin/panel/staff/add-staff-modal.scss
Normal file
23
client/src/app/admin/panel/staff/add-staff-modal.scss
Normal 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;
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ import i18n from 'lib-app/i18n';
|
||||
import API from 'lib-app/api-call';
|
||||
import SessionStore from 'lib-app/session-store';
|
||||
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 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')} />
|
||||
<div className="admin-panel-staff-members__wrapper">
|
||||
<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')}
|
||||
</Button>
|
||||
</div>
|
||||
@ -43,6 +46,10 @@ class AdminPanelStaffMembers extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
onAddNewStaff() {
|
||||
ModalContainer.openModal(<AddStaffModal />);
|
||||
}
|
||||
|
||||
getDepartmentDropdownProps() {
|
||||
return {
|
||||
items: this.getDepartments(),
|
||||
|
57
client/src/core-components/checkbox-group.js
Normal file
57
client/src/core-components/checkbox-group.js
Normal 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;
|
11
client/src/core-components/checkbox-group.scss
Normal file
11
client/src/core-components/checkbox-group.scss
Normal file
@ -0,0 +1,11 @@
|
||||
@import "../scss/vars";
|
||||
|
||||
.checkbox-group {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&__item {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
@ -11,28 +11,38 @@ class CheckBox extends React.Component {
|
||||
static propTypes = {
|
||||
alignment: 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 = {
|
||||
wrapInLabel: false,
|
||||
alignment: 'right'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
checked: false
|
||||
};
|
||||
}
|
||||
state = {
|
||||
checked: false
|
||||
};
|
||||
|
||||
render() {
|
||||
let Wrapper = (this.props.wrapInLabel) ? 'label' : 'span';
|
||||
|
||||
return (
|
||||
<span className={this.getClass()}>
|
||||
<Wrapper className={this.getClass()}>
|
||||
<span {...this.getIconProps()}>
|
||||
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
|
||||
</span>
|
||||
<input {...this.getProps()}/>
|
||||
{(this.props.label) ? this.renderLabel() : null}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
renderLabel() {
|
||||
return (
|
||||
<span className="checkbox__label">
|
||||
{this.props.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -43,7 +53,7 @@ class CheckBox extends React.Component {
|
||||
props.type = 'checkbox';
|
||||
|
||||
props['aria-hidden'] = true;
|
||||
props.className = 'checkbox--box';
|
||||
props.className = 'checkbox__box';
|
||||
props.checked = this.getValue();
|
||||
props.onChange = callback(this.handleChange.bind(this), this.props.onChange);
|
||||
|
||||
@ -69,7 +79,7 @@ class CheckBox extends React.Component {
|
||||
getIconProps() {
|
||||
return {
|
||||
'aria-checked': this.getValue(),
|
||||
className: 'checkbox--icon',
|
||||
className: 'checkbox__icon',
|
||||
onKeyDown: callback(this.handleIconKeyDown.bind(this), this.props.onKeyDown),
|
||||
role: "checkbox",
|
||||
tabIndex: 0
|
||||
|
@ -5,11 +5,11 @@
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
|
||||
&--box {
|
||||
&__box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
&__icon {
|
||||
color: $light-grey;
|
||||
outline: none;
|
||||
|
||||
@ -18,8 +18,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&_checked {
|
||||
.checkbox--icon {
|
||||
.checkbox__icon {
|
||||
color: $primary-red;
|
||||
|
||||
&:focus {
|
||||
|
@ -49,4 +49,17 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import _ from 'lodash';
|
||||
import Input from 'core-components/input';
|
||||
import DropDown from 'core-components/drop-down';
|
||||
import Checkbox from 'core-components/checkbox';
|
||||
import CheckboxGroup from 'core-components/checkbox-group';
|
||||
import TextEditor from 'core-components/text-editor';
|
||||
|
||||
class FormField extends React.Component {
|
||||
@ -21,7 +22,7 @@ class FormField extends React.Component {
|
||||
required: React.PropTypes.bool,
|
||||
error: React.PropTypes.string,
|
||||
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
|
||||
};
|
||||
|
||||
@ -36,6 +37,9 @@ class FormField extends React.Component {
|
||||
else if (field === 'checkbox') {
|
||||
return false;
|
||||
}
|
||||
else if (field === 'checkbox-group') {
|
||||
return [];
|
||||
}
|
||||
else if (field === 'textarea') {
|
||||
return RichTextEditor.createEmptyValue();
|
||||
}
|
||||
@ -45,7 +49,7 @@ class FormField extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const Wrapper = (this.props.field === 'textarea') ? 'div' : 'label';
|
||||
const Wrapper = (_.includes(this.getDivTypes(), this.props.field)) ? 'div' : 'label';
|
||||
const fieldContent = [
|
||||
<span className="form-field__label" key="label">{this.props.label}</span>,
|
||||
this.renderField(),
|
||||
@ -67,7 +71,8 @@ class FormField extends React.Component {
|
||||
'input': Input,
|
||||
'textarea': TextEditor,
|
||||
'select': DropDown,
|
||||
'checkbox': Checkbox
|
||||
'checkbox': Checkbox,
|
||||
'checkbox-group': CheckboxGroup
|
||||
}[this.props.field];
|
||||
|
||||
if(this.props.decorator) {
|
||||
@ -122,6 +127,13 @@ class FormField extends React.Component {
|
||||
return props;
|
||||
}
|
||||
|
||||
getDivTypes() {
|
||||
return [
|
||||
'textarea',
|
||||
'checkbox-group'
|
||||
];
|
||||
}
|
||||
|
||||
onChange(nativeEvent) {
|
||||
let event = nativeEvent;
|
||||
|
||||
|
@ -629,6 +629,17 @@ module.exports = [
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
path: '/staff/add',
|
||||
time: 100,
|
||||
response: function () {
|
||||
return {
|
||||
status: 'success',
|
||||
data: {
|
||||
staffId: 5
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
@ -97,8 +97,12 @@ export default {
|
||||
'ASSIGNED_TICKETS': '{tickets} assigned tickets',
|
||||
'CLOSED_TICKETS': '{tickets} closed tickets',
|
||||
'LAST_LOGIN': 'Last login',
|
||||
'STAFF_MEMBERS': 'Staff members',
|
||||
'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
|
||||
'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.',
|
||||
'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.',
|
||||
'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams',
|
||||
|
||||
//ERRORS
|
||||
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
||||
|
@ -25,4 +25,37 @@ $font-size--sm: 13px;
|
||||
$font-size--md: 16px;
|
||||
$font-size--bg: 19px;
|
||||
$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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user