diff --git a/client/src/app-components/people-list.js b/client/src/app-components/people-list.js
new file mode 100644
index 00000000..604d8967
--- /dev/null
+++ b/client/src/app-components/people-list.js
@@ -0,0 +1,117 @@
+import React from 'react';
+import _ from 'lodash';
+import {StaggeredMotion, spring} from 'react-motion';
+import Menu from 'core-components/menu'
+
+import DateTransformer from 'lib-core/date-transformer';
+import i18n from 'lib-app/i18n';
+
+class PeopleList extends React.Component {
+ static propTypes = {
+ list: React.PropTypes.arrayOf(React.PropTypes.shape({
+ profilePic: React.PropTypes.string,
+ name: React.PropTypes.string,
+ assignedTickets: React.PropTypes.number,
+ closedTickets: React.PropTypes.number,
+ lastLogin: React.PropTypes.number
+ })),
+ pageSize: React.PropTypes.number,
+ page: React.PropTypes.number,
+ onPageSelect: React.PropTypes.func
+ };
+
+ static defaultProps = {
+ pageSize: 4,
+ list: []
+ };
+
+ render() {
+ const pages = _.range(1, this.getPages() + 1).map((index) => {return {content: index};});
+
+ return (
+
+
+
+ {this.renderList.bind(this)}
+
+
+
+
+
+
+ );
+ }
+
+ getDefaultStyles() {
+ return _.times(this.props.pageSize).map(() => {return {offset: -100, alpha: 0}});
+ }
+
+ getStyles(prevStyles) {
+ return prevStyles.map((_, i) => {
+ return i === 0
+ ? {offset: spring(0), alpha: spring(1)}
+ : {offset: spring(prevStyles[i - 1].offset), alpha: spring(prevStyles[i - 1].alpha)}
+ });
+ }
+
+ renderList(styles) {
+ return (
+
+ {styles.map(this.renderAnimatedItem.bind(this))}
+
+ );
+ }
+
+ renderAnimatedItem(style, index) {
+ return (
+
+ {this.renderItem(index + this.props.pageSize * (this.props.page - 1))}
+
+ );
+ }
+
+ renderItem(index) {
+ if(index >= this.props.list.length) {
+ return null;
+ }
+
+ const item = this.props.list[index];
+ const minIndex = this.props.pageSize * (this.props.page - 1);
+ const maxIndex = this.props.pageSize * this.props.page;
+
+ return (minIndex <= index && index < maxIndex) ? (
+
+
+
![]({item.profilePic})
+
+
{item.name}
+
+ {i18n('ASSIGNED_TICKETS', {tickets: item.assignedTickets})}
+
+
+ {i18n('CLOSED_TICKETS', {tickets: item.closedTickets})}
+
+
+
{i18n('LAST_LOGIN')}
+
{DateTransformer.transformToString(item.lastLogin)}
+
+
+ ) : null;
+ }
+
+ getRowsQuantity() {
+ if(this.props.page == this.getPages()){
+ return this.props.list.length % this.props.pageSize;
+ }
+ else {
+ return this.props.pageSize;
+ }
+ }
+
+ getPages() {
+ return Math.ceil(this.props.list.length / this.props.pageSize);
+ }
+
+}
+
+export default PeopleList;
\ No newline at end of file
diff --git a/client/src/app-components/people-list.scss b/client/src/app-components/people-list.scss
new file mode 100644
index 00000000..8c52b03d
--- /dev/null
+++ b/client/src/app-components/people-list.scss
@@ -0,0 +1,64 @@
+@import "../scss/vars";
+
+.people-list {
+ max-width: 800px;
+ margin: 0 auto;
+
+ &__list {
+ }
+
+ &__item {
+ border: 2px solid $grey;
+ border-radius: 4px;
+ margin-bottom: 12px;
+ position: relative;
+ height: 105px;
+ padding-left: 60px;
+ font-size: $font-size--md;
+
+ &-profile-pic-wrapper {
+ vertical-align: top;
+ background-color: $secondary-blue;
+ color: white;
+ border-radius: 5px;
+ width: 60px;
+ height: 60px;
+ top: 20px;
+ left: 20px;
+
+ overflow: hidden;
+ position: absolute;
+ border: 2px solid $light-grey;
+ }
+
+ &-profile-pic {
+ position: absolute;
+ height: 100%;
+ left: 50%;
+ transform: translate(-50%, 0)
+ }
+
+ &-block {
+ padding: 30px 0;
+ width: 25%;
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ &-assigned-tickets {
+
+ }
+
+ &-closed-tickets {
+
+ }
+
+ &-last-login {
+
+ }
+ }
+
+ &__pagination {
+
+ }
+}
\ No newline at end of file
diff --git a/client/src/app/admin/panel/staff/add-staff-modal.js b/client/src/app/admin/panel/staff/add-staff-modal.js
new file mode 100644
index 00000000..defd85bd
--- /dev/null
+++ b/client/src/app/admin/panel/staff/add-staff-modal.js
@@ -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 (
+
+ );
+ }
+
+ 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;
\ No newline at end of file
diff --git a/client/src/app/admin/panel/staff/add-staff-modal.scss b/client/src/app/admin/panel/staff/add-staff-modal.scss
new file mode 100644
index 00000000..a2dd0d64
--- /dev/null
+++ b/client/src/app/admin/panel/staff/add-staff-modal.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/client/src/app/admin/panel/staff/admin-panel-staff-members.js b/client/src/app/admin/panel/staff/admin-panel-staff-members.js
index f869fc8b..5d85a0ea 100644
--- a/client/src/app/admin/panel/staff/admin-panel-staff-members.js
+++ b/client/src/app/admin/panel/staff/admin-panel-staff-members.js
@@ -1,14 +1,99 @@
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 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';
+import Button from 'core-components/button';
+import Icon from 'core-components/icon';
+import Loading from 'core-components/loading';
class AdminPanelStaffMembers extends React.Component {
+ state = {
+ selectedDepartment: 0,
+ staffList: [],
+ loading: true,
+ page: 1
+ };
+
+ componentDidMount() {
+ API.call({
+ path: '/staff/get-all',
+ data: {}
+ }).then(this.onStaffRetrieved.bind(this));
+ }
+
render() {
return (
-
- /admin/panel/staff/staff-members
+
+
+
+
+
+
+ {(this.state.loading) ?
:
this.setState({page: index+1})} />}
);
}
+
+ onAddNewStaff() {
+ ModalContainer.openModal(
);
+ }
+
+ getDepartmentDropdownProps() {
+ return {
+ items: this.getDepartments(),
+ onChange: (event) => {
+ let departments = SessionStore.getDepartments();
+ this.setState({
+ selectedDepartment: event.index && departments[event.index - 1].id,
+ page: 1
+ });
+ },
+ size: 'medium'
+ };
+ }
+
+ getStaffList() {
+ if(!this.state.selectedDepartment) {
+ return this.state.staffList;
+ }
+
+ return _.filter(this.state.staffList, (o) => {
+ return _.findIndex(o.departments, {id: this.state.selectedDepartment}) !== -1;
+ });
+ }
+
+ getDepartments() {
+ let departments = SessionStore.getDepartments().map((department) => {
+ return {content: department.name};
+ });
+
+ departments.unshift({
+ content: i18n('ALL_DEPARTMENTS')
+ });
+
+ return departments;
+ }
+
+ onStaffRetrieved(result) {
+ if(result.status == 'success'){
+ this.setState({
+ loading: false,
+ staffList: result.data
+ });
+ }
+ }
}
export default AdminPanelStaffMembers;
\ No newline at end of file
diff --git a/client/src/app/admin/panel/staff/admin-panel-staff-members.scss b/client/src/app/admin/panel/staff/admin-panel-staff-members.scss
new file mode 100644
index 00000000..d31be626
--- /dev/null
+++ b/client/src/app/admin/panel/staff/admin-panel-staff-members.scss
@@ -0,0 +1,17 @@
+@import "../../../../scss/vars";
+
+.admin-panel-staff-members {
+
+ &__wrapper {
+ height: 60px;
+ }
+
+ &__dropdown {
+ float: left;
+ }
+
+ &__button {
+ float: right;
+ margin-top: -5px;
+ }
+}
\ No newline at end of file
diff --git a/client/src/core-components/checkbox-group.js b/client/src/core-components/checkbox-group.js
new file mode 100644
index 00000000..a5e97e76
--- /dev/null
+++ b/client/src/core-components/checkbox-group.js
@@ -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 (
+
+ {this.props.items.map(this.renderItem.bind(this))}
+
+ );
+ }
+
+ renderItem(label, index) {
+ const checked = (_.includes(this.getValue(), index));
+
+ return (
+
+
+
+ );
+ }
+
+ 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;
\ No newline at end of file
diff --git a/client/src/core-components/checkbox-group.scss b/client/src/core-components/checkbox-group.scss
new file mode 100644
index 00000000..92612c5c
--- /dev/null
+++ b/client/src/core-components/checkbox-group.scss
@@ -0,0 +1,11 @@
+@import "../scss/vars";
+
+.checkbox-group {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+ &__item {
+ margin: 10px 0;
+ }
+}
\ No newline at end of file
diff --git a/client/src/core-components/checkbox.js b/client/src/core-components/checkbox.js
index c148783a..0dc5efed 100644
--- a/client/src/core-components/checkbox.js
+++ b/client/src/core-components/checkbox.js
@@ -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 (
-
+
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
+ {(this.props.label) ? this.renderLabel() : null}
+
+ );
+ }
+
+ renderLabel() {
+ return (
+
+ {this.props.label}
);
}
@@ -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
diff --git a/client/src/core-components/checkbox.scss b/client/src/core-components/checkbox.scss
index 9e09a1c5..68d3a18d 100644
--- a/client/src/core-components/checkbox.scss
+++ b/client/src/core-components/checkbox.scss
@@ -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 {
diff --git a/client/src/core-components/drop-down.scss b/client/src/core-components/drop-down.scss
index facd5c53..7082e708 100644
--- a/client/src/core-components/drop-down.scss
+++ b/client/src/core-components/drop-down.scss
@@ -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;
+ }
+ }
}
\ No newline at end of file
diff --git a/client/src/core-components/form-field.js b/client/src/core-components/form-field.js
index ad93729a..78cb9dad 100644
--- a/client/src/core-components/form-field.js
+++ b/client/src/core-components/form-field.js
@@ -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 = [
{this.props.label},
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;
diff --git a/client/src/data/fixtures/staff-fixtures.js b/client/src/data/fixtures/staff-fixtures.js
index 4aadb436..b0180b7b 100644
--- a/client/src/data/fixtures/staff-fixtures.js
+++ b/client/src/data/fixtures/staff-fixtures.js
@@ -536,6 +536,7 @@ module.exports = [
pages: 4
}
}
+
}
},
{
@@ -577,5 +578,68 @@ module.exports = [
}
}
}
+ },
+ {
+ path: '/staff/get-all',
+ time: 100,
+ response: function() {
+ return {
+ status: 'success',
+ data: [
+ {
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ name: 'Emilia Clarke',
+ departments: [{id: 2, name: 'Technical issues'}],
+ assignedTickets: 4,
+ closedTickets: 21,
+ lastLogin: 20161212
+ },
+ {
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ name: 'Yulian A GUI Yermo',
+ departments: [{id: 2, name: 'Technical issues'}, {id: 1, name: 'Sales Support'}],
+ assignedTickets: 9,
+ closedTickets: 0,
+ lastLogin: 20161212
+ },
+ {
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ name: 'Miltona Costa',
+ departments: [{id: 1, name: 'Sales Support'}],
+ assignedTickets: -1,
+ closedTickets: -1,
+ lastLogin: 20160212
+ },
+ {
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ name: 'Emiliasnikova Rusachestkvuy',
+ departments: [{id: 1, name: 'Sales Support'}, {id: 3, name: 'System and Administration'}],
+ assignedTickets: 100,
+ closedTickets: 21,
+ lastLogin: 20130101
+ },
+ {
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ name: 'Laurita Morrechaga Rusachestkvuy',
+ departments: [{id: 3, name: 'System and Administration'}],
+ assignedTickets: 1,
+ closedTickets: 1,
+ lastLogin: 2012050
+ }
+ ]
+ };
+ }
+ },
+ {
+ path: '/staff/add',
+ time: 100,
+ response: function () {
+ return {
+ status: 'success',
+ data: {
+ staffId: 5
+ }
+ };
+ }
}
];
\ No newline at end of file
diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js
index 0f60a85e..d67c1dcb 100644
--- a/client/src/data/languages/en.js
+++ b/client/src/data/languages/en.js
@@ -96,6 +96,15 @@ export default {
'DELETE_AND_BAN': 'Delete and ban',
'STAFF_LEVEL': 'Staff Level',
'ASSIGNED': 'Assigned',
+ 'ASSIGNED_TICKETS': '{tickets} assigned tickets',
+ 'CLOSED_TICKETS': '{tickets} closed tickets',
+ 'LAST_LOGIN': 'Last login',
+ '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.',
@@ -118,6 +127,8 @@ export default {
'ADD_ARTICLE_DESCRIPTION': 'Here you can add an article that will be available for every user. It will be added inside the category {category}.',
'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',
diff --git a/client/src/scss/_vars.scss b/client/src/scss/_vars.scss
index 59793c65..cea6e370 100644
--- a/client/src/scss/_vars.scss
+++ b/client/src/scss/_vars.scss
@@ -25,4 +25,37 @@ $font-size--sm: 13px;
$font-size--md: 16px;
$font-size--bg: 19px;
$font-size--lg: 24px;
-$font-size--xl: 32px;
\ No newline at end of file
+$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%);
+ }
+ }
+}
diff --git a/server/controllers/staff.php b/server/controllers/staff.php
index aafedcbd..fff73e65 100644
--- a/server/controllers/staff.php
+++ b/server/controllers/staff.php
@@ -6,6 +6,10 @@ require_once 'staff/get-tickets.php';
require_once 'staff/get-new-tickets.php';
require_once 'staff/get-all-tickets.php';
require_once 'staff/search-tickets.php';
+require_once 'staff/add.php';
+require_once 'staff/get-all.php';
+require_once 'staff/delete.php';
+require_once 'staff/edit.php';
$systemControllerGroup = new ControllerGroup();
$systemControllerGroup->setGroupPath('/staff');
@@ -17,5 +21,9 @@ $systemControllerGroup->addController(new GetTicketStaffController);
$systemControllerGroup->addController(new GetNewTicketsStaffController);
$systemControllerGroup->addController(new GetAllTicketsStaffController);
$systemControllerGroup->addController(new SearchTicketStaffController);
+$systemControllerGroup->addController(new AddStaffController);
+$systemControllerGroup->addController(new GetAllStaffController);
+$systemControllerGroup->addController(new DeleteStaffController);
+$systemControllerGroup->addController(new EditStaffController);
$systemControllerGroup->finalize();
\ No newline at end of file
diff --git a/server/controllers/staff/add.php b/server/controllers/staff/add.php
new file mode 100644
index 00000000..e3b13390
--- /dev/null
+++ b/server/controllers/staff/add.php
@@ -0,0 +1,87 @@
+ 'staff_3',
+ 'requestData' => [
+ 'name' => [
+ 'validation' => DataValidator::length(2, 55)->alpha(),
+ 'error' => ERRORS::INVALID_NAME
+ ],
+ 'email' => [
+ 'validation' => DataValidator::email(),
+ 'error' => ERRORS::INVALID_EMAIL
+ ],
+ 'password' => [
+ 'validation' => DataValidator::length(5, 200),
+ 'error' => ERRORS::INVALID_PASSWORD
+ ],
+ 'level' => [
+ 'validation' => DataValidator::between(1, 3, true),
+ 'error' => ERRORS::INVALID_LEVEL
+ ]
+
+ ]
+ ];
+ }
+
+ public function handler() {
+ $this->storeRequestData();
+ $staff = new Staff();
+
+ $staffRow = Staff::getDataStore($this->email,'email');
+
+ if($staffRow->isNull()) {
+ $staff->setProperties([
+ 'name'=> $this->name,
+ 'email' => $this->email,
+ 'password'=> Hashing::hashPassword($this->password),
+ 'profilePic' => $this->profilePic,
+ 'level' => $this->level,
+ 'sharedDepartmentList'=> $this->getDepartmentList(),
+ ]);
+
+
+ Response::respondSuccess([
+ 'id' => $staff->store()
+ ]);
+ return;
+ }
+
+ Response::respondError(ERRORS::ALREADY_A_STAFF);
+ }
+
+ public function storeRequestData() {
+ $this->name = Controller::request('name');
+ $this->email = Controller::request('email');
+ $this->password = Controller::request('password');
+ $this->profilePic = Controller::request('profilePic');
+ $this->level = Controller::request('level');
+ $this->departments = Controller::request('departments');
+ }
+
+ public function getDepartmentList() {
+ $listDepartments = new DataStoreList();
+ $departmentIds = json_decode($this->departments);
+
+ foreach($departmentIds as $id) {
+ $department = Department::getDataStore($id);
+ $listDepartments->add($department);
+ }
+
+ return $listDepartments;
+ }
+}
\ No newline at end of file
diff --git a/server/controllers/staff/delete.php b/server/controllers/staff/delete.php
new file mode 100644
index 00000000..d9332267
--- /dev/null
+++ b/server/controllers/staff/delete.php
@@ -0,0 +1,34 @@
+ 'staff_3',
+ 'requestData' => [
+ 'staffId' =>[
+ 'validation' => DataValidator::dataStoreId('staff'),
+ 'error' => ERRORS::INVALID_STAFF
+ ]
+ ]
+ ];
+ }
+
+ public function handler() {
+ $staffId = Controller::request('staffId');
+ $staff = Staff::getDataStore($staffId);
+
+ foreach($staff->sharedTicketList as $ticket) {
+ $ticket->owner = null;
+ $ticket->true = true;
+ $ticket->store();
+ }
+
+ $staff->delete();
+ Response::respondSuccess();
+ }
+
+}
\ No newline at end of file
diff --git a/server/controllers/staff/edit.php b/server/controllers/staff/edit.php
new file mode 100644
index 00000000..66500cc0
--- /dev/null
+++ b/server/controllers/staff/edit.php
@@ -0,0 +1,71 @@
+ 'staff_1',
+ 'requestData' => []
+ ];
+ }
+
+ public function handler() {
+ $this->staffId = Controller::request('staffId');
+
+ if(!$this->staffId) {
+ $this->staffRow = Controller::getLoggedUser();
+ } else if(Controller::isStaffLogged(3)) {
+ $this->staffRow = Staff::getDataStore($this->staffId, 'id');
+
+ if($this->staffRow->isNull()) {
+ Response::respondError(ERRORS::INVALID_STAFF);
+ return;
+ }
+ } else {
+ Response::respondError(ERRORS::NO_PERMISSION);
+ return;
+ }
+
+ $this->editInformation();
+ Response::respondSuccess();
+ }
+
+ public function editInformation() {
+
+ if(Controller::request('email')) {
+ $this->staffRow->email = Controller::request('email');
+ }
+
+ if(Controller::request('password')) {
+ $this->staffRow->password = Hashing::hashPassword(Controller::request('password'));
+ }
+
+ if(Controller::request('level') && Controller::isStaffLogged(3)) {
+ $this->staffRow->level = Controller::request('level');
+ }
+
+ if(Controller::request('departments') && Controller::isStaffLogged(3)) {
+ $this->staffRow->sharedDepartmentList = $this->getDepartmentList();
+ }
+
+ $this->staffRow->store();
+ }
+
+
+ public function getDepartmentList() {
+ $listDepartments = new DataStoreList();
+ $departmentIds = json_decode(Controller::request('departments'));
+
+ foreach($departmentIds as $id) {
+ $department = Department::getDataStore($id);
+ $listDepartments->add($department);
+ }
+
+ return $listDepartments;
+ }
+}
\ No newline at end of file
diff --git a/server/controllers/staff/get-all.php b/server/controllers/staff/get-all.php
new file mode 100644
index 00000000..2c93165a
--- /dev/null
+++ b/server/controllers/staff/get-all.php
@@ -0,0 +1,45 @@
+ 'staff_3',
+ 'requestData' => []
+ ];
+ }
+
+
+ public function handler() {
+ $staffs = Staff::getAll();
+ $staffArray = [];
+
+ foreach($staffs as $staff) {
+ $assignedTickets = 0;
+ $closedTickets = 0;
+
+ foreach ($staff->sharedTicketList as $ticket) {
+ if($ticket->closed) $closedTickets++;
+ else $assignedTickets++;
+ }
+
+ $staffArray[] = [
+ 'id' => $staff->id,
+ 'name' => $staff->name,
+ 'email' => $staff->email,
+ 'profilePic' => $staff->profilePic,
+ 'level' => $staff->level,
+ 'departments' => $staff->sharedDepartmentList->toArray(),
+ 'assignedTickets' => $assignedTickets,
+ 'closedTickets' => $closedTickets,
+ 'lastLogin' => $staff->lastLogin
+ ];
+ }
+
+ Response::respondSuccess($staffArray);
+
+ }
+}
\ No newline at end of file
diff --git a/server/controllers/staff/get.php b/server/controllers/staff/get.php
index db922c80..1cd6a491 100644
--- a/server/controllers/staff/get.php
+++ b/server/controllers/staff/get.php
@@ -14,6 +14,14 @@ class GetStaffController extends Controller {
public function handler() {
$user = Controller::getLoggedUser();
+
+ $userId = Controller::request('staffId');
+ $userRow = Staff::getDataStore($userId);
+
+ if($user->level == 3 && !$userRow->isNull()) {
+ $user = $userRow;
+ }
+
$parsedDepartmentList = [];
$departmentList = $user->sharedDepartmentList;
diff --git a/server/controllers/user/login.php b/server/controllers/user/login.php
index 07cb3340..020eae13 100644
--- a/server/controllers/user/login.php
+++ b/server/controllers/user/login.php
@@ -22,6 +22,10 @@ class LoginController extends Controller {
if ($this->checkInputCredentials() || $this->checkRememberToken()) {
$this->createUserSession();
$this->createSessionCookie();
+ if(Controller::request('staff')) {
+ $this->userInstance->lastLogin = Date::getCurrentDate();
+ $this->userInstance->store();
+ }
Response::respondSuccess($this->getUserData());
} else {
diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php
index 91387377..5f1c70b0 100644
--- a/server/data/ERRORS.php
+++ b/server/data/ERRORS.php
@@ -26,4 +26,7 @@ class ERRORS {
const INVALID_ORDER = 'INVALID_ORDER';
const INVALID_USER = 'INVALID_USER';
const ALREADY_BANNED = 'ALREADY_BANNED';
+ const INVALID_LEVEL = 'INVALID_LEVEL';
+ const ALREADY_A_STAFF = 'ALREADY_A_STAFF';
+ const INVALID_STAFF = 'INVALID_STAFF';
}
diff --git a/server/libs/validations/dataStoreId.php b/server/libs/validations/dataStoreId.php
index a510cb55..029ee33c 100644
--- a/server/libs/validations/dataStoreId.php
+++ b/server/libs/validations/dataStoreId.php
@@ -22,6 +22,9 @@ class DataStoreId extends AbstractRule {
case 'user':
$dataStore = \User::getUser($dataStoreId);
break;
+ case 'staff':
+ $dataStore = \Staff::getUser($dataStoreId);
+ break;
case 'ticket':
$dataStore = \Ticket::getTicket($dataStoreId);
break;
@@ -45,6 +48,7 @@ class DataStoreId extends AbstractRule {
private function isDataStoreNameValid($dataStoreName) {
return in_array($dataStoreName, [
'user',
+ 'staff',
'ticket',
'department',
'customresponse',
diff --git a/server/models/Department.php b/server/models/Department.php
index a263d38e..58d8cd38 100644
--- a/server/models/Department.php
+++ b/server/models/Department.php
@@ -24,4 +24,10 @@ class Department extends DataStore {
return $departmentsNameList;
}
+ public function toArray() {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name
+ ];
+ }
}
\ No newline at end of file
diff --git a/server/models/Staff.php b/server/models/Staff.php
index 03e3e078..cca0bc4c 100644
--- a/server/models/Staff.php
+++ b/server/models/Staff.php
@@ -17,7 +17,8 @@ class Staff extends DataStore {
'profilePic',
'level',
'sharedDepartmentList',
- 'sharedTicketList'
+ 'sharedTicketList',
+ 'lastLogin'
];
}
@@ -30,4 +31,17 @@ class Staff extends DataStore {
public static function getUser($value, $property = 'id') {
return parent::getDataStore($value, $property);
}
+
+ public function toArray() {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'email' => $this->email,
+ 'profilePic' => $this->profilePic,
+ 'level' => $this->level,
+ 'departments' => $this->sharedDepartmentList->toArray(),
+ 'tickets' => $this->sharedTicketList->toArray(),
+ 'lastLogin' => $this->lastLogin
+ ];
+ }
}
diff --git a/tests/init.rb b/tests/init.rb
index 45791f55..0af3e81a 100644
--- a/tests/init.rb
+++ b/tests/init.rb
@@ -25,7 +25,10 @@ require './ticket/custom-response.rb'
require './ticket/change-department.rb'
require './ticket/close.rb'
require './ticket/re-open.rb'
+require './staff/add.rb'
require './staff/get.rb'
+require './staff/edit.rb'
+require './staff/delete.rb'
require './staff/assign-ticket.rb'
require './staff/un-assign-ticket.rb'
require './staff/get-tickets.rb'
@@ -39,5 +42,6 @@ require './user/get-user.rb'
require './user/ban.rb'
require './user/get-users-test.rb'
require './user/delete.rb'
+require './staff/get-all.rb'
diff --git a/tests/staff/add.rb b/tests/staff/add.rb
new file mode 100644
index 00000000..5b189cc3
--- /dev/null
+++ b/tests/staff/add.rb
@@ -0,0 +1,42 @@
+describe'/staff/add' do
+ request('/user/logout')
+ Scripts.login($staff[:email], $staff[:password], true)
+
+ it 'should add staff member' do
+ result= request('/staff/add', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ name: 'Tyrion Lannister',
+ email: 'tyrion@opensupports.com',
+ password: 'testpassword',
+ level: 2,
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ departments: '[1]'
+ })
+
+ (result['status']).should.equal('success')
+
+ row = $database.getRow('staff', result['data']['id'], 'id')
+
+ (row['name']).should.equal('Tyrion Lannister')
+ (row['email']).should.equal('tyrion@opensupports.com')
+ (row['profile_pic']).should.equal('http://www.opensupports.com/profilepic.jpg')
+ (row['level']).should.equal('2')
+ end
+ it 'should fail if staff member is alrady a staff' do
+ result= request('/staff/add', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ name: 'Tyrion Lannister',
+ email: 'tyrion@opensupports.com',
+ password: 'testpassword',
+ level: 2,
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ departments: '[1]'
+ })
+
+ (result['status']).should.equal('fail')
+ (result['message']).should.equal('ALREADY_A_STAFF')
+
+ end
+end
\ No newline at end of file
diff --git a/tests/staff/delete.rb b/tests/staff/delete.rb
new file mode 100644
index 00000000..af55dcc5
--- /dev/null
+++ b/tests/staff/delete.rb
@@ -0,0 +1,29 @@
+describe'/staff/delete' do
+ request('/user/logout')
+ Scripts.login($staff[:email], $staff[:password], true)
+
+ it 'should delete staff member' do
+ result= request('/staff/delete', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ staffId:2
+ })
+
+ (result['status']).should.equal('success')
+
+ row = $database.getRow('staff', 2, 'id')
+ (row).should.equal(nil)
+
+ end
+ it 'should fail delete if staff member is does not exist' do
+ result= request('/staff/delete', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ staffId:2
+ })
+
+ (result['status']).should.equal('fail')
+ (result['message']).should.equal('INVALID_STAFF')
+
+ end
+end
\ No newline at end of file
diff --git a/tests/staff/edit.rb b/tests/staff/edit.rb
new file mode 100644
index 00000000..3167359f
--- /dev/null
+++ b/tests/staff/edit.rb
@@ -0,0 +1,56 @@
+describe'/staff/edit' do
+ request('/user/logout')
+ Scripts.login($staff[:email], $staff[:password], true)
+
+ it 'should edit another staff member' do
+ result= request('/staff/edit', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ email: 'LittleLannister@opensupports.com',
+ level: 1,
+ departments: '[1, 2]',
+ staffId: 2
+ })
+
+ (result['status']).should.equal('success')
+
+ row = $database.getRow('staff', 2, 'id')
+
+ (row['email']).should.equal('LittleLannister@opensupports.com')
+ (row['level']).should.equal('1')
+
+ rows = $database.getRow('department_staff', 2, 'staff_id')
+
+ (rows['department_id']).should.equal('1')
+
+ end
+
+ it 'should edit staff member ' do
+ request('/staff/add', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ name: 'Arya Stark',
+ password: 'starkpassword',
+ email: 'arya@opensupports.com',
+ level: 2,
+ profilePic: 'http://www.opensupports.com/profilepic.jpg',
+ departments: '[1]'
+ })
+ request('/user/logout')
+ Scripts.login('arya@opensupports.com', 'starkpassword', true)
+
+ result = request('/staff/edit', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ email: 'newwstaff@opensupports.com',
+ })
+
+ (result['status']).should.equal('success')
+
+ row = $database.getRow('staff', $csrf_userid, 'id')
+
+ (row['email']).should.equal('newwstaff@opensupports.com')
+ (row['level']).should.equal('2')
+
+ end
+end
\ No newline at end of file
diff --git a/tests/staff/get-all.rb b/tests/staff/get-all.rb
new file mode 100644
index 00000000..ba5544f5
--- /dev/null
+++ b/tests/staff/get-all.rb
@@ -0,0 +1,35 @@
+describe'/staff/get-all' do
+ request('/user/logout')
+ Scripts.login($staff[:email], $staff[:password], true)
+
+ it 'should get all staff member' do
+ result= request('/staff/get-all', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token
+ })
+
+ (result['status']).should.equal('success')
+
+ (result['data'][0]['name']).should.equal('Emilia Clarke')
+ (result['data'][0]['email']).should.equal('staff@opensupports.com')
+ (result['data'][0]['profilePic']).should.equal('http://www.opensupports.com/profilepic.jpg')
+ (result['data'][0]['level']).should.equal('3')
+ (result['data'][0]['departments'][0]['id']).should.equal('1')
+ (result['data'][0]['departments'][0]['name']).should.equal('Tech Support')
+ (result['data'][0]['departments'][1]['id']).should.equal('2')
+ (result['data'][0]['departments'][1]['name']).should.equal('Suggestions')
+ (result['data'][0]['departments'][2]['id']).should.equal('3')
+ (result['data'][0]['departments'][2]['name']).should.equal('Sales and Subscriptions')
+ (result['data'][0]['assignedTickets']).should.equal(3)
+ (result['data'][0]['closedTickets']).should.equal(0)
+
+ (result['data'][1]['name']).should.equal('Arya Stark')
+ (result['data'][1]['email']).should.equal('newwstaff@opensupports.com')
+ (result['data'][1]['profilePic']).should.equal('http://www.opensupports.com/profilepic.jpg')
+ (result['data'][1]['level']).should.equal('2')
+ (result['data'][1]['departments'][0]['id']).should.equal('1')
+ (result['data'][1]['departments'][0]['name']).should.equal('Tech Support')
+ (result['data'][1]['assignedTickets']).should.equal(0)
+ (result['data'][1]['closedTickets']).should.equal(0)
+ end
+end
\ No newline at end of file
diff --git a/tests/staff/get.rb b/tests/staff/get.rb
index a0abd11b..86f1870b 100644
--- a/tests/staff/get.rb
+++ b/tests/staff/get.rb
@@ -11,5 +11,20 @@ describe '/staff/get/' do
(result['status']).should.equal('success')
(result['data']['name']).should.equal('Emilia Clarke')
(result['data']['staff']).should.equal(true)
+ (result['data']['email']).should.equal('staff@opensupports.com')
+ (result['data']['level']).should.equal('3')
+ end
+ it 'should return staff member data with staff Id' do
+ result = request('/staff/get', {
+ csrf_userid: $csrf_userid,
+ csrf_token: $csrf_token,
+ staffId:2
+ })
+
+ (result['status']).should.equal('success')
+ (result['data']['name']).should.equal('Tyrion Lannister')
+ (result['data']['staff']).should.equal(true)
+ (result['data']['email']).should.equal('tyrion@opensupports.com')
+ (result['data']['level']).should.equal('2')
end
end
\ No newline at end of file