commit
5874cf279e
|
@ -19,13 +19,13 @@ class ArticleAddModal extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div className="add-article-modal">
|
||||
<div className="article-add-modal">
|
||||
<Header title={i18n('ADD_ARTICLE')} description={i18n('ADD_ARTICLE_DESCRIPTION', {category: this.props.topicName})} />
|
||||
<Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)}>
|
||||
<FormField name="title" label={i18n('TITLE')} field="input" fieldProps={{size: 'large'}} validation="TITLE" required/>
|
||||
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required/>
|
||||
<SubmitButton type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton>
|
||||
<Button className="add-article-modal__cancel-button" type="link" onClick={(event) => {
|
||||
<Button className="article-add-modal__cancel-button" type="link" onClick={(event) => {
|
||||
event.preventDefault();
|
||||
ModalContainer.closeModal();
|
||||
}}>{i18n('CANCEL')}</Button>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.article-add-article {
|
||||
.article-add-modal {
|
||||
width: 800px;
|
||||
|
||||
&__cancel-button {
|
||||
float: right;
|
||||
|
|
|
@ -11,6 +11,7 @@ import TopicEditModal from 'app-components/topic-edit-modal';
|
|||
import Loading from 'core-components/loading';
|
||||
import Button from 'core-components/button';
|
||||
import Icon from 'core-components/icon';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class ArticlesList extends React.Component {
|
||||
|
||||
|
@ -18,6 +19,7 @@ class ArticlesList extends React.Component {
|
|||
editable: React.PropTypes.bool,
|
||||
articlePath: React.PropTypes.string,
|
||||
loading: React.PropTypes.bool,
|
||||
errored: React.PropTypes.bool,
|
||||
topics: React.PropTypes.array,
|
||||
retrieveOnMount: React.PropTypes.bool
|
||||
};
|
||||
|
@ -34,6 +36,10 @@ class ArticlesList extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
if(this.props.errored) {
|
||||
return <Message type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>;
|
||||
}
|
||||
|
||||
return (this.props.loading) ? <Loading /> : this.renderContent();
|
||||
}
|
||||
|
||||
|
@ -84,6 +90,7 @@ class ArticlesList extends React.Component {
|
|||
export default connect((store) => {
|
||||
return {
|
||||
topics: store.articles.topics,
|
||||
errored: store.articles.errored,
|
||||
loading: store.articles.loading
|
||||
};
|
||||
})(ArticlesList);
|
||||
|
|
|
@ -10,7 +10,7 @@ class PeopleList extends React.Component {
|
|||
static propTypes = {
|
||||
list: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
profilePic: React.PropTypes.string,
|
||||
name: React.PropTypes.string,
|
||||
name: React.PropTypes.node,
|
||||
assignedTickets: React.PropTypes.number,
|
||||
closedTickets: React.PropTypes.number,
|
||||
lastLogin: React.PropTypes.number
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
|
||||
&__comment {
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
|
||||
&-pointer {
|
||||
right: 100%;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import i18n from 'lib-app/i18n';
|
||||
|
||||
class TicketInfo extends React.Component {
|
||||
|
@ -16,12 +18,12 @@ class TicketInfo extends React.Component {
|
|||
{this.props.ticket.content}
|
||||
</div>
|
||||
<div className="ticket-info__author">
|
||||
Author: {this.props.ticket.author.name}
|
||||
{i18n('AUTHOR')}: {this.props.ticket.author.name}
|
||||
</div>
|
||||
<div className="ticket-info__properties">
|
||||
<div className="ticket-info__properties__status">
|
||||
<span className="ticket-info__properties__label">
|
||||
Status:
|
||||
{i18n('STATUS')}:
|
||||
</span>
|
||||
<span className={this.getStatusClass()}>
|
||||
{(this.props.ticket.closed) ? 'closed' : 'open'}
|
||||
|
@ -29,7 +31,7 @@ class TicketInfo extends React.Component {
|
|||
</div>
|
||||
<div className="ticket-info__properties__priority">
|
||||
<span className="ticket-info__properties__label">
|
||||
Priority:
|
||||
{i18n('PRIORITY')}:
|
||||
</span>
|
||||
<span className={this.getPriorityClass()}>
|
||||
{this.props.ticket.priority}
|
||||
|
@ -37,24 +39,25 @@ class TicketInfo extends React.Component {
|
|||
</div>
|
||||
<div className="ticket-info__properties__owner">
|
||||
<span className="ticket-info__properties__label">
|
||||
Owned:
|
||||
{i18n('OWNED')}:
|
||||
</span>
|
||||
<span className="ticket-info__properties__badge-red">
|
||||
none
|
||||
<span className={this.getOwnedClass()}>
|
||||
{(this.props.ticket.owner) ? i18n('YES') : i18n('NO')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="ticket-info__properties__comments">
|
||||
<span className="ticket-info__properties__label">
|
||||
Comments:
|
||||
{i18n('COMMENTS')}:
|
||||
</span>
|
||||
<span className="ticket-info__properties__badge-blue">
|
||||
21
|
||||
{_.filter(this.props.ticket.events, event => event.type === 'COMMENT').length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getStatusClass() {
|
||||
if(this.props.ticket.closed) {
|
||||
return 'ticket-info__properties__badge-red';
|
||||
|
@ -63,6 +66,14 @@ class TicketInfo extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
getOwnedClass() {
|
||||
if(this.props.ticket.owner) {
|
||||
return 'ticket-info__properties__badge-green';
|
||||
} else {
|
||||
return 'ticket-info__properties__badge-red';
|
||||
}
|
||||
}
|
||||
|
||||
getPriorityClass() {
|
||||
let priorityClasses = {
|
||||
'low': 'ticket-info__properties__badge-green',
|
||||
|
|
|
@ -3,19 +3,28 @@
|
|||
.ticket-info {
|
||||
width: 300px;
|
||||
font-weight: normal;
|
||||
&__title{
|
||||
|
||||
&__title {
|
||||
color: $primary-black;
|
||||
font-variant: small-caps;
|
||||
font-size: $font-size--md;
|
||||
}
|
||||
&__description{
|
||||
color: $grey;
|
||||
|
||||
&__description {
|
||||
margin-top: 5px;
|
||||
font-size: small;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&__author{
|
||||
|
||||
&__author {
|
||||
color: $primary-blue;
|
||||
margin-bottom: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
&__properties{
|
||||
|
||||
&__properties {
|
||||
background-color: $grey;
|
||||
padding: 10px;
|
||||
font-variant: small-caps;
|
||||
|
@ -35,7 +44,7 @@
|
|||
|
||||
&__badge-green,
|
||||
&__badge-blue,
|
||||
&__badge-red{
|
||||
&__badge-red {
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
display: inline-block;
|
||||
|
@ -44,19 +53,19 @@
|
|||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&__badge-green{
|
||||
&__badge-green {
|
||||
background-color: $primary-green;
|
||||
}
|
||||
|
||||
&__badge-blue{
|
||||
&__badge-blue {
|
||||
background-color: $secondary-blue;
|
||||
}
|
||||
|
||||
&__badge-red{
|
||||
&__badge-red {
|
||||
background-color: $primary-red;
|
||||
}
|
||||
|
||||
&__label{
|
||||
&__label {
|
||||
text-align: right;
|
||||
width: 71px;
|
||||
display: inline-block;
|
||||
|
@ -64,10 +73,11 @@
|
|||
}
|
||||
|
||||
&__status,
|
||||
&__owner{
|
||||
&__owner {
|
||||
margin-right: 10px;
|
||||
width: 125px;
|
||||
.ticket-info__properties__label{
|
||||
|
||||
.ticket-info__properties__label {
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import FormField from 'core-components/form-field';
|
|||
import SubmitButton from 'core-components/submit-button';
|
||||
import DropDown from 'core-components/drop-down';
|
||||
import Button from 'core-components/button';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class TicketViewer extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -188,6 +189,7 @@ class TicketViewer extends React.Component {
|
|||
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||
</Form>
|
||||
</div>
|
||||
{(this.state.commentError) ? this.renderCommentError() : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -216,6 +218,12 @@ class TicketViewer extends React.Component {
|
|||
return customResponsesNode;
|
||||
}
|
||||
|
||||
renderCommentError() {
|
||||
return (
|
||||
<Message className="ticket-viewer__message" type="error">{i18n('TICKET_COMMENT_ERROR')}</Message>
|
||||
);
|
||||
}
|
||||
|
||||
getCommentFormProps() {
|
||||
return {
|
||||
onSubmit: this.onSubmit.bind(this),
|
||||
|
@ -316,7 +324,8 @@ class TicketViewer extends React.Component {
|
|||
|
||||
onCommentSuccess() {
|
||||
this.setState({
|
||||
loading: false
|
||||
loading: false,
|
||||
commentError: false
|
||||
});
|
||||
|
||||
this.onTicketModification();
|
||||
|
@ -324,7 +333,8 @@ class TicketViewer extends React.Component {
|
|||
|
||||
onCommentFail() {
|
||||
this.setState({
|
||||
loading: false
|
||||
loading: false,
|
||||
commentError: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import {Link} from 'react-router';
|
||||
|
@ -78,11 +79,33 @@ class TopicViewer extends React.Component {
|
|||
}
|
||||
|
||||
renderArticleItem(article, index) {
|
||||
let props = {
|
||||
className: 'topic-viewer__list-item',
|
||||
key: index,
|
||||
draggable: true
|
||||
};
|
||||
|
||||
if(this.props.editable) {
|
||||
_.extend(props, {
|
||||
onDragOver: (this.state.currentDraggedId) ? this.onItemDragOver.bind(this, article, index) : null,
|
||||
onDrop: (this.state.currentDraggedId) ? this.onItemDrop.bind(this, article, index) : null,
|
||||
onDragStart: () => {
|
||||
this.setState({currentDraggedId: article.id})
|
||||
},
|
||||
onDragEnd: () => {
|
||||
if(this.state.currentDraggedId) {
|
||||
this.setState({articles: this.props.articles, currentDraggedId: 0});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="topic-viewer__list-item" key={index}>
|
||||
<li {...props}>
|
||||
<Link {...this.getArticleLinkProps(article, index)}>
|
||||
{article.title}
|
||||
</Link>
|
||||
<Icon className="topic-viewer__grab-icon" name="arrows" ref={'grab-' + index}/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
@ -126,31 +149,16 @@ class TopicViewer extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
getArticleLinkProps(article, index) {
|
||||
getArticleLinkProps(article) {
|
||||
let classes = {
|
||||
'topic-viewer__list-item-button': true,
|
||||
'topic-viewer__list-item-hidden': article.hidden
|
||||
};
|
||||
|
||||
let props = {
|
||||
|
||||
return {
|
||||
className: classNames(classes),
|
||||
to: this.props.articlePath + article.id
|
||||
};
|
||||
|
||||
if(this.props.editable) {
|
||||
_.extend(props, {
|
||||
onDragOver: this.onItemDragOver.bind(this, article, index),
|
||||
onDrop: this.onItemDrop.bind(this, article, index),
|
||||
onDragStart: () => this.setState({currentDraggedId: article.id}),
|
||||
onDragEnd: () => {
|
||||
if(this.state.currentDraggedId) {
|
||||
this.setState({articles: this.props.articles});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
onDeleteClick() {
|
||||
|
|
|
@ -38,12 +38,22 @@
|
|||
width: 50%;
|
||||
color: $secondary-blue;
|
||||
margin-bottom: 10px;
|
||||
user-select: none;
|
||||
|
||||
&-hidden {
|
||||
width: 80%;
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.topic-viewer__grab-icon {
|
||||
display: inline-block;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-item:before {
|
||||
|
@ -52,10 +62,18 @@
|
|||
}
|
||||
|
||||
&-item-button {
|
||||
display: inline-block;
|
||||
color: $secondary-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&__grab-icon {
|
||||
color: $grey;
|
||||
cursor: move;
|
||||
margin-left: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__add-item {
|
||||
color: $dark-grey;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import {connect} from 'react-redux';
|
||||
import {browserHistory} from 'react-router';
|
||||
import RichTextEditor from 'react-rte-browserify';
|
||||
|
||||
import ArticlesActions from 'actions/articles-actions';
|
||||
|
@ -9,6 +10,7 @@ import i18n from 'lib-app/i18n';
|
|||
import API from 'lib-app/api-call';
|
||||
import DateTransformer from 'lib-core/date-transformer';
|
||||
|
||||
import AreYouSure from 'app-components/are-you-sure';
|
||||
import Header from 'core-components/header';
|
||||
import Loading from 'core-components/loading';
|
||||
import Button from 'core-components/button';
|
||||
|
@ -61,10 +63,14 @@ class AdminPanelViewArticle extends React.Component {
|
|||
renderArticlePreview(article) {
|
||||
return (
|
||||
<div className="admin-panel-view-article__content">
|
||||
<div className="admin-panel-view-article__edit-button">
|
||||
<Button size="medium" onClick={this.onEditClick.bind(this, article)}>{i18n('EDIT')}</Button>
|
||||
<div className="admin-panel-view-article__edit-buttons">
|
||||
<Button className="admin-panel-view-article__edit-button" size="medium" onClick={this.onEditClick.bind(this, article)} type="tertiary">
|
||||
{i18n('EDIT')}
|
||||
</Button>
|
||||
<Button size="medium" onClick={this.onDeleteClick.bind(this, article)}>
|
||||
{i18n('DELETE')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="admin-panel-view-article__article">
|
||||
<Header title={article.title}/>
|
||||
|
||||
|
@ -116,6 +122,10 @@ class AdminPanelViewArticle extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onDeleteClick(article) {
|
||||
AreYouSure.openModal(i18n('DELETE_ARTICLE_DESCRIPTION'), this.onArticleDeleted.bind(this, article));
|
||||
}
|
||||
|
||||
onFormSubmit(form) {
|
||||
API.call({
|
||||
path: '/article/edit',
|
||||
|
@ -139,6 +149,15 @@ class AdminPanelViewArticle extends React.Component {
|
|||
editable: false
|
||||
});
|
||||
}
|
||||
|
||||
onArticleDeleted(article) {
|
||||
API.call({
|
||||
path: '/article/delete',
|
||||
data: {
|
||||
articleId: article.id
|
||||
}
|
||||
}).then(() => browserHistory.push('/admin/panel/articles/list-articles'));
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
.admin-panel-view-article {
|
||||
|
||||
&__edit-button {
|
||||
&__edit-buttons {
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__edit-button {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&__last-edited {
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {browserHistory} from 'react-router';
|
||||
import _ from 'lodash';
|
||||
|
||||
import i18n from 'lib-app/i18n';
|
||||
|
@ -36,7 +36,8 @@ class AdminPanelViewStaff extends React.Component {
|
|||
|
||||
getProps() {
|
||||
return _.extend({}, this.state.userData, {
|
||||
staffId: this.props.params.staffId * 1
|
||||
staffId: this.props.params.staffId * 1,
|
||||
onDelete: this.onDelete.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -46,6 +47,10 @@ class AdminPanelViewStaff extends React.Component {
|
|||
userData: result.data
|
||||
});
|
||||
}
|
||||
|
||||
onDelete() {
|
||||
browserHistory.push('/admin/panel/staff/staff-members');
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminPanelViewStaff;
|
|
@ -5,10 +5,13 @@ import i18n from 'lib-app/i18n';
|
|||
import API from 'lib-app/api-call';
|
||||
import SessionStore from 'lib-app/session-store';
|
||||
import TicketList from 'app-components/ticket-list';
|
||||
import AreYouSure from 'app-components/are-you-sure';
|
||||
|
||||
import Form from 'core-components/form';
|
||||
import FormField from 'core-components/form-field';
|
||||
import SubmitButton from 'core-components/submit-button';
|
||||
import Message from 'core-components/message';
|
||||
import Button from 'core-components/button';
|
||||
|
||||
class StaffEditor extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -20,18 +23,21 @@ class StaffEditor extends React.Component {
|
|||
level: React.PropTypes.number.isRequired,
|
||||
tickets: React.PropTypes.array.isRequired,
|
||||
departments: React.PropTypes.array.isRequired,
|
||||
onChange: React.PropTypes.func
|
||||
onChange: React.PropTypes.func,
|
||||
onDelete: React.PropTypes.func
|
||||
};
|
||||
|
||||
state = {
|
||||
email: this.props.email,
|
||||
level: this.props.level - 1,
|
||||
message: null,
|
||||
departments: this.getUserDepartments()
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="staff-editor">
|
||||
{(this.state.message) ? this.renderMessage() : null}
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<div className="staff-editor__card">
|
||||
|
@ -67,12 +73,12 @@ class StaffEditor extends React.Component {
|
|||
</div>
|
||||
<div className="col-md-8">
|
||||
<div className="staff-editor__form">
|
||||
<Form className="staff-editor__update-email" values={{email: this.state.email}} onChange={form => this.setState({email: form.email})} onSubmit={this.onSubmit.bind(this)}>
|
||||
<Form className="staff-editor__update-email" values={{email: this.state.email}} onChange={form => this.setState({email: form.email})} onSubmit={this.onSubmit.bind(this, 'EMAIL')}>
|
||||
<FormField name="email" validation="EMAIL" required label={i18n('EMAIL')} fieldProps={{size: 'large'}}/>
|
||||
<SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_EMAIL')}</SubmitButton>
|
||||
</Form>
|
||||
<span className="separator staff-editor__separator" />
|
||||
<Form className="staff-editor__update-password" onSubmit={this.onSubmit.bind(this)}>
|
||||
<Form className="staff-editor__update-password" onSubmit={this.onSubmit.bind(this, 'PASSWORD')}>
|
||||
<FormField name="password" validation="PASSWORD" required label={i18n('PASSWORD')} fieldProps={{size: 'large', password: true}}/>
|
||||
<FormField name="rpassword" validation="REPEAT_PASSWORD" required label={i18n('REPEAT_PASSWORD')} fieldProps={{size: 'large', password: true}}/>
|
||||
<SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_PASSWORD')}</SubmitButton>
|
||||
|
@ -96,15 +102,41 @@ class StaffEditor extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
{(this.props.tickets) ? this.renderTickets() : null}
|
||||
{(!this.props.myAccount) ? this.renderDelete() : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
let messageType = (this.state.message === 'FAIL') ? 'error' : 'success';
|
||||
let message = null;
|
||||
|
||||
switch (this.state.message) {
|
||||
case 'EMAIL':
|
||||
message = 'EMAIL_CHANGED';
|
||||
break;
|
||||
case 'PASSWORD':
|
||||
message = 'PASSWORD_CHANGED';
|
||||
break;
|
||||
case 'LEVEL':
|
||||
message = 'LEVEL_UPDATED';
|
||||
break;
|
||||
case 'DEPARTMENTS':
|
||||
message = 'DEPARTMENTS_UPDATED';
|
||||
break;
|
||||
case 'FAIL':
|
||||
message = 'FAILED_EDIT_STAFF';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Message className="staff-editor__message" type={messageType}>{i18n(message)}</Message>;
|
||||
}
|
||||
|
||||
renderLevelForm() {
|
||||
return (
|
||||
<div>
|
||||
<span className="separator staff-editor__separator"/>
|
||||
<Form className="staff-editor__update-level" values={{level: this.state.level}} onChange={form => this.setState({level: form.level})} onSubmit={this.onSubmit.bind(this)}>
|
||||
<Form className="staff-editor__update-level" values={{level: this.state.level}} onChange={form => this.setState({level: form.level})} onSubmit={this.onSubmit.bind(this, 'LEVEL')}>
|
||||
<FormField name="level" label={i18n('LEVEL')} field="select" fieldProps={{
|
||||
items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}],
|
||||
size: 'large'
|
||||
|
@ -117,7 +149,7 @@ class StaffEditor extends React.Component {
|
|||
|
||||
renderDepartmentsForm() {
|
||||
return (
|
||||
<Form values={{departments: this.state.departments}} onChange={form => this.setState({departments: form.departments})} onSubmit={this.onSubmit.bind(this)}>
|
||||
<Form values={{departments: this.state.departments}} onChange={form => this.setState({departments: form.departments})} onSubmit={this.onSubmit.bind(this, 'DEPARTMENTS')}>
|
||||
<FormField name="departments" field="checkbox-group" fieldProps={{items: this.getDepartments()}} />
|
||||
<SubmitButton size="medium">{i18n('UPDATE_DEPARTMENTS')}</SubmitButton>
|
||||
</Form>
|
||||
|
@ -144,6 +176,22 @@ class StaffEditor extends React.Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDelete() {
|
||||
return (
|
||||
<div>
|
||||
<span className="separator"/>
|
||||
<div className="staff-editor__delete">
|
||||
<div className="staff-editor__delete-title">
|
||||
{i18n('DELETE_STAFF_MEMBER')}
|
||||
</div>
|
||||
<Button onClick={AreYouSure.openModal.bind(this, i18n('WILL_DELETE_STAFF'), this.onDeleteClick.bind(this))}>
|
||||
{i18n('DELETE_STAFF_MEMBER')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getTicketListProps() {
|
||||
return {
|
||||
|
@ -171,7 +219,7 @@ class StaffEditor extends React.Component {
|
|||
return SessionStore.getDepartments().map(department => department.name);
|
||||
}
|
||||
|
||||
onSubmit(form) {
|
||||
onSubmit(eventType, form) {
|
||||
let departments;
|
||||
|
||||
if(form.departments) {
|
||||
|
@ -189,7 +237,29 @@ class StaffEditor extends React.Component {
|
|||
level: (form.level !== undefined) ? form.level + 1 : null,
|
||||
departments: departments && JSON.stringify(departments)
|
||||
}
|
||||
}).then(this.props.onChange);
|
||||
}).then(() => {
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: eventType});
|
||||
|
||||
if(this.props.onChange) {
|
||||
this.props.onChange();
|
||||
}
|
||||
}).catch(() => {
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: 'FAIL'});
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteClick() {
|
||||
API.call({
|
||||
path: '/staff/delete',
|
||||
data: {
|
||||
staffId: this.props.staffId
|
||||
}
|
||||
}).then(this.props.onDelete).catch(() => {
|
||||
window.scrollTo(0,0);
|
||||
this.setState({message: 'FAIL'});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -122,4 +122,21 @@
|
|||
&__separator {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
&__message {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__delete {
|
||||
border: 1px solid $grey;
|
||||
padding: 20px 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__delete-title {
|
||||
font-size: $font-size--md;
|
||||
text-align: center;
|
||||
float: left;
|
||||
margin-top: 11px;
|
||||
}
|
||||
}
|
|
@ -4,9 +4,11 @@ import {connect} from 'react-redux';
|
|||
import i18n from 'lib-app/i18n';
|
||||
|
||||
import AdminDataAction from 'actions/admin-data-actions';
|
||||
import Header from 'core-components/header';
|
||||
import TicketList from 'app-components/ticket-list';
|
||||
|
||||
import Header from 'core-components/header';
|
||||
import SearchBox from 'core-components/search-box';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class AdminPanelAllTickets extends React.Component {
|
||||
|
||||
|
@ -31,7 +33,7 @@ class AdminPanelAllTickets extends React.Component {
|
|||
<div className="admin-panel-my-tickets__search-box">
|
||||
<SearchBox onSearch={this.onSearch.bind(this)} />
|
||||
</div>
|
||||
<TicketList {...this.getTicketListProps()}/>
|
||||
{(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getTicketListProps()}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -52,7 +54,8 @@ class AdminPanelAllTickets extends React.Component {
|
|||
|
||||
onSearch(query) {
|
||||
this.setState({query, page: 1});
|
||||
if (query) {
|
||||
|
||||
if(query) {
|
||||
this.props.dispatch(AdminDataAction.searchTickets(query));
|
||||
} else {
|
||||
this.props.dispatch(AdminDataAction.retrieveAllTickets());
|
||||
|
@ -60,9 +63,7 @@ class AdminPanelAllTickets extends React.Component {
|
|||
}
|
||||
|
||||
onPageChange(event) {
|
||||
this.setState({
|
||||
page: event.target.value
|
||||
});
|
||||
this.setState({page: event.target.value});
|
||||
|
||||
if(this.state.query) {
|
||||
this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value));
|
||||
|
@ -77,6 +78,7 @@ export default connect((store) => {
|
|||
departments: store.session.userDepartments,
|
||||
tickets: store.adminData.allTickets,
|
||||
pages: store.adminData.allTicketsPages,
|
||||
loading: !store.adminData.allTicketsLoaded
|
||||
loading: !store.adminData.allTicketsLoaded,
|
||||
error: store.adminData.allTicketsError
|
||||
};
|
||||
})(AdminPanelAllTickets);
|
||||
|
|
|
@ -4,9 +4,11 @@ import {connect} from 'react-redux';
|
|||
import i18n from 'lib-app/i18n';
|
||||
|
||||
import AdminDataAction from 'actions/admin-data-actions';
|
||||
import Header from 'core-components/header';
|
||||
import TicketList from 'app-components/ticket-list';
|
||||
|
||||
import Header from 'core-components/header';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class AdminPanelMyTickets extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -22,7 +24,7 @@ class AdminPanelMyTickets extends React.Component {
|
|||
return (
|
||||
<div className="admin-panel-my-tickets">
|
||||
<Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} />
|
||||
<TicketList {...this.getProps()}/>
|
||||
{(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -42,6 +44,7 @@ export default connect((store) => {
|
|||
return {
|
||||
departments: store.session.userDepartments,
|
||||
tickets: store.adminData.myTickets,
|
||||
loading: !store.adminData.myTicketsLoaded
|
||||
loading: !store.adminData.myTicketsLoaded,
|
||||
error: store.adminData.myTicketsError
|
||||
};
|
||||
})(AdminPanelMyTickets);
|
||||
|
|
|
@ -4,9 +4,11 @@ import {connect} from 'react-redux';
|
|||
import i18n from 'lib-app/i18n';
|
||||
|
||||
import AdminDataAction from 'actions/admin-data-actions';
|
||||
import Header from 'core-components/header';
|
||||
import TicketList from 'app-components/ticket-list';
|
||||
|
||||
import Header from 'core-components/header';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class AdminPanelNewTickets extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -22,7 +24,7 @@ class AdminPanelNewTickets extends React.Component {
|
|||
return (
|
||||
<div className="admin-panel-my-tickets">
|
||||
<Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} />
|
||||
<TicketList {...this.getProps()}/>
|
||||
{(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -42,6 +44,7 @@ export default connect((store) => {
|
|||
return {
|
||||
departments: store.session.userDepartments,
|
||||
tickets: store.adminData.newTickets,
|
||||
loading: !store.adminData.newTicketsLoaded
|
||||
loading: !store.adminData.newTicketsLoaded,
|
||||
error: store.adminData.newTicketsError
|
||||
};
|
||||
})(AdminPanelNewTickets);
|
||||
|
|
|
@ -11,24 +11,35 @@ import Button from 'core-components/button';
|
|||
import SubmitButton from 'core-components/submit-button';
|
||||
import Form from 'core-components/form';
|
||||
import FormField from 'core-components/form-field';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class AdminPanelBanUsers extends React.Component {
|
||||
|
||||
state = {
|
||||
loadingList: true,
|
||||
loadingForm: false,
|
||||
listError: false,
|
||||
addBanStatus: 'none',
|
||||
emails: [],
|
||||
filteredEmails: []
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.retrieveEmails()
|
||||
this.retrieveEmails();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="admin-panel-ban-users row">
|
||||
<Header title={i18n('BAN_USERS')} description={i18n('BAN_USERS_DESCRIPTION')} />
|
||||
{(this.state.listError) ? <Message type="error">{i18n('ERROR_RETRIEVING_BAN_LIST')}</Message> : this.renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return (
|
||||
<div>
|
||||
<div className="admin-panel-ban-users__email-list col-md-6">
|
||||
<SearchBox className="admin-panel-ban-users__search" onSearch={this.onSearch.bind(this)} searchOnType placeholder={i18n('SEARCH_EMAIL')}/>
|
||||
<Table {...this.getTableProps()} />
|
||||
|
@ -41,11 +52,23 @@ class AdminPanelBanUsers extends React.Component {
|
|||
<FormField className="admin-panel-ban-users__input" placeholder="email" name="email" validation="EMAIL" required fieldProps={{size: 'large'}}/>
|
||||
<SubmitButton>{i18n('BAN_EMAIL')}</SubmitButton>
|
||||
</Form>
|
||||
{this.renderMessage()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.addBanStatus) {
|
||||
case 'success':
|
||||
return <Message className="admin-panel-ban-users__form-message" type="success">{i18n('EMAIL_BANNED_SUCCESSFULLY')}</Message>;
|
||||
case 'fail':
|
||||
return <Message className="admin-panel-ban-users__form-message" type="error">{i18n('ERROR_BANNING_EMAIL')}</Message>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getTableProps() {
|
||||
return {
|
||||
loading: this.state.loadingList,
|
||||
|
@ -94,7 +117,12 @@ class AdminPanelBanUsers extends React.Component {
|
|||
data: {
|
||||
email: form.email
|
||||
}
|
||||
}).then(this.retrieveEmails.bind(this));
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
addBanStatus: 'success'
|
||||
});
|
||||
this.retrieveEmails();
|
||||
}).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false}));
|
||||
}
|
||||
|
||||
onUnBanClick(email) {
|
||||
|
@ -114,14 +142,17 @@ class AdminPanelBanUsers extends React.Component {
|
|||
API.call({
|
||||
path: '/user/list-ban',
|
||||
data: {}
|
||||
}).then((result) => {
|
||||
this.setState({
|
||||
loadingList: false,
|
||||
loadingForm: false,
|
||||
emails: result.data,
|
||||
filteredEmails: result.data
|
||||
});
|
||||
});
|
||||
}).then(result => this.setState({
|
||||
listError: false,
|
||||
loadingList: false,
|
||||
loadingForm: false,
|
||||
emails: result.data,
|
||||
filteredEmails: result.data
|
||||
})).catch(() => this.setState({
|
||||
listError: true,
|
||||
loadingList: false,
|
||||
loadingForm: false
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
.admin-panel-ban-users {
|
||||
padding: 0 20px;
|
||||
|
||||
&__email-list {
|
||||
|
||||
}
|
||||
|
||||
&__search {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
@ -33,4 +29,8 @@
|
|||
&__input {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__form-message {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import Header from 'core-components/header';
|
|||
import Table from 'core-components/table';
|
||||
import SearchBox from 'core-components/search-box';
|
||||
import Button from 'core-components/button';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class AdminPanelListUsers extends React.Component {
|
||||
|
||||
|
@ -16,6 +17,7 @@ class AdminPanelListUsers extends React.Component {
|
|||
users: [],
|
||||
orderBy: 'id',
|
||||
desc: true,
|
||||
error: false,
|
||||
page: 1,
|
||||
pages: 1
|
||||
};
|
||||
|
@ -34,7 +36,7 @@ class AdminPanelListUsers extends React.Component {
|
|||
<div className="admin-panel-list-users">
|
||||
<Header title={i18n('LIST_USERS')} description={i18n('LIST_USERS_DESCRIPTION')} />
|
||||
<SearchBox className="admin-panel-list-users__search-box" placeholder={i18n('SEARCH_USERS')} onSearch={this.onSearch.bind(this)} />
|
||||
<Table {...this.getTableProps()} />
|
||||
{(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : <Table {...this.getTableProps()}/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -144,7 +146,7 @@ class AdminPanelListUsers extends React.Component {
|
|||
API.call({
|
||||
path: '/user/get-users',
|
||||
data: data
|
||||
}).then(this.onUsersRetrieved.bind(this));
|
||||
}).then(this.onUsersRetrieved.bind(this)).catch(this.onUsersRejected.bind(this));
|
||||
}
|
||||
|
||||
onUsersRetrieved(result) {
|
||||
|
@ -154,6 +156,14 @@ class AdminPanelListUsers extends React.Component {
|
|||
users: result.data.users,
|
||||
orderBy: result.data.orderBy,
|
||||
desc: (result.data.desc === '1'),
|
||||
error: false,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
|
||||
onUsersRejected() {
|
||||
this.setState({
|
||||
error: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,11 +5,13 @@ import {browserHistory} from 'react-router';
|
|||
import i18n from 'lib-app/i18n';
|
||||
import API from 'lib-app/api-call';
|
||||
|
||||
import Header from 'core-components/header';
|
||||
import Button from 'core-components/button';
|
||||
import TicketList from 'app-components/ticket-list';
|
||||
import AreYouSure from 'app-components/are-you-sure';
|
||||
|
||||
import Header from 'core-components/header';
|
||||
import Button from 'core-components/button';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
class AdminPanelViewUser extends React.Component {
|
||||
|
||||
state = {
|
||||
|
@ -43,7 +45,7 @@ class AdminPanelViewUser extends React.Component {
|
|||
renderInvalid() {
|
||||
return (
|
||||
<div className="admin-panel-view-user__invalid">
|
||||
{i18n('INVALID_USER')}
|
||||
<Message type="error">{i18n('INVALID_USER')}</Message>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class DashboardListArticlesPage extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div classnames="dashboard-list-articles-page">
|
||||
<div className="dashboard-list-articles-page">
|
||||
<Header title={i18n('LIST_ARTICLES')} description={i18n('LIST_ARTICLES_DESCRIPTION')}/>
|
||||
<SearchBox className="dashboard-list-articles-page__search-box" onSearch={this.onSearch.bind(this)} searchOnType />
|
||||
{(!this.state.showSearchResults) ? this.renderArticleList() : this.renderSearchResults()}
|
||||
|
|
|
@ -19,7 +19,7 @@ class Icon extends React.Component {
|
|||
|
||||
renderFontIcon() {
|
||||
return (
|
||||
<span className={this.getFontIconClass()} aria-hidden="true" style={{color: this.props.color}}/>
|
||||
<span onClick={this.props.onClick} className={this.getFontIconClass()} aria-hidden="true" style={{color: this.props.color}}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class Message extends React.Component {
|
|||
getAnimationProps() {
|
||||
return {
|
||||
defaultStyle: {
|
||||
opacity: spring(0, [100, 30])
|
||||
opacity: 0
|
||||
},
|
||||
style: {
|
||||
opacity: spring(1, [100, 30])
|
||||
|
|
|
@ -28,7 +28,7 @@ class Tooltip extends React.Component {
|
|||
|
||||
renderAnimatedMessage() {
|
||||
return (
|
||||
<Motion defaultStyle={{opacity: 0}} style={{opacity: spring(1)}}>
|
||||
<Motion defaultStyle={{opacity: 0, top: -30}} style={{opacity: spring(1), top: spring(0)}}>
|
||||
{this.renderMessage.bind(this)}
|
||||
</Motion>
|
||||
)
|
||||
|
@ -36,7 +36,7 @@ class Tooltip extends React.Component {
|
|||
|
||||
renderMessage(animation) {
|
||||
return (
|
||||
<div style={animation}>
|
||||
<div className="tooltip__animated-container" style={animation}>
|
||||
<span className="tooltip__pointer-shadow"/>
|
||||
<span className="tooltip__pointer"/>
|
||||
<div className="tooltip__message">
|
||||
|
|
|
@ -8,12 +8,15 @@
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
&__animated-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__message {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: -25%;
|
||||
margin-bottom: 15px;
|
||||
margin-left: -10px;
|
||||
margin-left: -15px;
|
||||
border: 0 solid rgba(0, 0, 0, 0.247059);
|
||||
box-shadow: rgba(0, 0, 0, 0.247059) 0 -1px 4px;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -1047,5 +1047,15 @@ module.exports = [
|
|||
data: {}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/staff/delete',
|
||||
time: 100,
|
||||
response: function () {
|
||||
return {
|
||||
status: 'success',
|
||||
data: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -147,7 +147,7 @@ module.exports = [
|
|||
email: 'kurt@currycurrylady.hs',
|
||||
tickets: _.times(13).map(() => {
|
||||
return {
|
||||
ticketNumber: '1185510000',
|
||||
ticketNumber: '118551',
|
||||
title: 'Lorem ipsum door',
|
||||
content: 'I had a problem with the installation of the php server',
|
||||
department: {
|
||||
|
@ -368,7 +368,7 @@ module.exports = [
|
|||
{
|
||||
ticketNumber: '445441',
|
||||
title: 'Problem with installation',
|
||||
content: 'I had a problem with the installation of the php server',
|
||||
content: 'In varius, tellus ut luctus vestibulum, orci erat commodo ligula, sit amet bibendum arcu libero sed magna. Suspendisse in ligula vitae ante placerat varius id in eros. Etiam commodo viverra nisi in ornare. Donec ullamcorper felis sapien, eu laoreet dolor tincidunt nec. Aliquam erat volutpat. Proin semper viverra purus eget facilisis. Proin fermentum et odio in elementum. Maecenas lacinia, massa consectetur gravida lacinia, nisl lectus tincidunt diam, ut viverra ipsum ex sit amet diam. Mauris ac massa turpis. Fusce ultrices venenatis vestibulum. In et nulla purus. Nullam porta vestibulum leo in dignissim. Duis id ullamcorper odio. Ut purus nulla, consequat lobortis volutpat quis, consequat et libero. Maecenas sit amet libero laoreet, dictum sapien at, hendrerit sapien.',
|
||||
department: {
|
||||
id: 2,
|
||||
name: 'Technical Issues'
|
||||
|
@ -502,9 +502,7 @@ module.exports = [
|
|||
name: 'Haskell Curry',
|
||||
email: 'haskell@lambda.com'
|
||||
},
|
||||
owner: {
|
||||
name: 'Steve Jobs'
|
||||
},
|
||||
owner: null,
|
||||
events: [
|
||||
{
|
||||
type: 'ASSIGN',
|
||||
|
@ -516,17 +514,6 @@ module.exports = [
|
|||
staff: true
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'COMMENT',
|
||||
date: '201504090912',
|
||||
content: 'Do you have apache installed? It generally happens if you dont have apache.',
|
||||
author: {
|
||||
name: 'Emilia Clarke',
|
||||
email: 'jobs@steve.com',
|
||||
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||
staff: true
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'UN_ASSIGN',
|
||||
date: '201504100924',
|
||||
|
|
|
@ -30,6 +30,7 @@ export default {
|
|||
'STAFF': 'Staff',
|
||||
'CUSTOMER': 'Customer',
|
||||
'YES': 'Yes',
|
||||
'NO': 'No',
|
||||
'CANCEL': 'Cancel',
|
||||
'MY_ACCOUNT': 'My Account',
|
||||
'DASHBOARD': 'Dashboard',
|
||||
|
@ -114,6 +115,8 @@ export default {
|
|||
'ADD_DEPARTMENT': 'Add department',
|
||||
'UPDATE_DEPARTMENT': 'Update department',
|
||||
'TRANSFER_TICKETS_TO': 'Transfer tickets to',
|
||||
'COMMENTS': 'Comments',
|
||||
'DELETE_STAFF_MEMBER': 'Delete staff member',
|
||||
|
||||
//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.',
|
||||
|
@ -136,6 +139,7 @@ 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.',
|
||||
'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article for ever.',
|
||||
'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.',
|
||||
'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams.',
|
||||
'EDIT_STAFF_DESCRIPTION': 'Here you can edit information about a staff member.',
|
||||
|
@ -154,8 +158,14 @@ export default {
|
|||
'PASSWORD_NOT_MATCH': 'Password does not match',
|
||||
'INVALID_RECOVER': 'Invalid recover data',
|
||||
'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.',
|
||||
'TICKET_COMMENT_ERROR': 'An error occurred while trying to add the comment.',
|
||||
'NO_PERMISSION': 'You\'ve no permission to access to this page.',
|
||||
'INVALID_USER': 'User id is invalid',
|
||||
'ERROR_RETRIEVING_TICKETS': 'An error occurred while trying to retrieve tickets.',
|
||||
'ERROR_RETRIEVING_USERS': 'An error occurred while trying to retrieve users.',
|
||||
'ERROR_RETRIEVING_BAN_LIST': 'An error occurred while trying to retrieve the list of banned emails.',
|
||||
'ERROR_BANNING_EMAIL': 'An error occurred while trying to ban the email.',
|
||||
'ERROR_RETRIEVING_ARTICLES': 'An error occurred while trying to retrieve articles.',
|
||||
|
||||
//MESSAGES
|
||||
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
||||
|
@ -169,5 +179,10 @@ export default {
|
|||
'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.',
|
||||
'WILL_DELETE_CUSTOM_RESPONSE': 'The custom response will be deleted.',
|
||||
'WILL_DELETE_DEPARTMENT': 'The department will be deleted. All the tickets will be transfer to the department selected.',
|
||||
'NO_STAFF_ASSIGNED': 'No staff member is assigned to this department.'
|
||||
'NO_STAFF_ASSIGNED': 'No staff member is assigned to this department.',
|
||||
'LEVEL_UPDATED': 'Level has been updated successfully.',
|
||||
'DEPARTMENTS_UPDATED': 'Departments have been updated successfully.',
|
||||
'FAILED_EDIT_STAFF': 'An error occurred while trying to edit staff member.',
|
||||
'EMAIL_BANNED_SUCCESSFULLY': 'Email has been banned successfully',
|
||||
'WILL_DELETE_STAFF': 'This staff member will be deleted and all its tickets will be unassigned.'
|
||||
};
|
||||
|
|
|
@ -11,10 +11,15 @@ class AdminDataReducer extends Reducer {
|
|||
customResponsesLoaded: false,
|
||||
myTickets: [],
|
||||
myTicketsLoaded: false,
|
||||
myTicketsError: false,
|
||||
|
||||
newTickets: [],
|
||||
newTicketsLoaded: false,
|
||||
newTicketsError: false,
|
||||
|
||||
allTickets: [],
|
||||
allTicketsLoaded: false
|
||||
allTicketsLoaded: false,
|
||||
allTicketsError: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -22,11 +27,17 @@ class AdminDataReducer extends Reducer {
|
|||
return {
|
||||
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
|
||||
'SESSION_CHECKED': this.onSessionChecked,
|
||||
|
||||
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
|
||||
'MY_TICKETS_REJECTED': this.onMyTicketsRejected,
|
||||
'MY_TICKETS_PENDING': this.onMyTicketsPending,
|
||||
|
||||
'NEW_TICKETS_FULFILLED': this.onNewTicketsRetrieved,
|
||||
'NEW_TICKETS_REJECTED': this.onNewTicketsRejected,
|
||||
'NEW_TICKETS_PENDING': this.onNewTicketsPending,
|
||||
|
||||
'ALL_TICKETS_FULFILLED': this.onAllTicketsRetrieved,
|
||||
'ALL_TICKETS_REJECTED': this.onAllTicketsRejected,
|
||||
'ALL_TICKETS_PENDING': this.onAllTicketsPending
|
||||
};
|
||||
}
|
||||
|
@ -53,26 +64,42 @@ class AdminDataReducer extends Reducer {
|
|||
return _.extend({}, state, {
|
||||
myTickets: payload.data,
|
||||
myTicketsLoaded: true
|
||||
});
|
||||
}
|
||||
|
||||
onMyTicketsRejected(state) {
|
||||
return _.extend({}, state, {
|
||||
myTicketsError: true,
|
||||
myTicketsLoaded: true
|
||||
})
|
||||
}
|
||||
|
||||
onMyTicketsPending(state) {
|
||||
return _.extend({}, state, {
|
||||
myTicketsError: false,
|
||||
myTicketsLoaded: false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onNewTicketsRetrieved(state, payload) {
|
||||
return _.extend({}, state, {
|
||||
newTickets: payload.data,
|
||||
newTicketsLoaded: true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onNewTicketsRejected(state) {
|
||||
return _.extend({}, state, {
|
||||
newTicketsError: true,
|
||||
newTicketsLoaded: false
|
||||
});
|
||||
}
|
||||
|
||||
onNewTicketsPending(state) {
|
||||
return _.extend({}, state, {
|
||||
newTicketsError: false,
|
||||
newTicketsLoaded: false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onAllTicketsRetrieved(state, payload) {
|
||||
|
@ -80,13 +107,21 @@ class AdminDataReducer extends Reducer {
|
|||
allTickets: payload.data.tickets,
|
||||
allTicketsPages: payload.data.pages,
|
||||
allTicketsLoaded: true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onAllTicketsRejected(state) {
|
||||
return _.extend({}, state, {
|
||||
allTicketsError: false,
|
||||
allTicketsLoaded: false
|
||||
});
|
||||
}
|
||||
|
||||
onAllTicketsPending(state) {
|
||||
return _.extend({}, state, {
|
||||
allTicketsError: false,
|
||||
allTicketsLoaded: false
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ class ArticlesReducer extends Reducer {
|
|||
return {
|
||||
retrieved: false,
|
||||
loading: true,
|
||||
errored: false,
|
||||
topics: []
|
||||
};
|
||||
}
|
||||
|
@ -16,6 +17,7 @@ class ArticlesReducer extends Reducer {
|
|||
getTypeHandlers() {
|
||||
return {
|
||||
'GET_ARTICLES_FULFILLED': this.onArticlesRetrieved,
|
||||
'GET_ARTICLES_REJECTED': this.onArticlesRejected,
|
||||
'INIT_ARTICLES': this.onInitArticles
|
||||
};
|
||||
}
|
||||
|
@ -26,10 +28,19 @@ class ArticlesReducer extends Reducer {
|
|||
return _.extend({}, state, {
|
||||
retrieved: true,
|
||||
loading: false,
|
||||
errored: false,
|
||||
topics: payload.data
|
||||
});
|
||||
}
|
||||
|
||||
onArticlesRejected(state) {
|
||||
return _.extend({}, state, {
|
||||
retrieved: true,
|
||||
loading: false,
|
||||
errored: true
|
||||
});
|
||||
}
|
||||
|
||||
onInitArticles(state) {
|
||||
let topics = SessionStore.getItem('topics');
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"require": {
|
||||
"slim/slim": "~2.0",
|
||||
"gabordemooij/redbean": "~4.2",
|
||||
"respect/validation": "^1.1",
|
||||
"phpmailer/phpmailer": "^5.2",
|
||||
"google/recaptcha": "~1.1"
|
||||
"google/recaptcha": "~1.1",
|
||||
"gabordemooij/redbean": "^4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.0.*"
|
||||
|
|
|
@ -21,6 +21,11 @@ class DeleteStaffController extends Controller {
|
|||
$staffId = Controller::request('staffId');
|
||||
$staff = Staff::getDataStore($staffId);
|
||||
|
||||
if($staffId === Controller::getLoggedUser()->id) {
|
||||
Response::respondError(ERRORS::INVALID_STAFF);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($staff->sharedTicketList as $ticket) {
|
||||
$ticket->owner = null;
|
||||
$ticket->true = true;
|
||||
|
@ -31,7 +36,6 @@ class DeleteStaffController extends Controller {
|
|||
$department->owners--;
|
||||
$department->store();
|
||||
}
|
||||
|
||||
|
||||
$staff->delete();
|
||||
Response::respondSuccess();
|
||||
|
|
|
@ -4,8 +4,7 @@ use Respect\Validation\Validator as DataValidator;
|
|||
class EditStaffController extends Controller {
|
||||
const PATH = '/edit';
|
||||
|
||||
private $staffRow;
|
||||
private $staffId;
|
||||
private $staffInstance;
|
||||
|
||||
public function validations() {
|
||||
return [
|
||||
|
@ -15,14 +14,14 @@ class EditStaffController extends Controller {
|
|||
}
|
||||
|
||||
public function handler() {
|
||||
$this->staffId = Controller::request('staffId');
|
||||
$staffId = Controller::request('staffId');
|
||||
|
||||
if(!$this->staffId) {
|
||||
$this->staffRow = Controller::getLoggedUser();
|
||||
if(!$staffId) {
|
||||
$this->staffInstance = Controller::getLoggedUser();
|
||||
} else if(Controller::isStaffLogged(3)) {
|
||||
$this->staffRow = Staff::getDataStore($this->staffId, 'id');
|
||||
$this->staffInstance = Staff::getDataStore($staffId, 'id');
|
||||
|
||||
if($this->staffRow->isNull()) {
|
||||
if($this->staffInstance->isNull()) {
|
||||
Response::respondError(ERRORS::INVALID_STAFF);
|
||||
return;
|
||||
}
|
||||
|
@ -39,29 +38,29 @@ class EditStaffController extends Controller {
|
|||
Response::respondSuccess();
|
||||
}
|
||||
|
||||
public function editInformation() {
|
||||
private function editInformation() {
|
||||
|
||||
if(Controller::request('email')) {
|
||||
$this->staffRow->email = Controller::request('email');
|
||||
$this->staffInstance->email = Controller::request('email');
|
||||
}
|
||||
|
||||
if(Controller::request('password')) {
|
||||
$this->staffRow->password = Hashing::hashPassword(Controller::request('password'));
|
||||
$this->staffInstance->password = Hashing::hashPassword(Controller::request('password'));
|
||||
}
|
||||
|
||||
if(Controller::request('level') && Controller::isStaffLogged(3)) {
|
||||
$this->staffRow->level = Controller::request('level');
|
||||
if(Controller::request('level') && Controller::isStaffLogged(3) && Controller::request('staffId') !== Controller::getLoggedUser()->id) {
|
||||
$this->staffInstance->level = Controller::request('level');
|
||||
}
|
||||
|
||||
if(Controller::request('departments') && Controller::isStaffLogged(3)) {
|
||||
$this->staffRow->sharedDepartmentList = $this->getDepartmentList();
|
||||
$this->staffInstance->sharedDepartmentList = $this->getDepartmentList();
|
||||
}
|
||||
|
||||
$this->staffRow->store();
|
||||
$this->staffInstance->store();
|
||||
}
|
||||
|
||||
|
||||
public function getDepartmentList() {
|
||||
private function getDepartmentList() {
|
||||
$listDepartments = new DataStoreList();
|
||||
$departmentIds = json_decode(Controller::request('departments'));
|
||||
|
||||
|
@ -73,8 +72,8 @@ class EditStaffController extends Controller {
|
|||
return $listDepartments;
|
||||
}
|
||||
|
||||
public function updateDepartmentsOwners() {
|
||||
$list1 = $this->staffRow->sharedDepartmentList;
|
||||
private function updateDepartmentsOwners() {
|
||||
$list1 = $this->staffInstance->sharedDepartmentList;
|
||||
$list2 = $this->getDepartmentList();
|
||||
|
||||
foreach ($list1 as $department1) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class CommentController extends Controller {
|
|||
'permission' => 'user',
|
||||
'requestData' => [
|
||||
'content' => [
|
||||
'validation' => DataValidator::length(20, 500),
|
||||
'validation' => DataValidator::length(20, 5000),
|
||||
'error' => ERRORS::INVALID_CONTENT
|
||||
],
|
||||
'ticketNumber' => [
|
||||
|
|
|
@ -20,7 +20,7 @@ class CreateController extends Controller {
|
|||
'error' => ERRORS::INVALID_TITLE
|
||||
],
|
||||
'content' => [
|
||||
'validation' => DataValidator::length(10, 500),
|
||||
'validation' => DataValidator::length(10, 5000),
|
||||
'error' => ERRORS::INVALID_CONTENT
|
||||
],
|
||||
'departmentId' => [
|
||||
|
|
|
@ -32,7 +32,7 @@ describe '/ticket/comment/' do
|
|||
|
||||
it 'should fail if content is very long' do
|
||||
long_text = ''
|
||||
600.times {long_text << 'a'}
|
||||
6000.times {long_text << 'a'}
|
||||
|
||||
result = request('/ticket/comment', {
|
||||
content: long_text,
|
||||
|
|
|
@ -62,7 +62,7 @@ describe '/ticket/create' do
|
|||
|
||||
it 'should fail if content is very long' do
|
||||
long_text = ''
|
||||
600.times {long_text << 'a'}
|
||||
6000.times {long_text << 'a'}
|
||||
|
||||
result = request('/ticket/create',{
|
||||
title: 'Winter is coming',
|
||||
|
|
Loading…
Reference in New Issue