mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-29 16:54:53 +02:00
Merge branch 'master' into OS-78-My-tickets-view
# Conflicts: # client/src/data/languages/en.js # client/src/reducers/admin-data-reducer.js
This commit is contained in:
commit
22cd244846
@ -1,9 +1,8 @@
|
|||||||
import API from 'lib-app/api-call';
|
import API from 'lib-app/api-call';
|
||||||
|
import AdminDataActions from 'actions/admin-data-actions';
|
||||||
import sessionStore from 'lib-app/session-store';
|
import sessionStore from 'lib-app/session-store';
|
||||||
import store from 'app/store';
|
import store from 'app/store';
|
||||||
|
|
||||||
import ConfigActions from 'actions/config-actions';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
login(loginData) {
|
login(loginData) {
|
||||||
return {
|
return {
|
||||||
@ -13,6 +12,10 @@ export default {
|
|||||||
data: loginData
|
data: loginData
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
|
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
|
||||||
|
|
||||||
|
if(result.data.staff) {
|
||||||
|
store.dispatch(AdminDataActions.retrieveCustomResponses());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
|
|
||||||
class TicketAction extends React.Component {
|
class TicketEvent extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: React.PropTypes.oneOf([
|
type: React.PropTypes.oneOf([
|
||||||
'COMMENT',
|
'COMMENT',
|
||||||
@ -17,7 +17,7 @@ class TicketAction extends React.Component {
|
|||||||
]),
|
]),
|
||||||
author: React.PropTypes.object,
|
author: React.PropTypes.object,
|
||||||
content: React.PropTypes.string,
|
content: React.PropTypes.string,
|
||||||
date: React.PropTypes.number
|
date: React.PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -31,12 +31,12 @@ class TicketAction extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.getClass()}>
|
<div className={this.getClass()}>
|
||||||
<span className="ticket-action__connector" />
|
<span className="ticket-event__connector" />
|
||||||
<div className="col-md-1">
|
<div className="col-md-1">
|
||||||
{iconNode}
|
{iconNode}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-11">
|
<div className="col-md-11">
|
||||||
{this.renderActionDescription()}
|
{this.renderEventDescription()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -44,21 +44,21 @@ class TicketAction extends React.Component {
|
|||||||
|
|
||||||
renderStaffPic() {
|
renderStaffPic() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__staff-pic">
|
<div className="ticket-event__staff-pic">
|
||||||
<img src={this.props.author.profilePic} className="ticket-action__staff-pic-img" />
|
<img src={this.props.author.profilePic} className="ticket-event__staff-pic-img" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIcon() {
|
renderIcon() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__icon">
|
<div className="ticket-event__icon">
|
||||||
<Icon {...this.getIconProps()}/>
|
<Icon {...this.getIconProps()}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActionDescription() {
|
renderEventDescription() {
|
||||||
const renders = {
|
const renders = {
|
||||||
'COMMENT': this.renderComment.bind(this),
|
'COMMENT': this.renderComment.bind(this),
|
||||||
'ASSIGN': this.renderAssignment.bind(this),
|
'ASSIGN': this.renderAssignment.bind(this),
|
||||||
@ -74,14 +74,14 @@ class TicketAction extends React.Component {
|
|||||||
|
|
||||||
renderComment() {
|
renderComment() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__comment">
|
<div className="ticket-event__comment">
|
||||||
<span className="ticket-action__comment-pointer" />
|
<span className="ticket-event__comment-pointer" />
|
||||||
<div className="ticket-action__comment-author">
|
<div className="ticket-event__comment-author">
|
||||||
<span className="ticket-action__comment-author-name">{this.props.author.name}</span>
|
<span className="ticket-event__comment-author-name">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__comment-author-type">({i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')})</span>
|
<span className="ticket-event__comment-author-type">({i18n((this.props.author.staff) ? 'STAFF' : 'CUSTOMER')})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-action__comment-date">{this.props.date}</div>
|
<div className="ticket-event__comment-date">{this.props.date}</div>
|
||||||
<div className="ticket-action__comment-content" dangerouslySetInnerHTML={{__html: this.props.content}}></div>
|
<div className="ticket-event__comment-content" dangerouslySetInnerHTML={{__html: this.props.content}}></div>
|
||||||
{this.renderFileRow(this.props.file)}
|
{this.renderFileRow(this.props.file)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -89,62 +89,62 @@ class TicketAction extends React.Component {
|
|||||||
|
|
||||||
renderAssignment() {
|
renderAssignment() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__circled">
|
<div className="ticket-event__circled">
|
||||||
<span className="ticket-action__circled-author">{this.props.author.name}</span>
|
<span className="ticket-event__circled-author">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__circled-text"> assigned this ticket</span>
|
<span className="ticket-event__circled-text"> assigned this ticket</span>
|
||||||
<span className="ticket-action__circled-date"> on {this.props.date}</span>
|
<span className="ticket-event__circled-date"> on {this.props.date}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUnAssignment() {
|
renderUnAssignment() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__circled">
|
<div className="ticket-event__circled">
|
||||||
<span className="ticket-action__circled-author">{this.props.author.name}</span>
|
<span className="ticket-event__circled-author">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__circled-text"> unassigned this ticket</span>
|
<span className="ticket-event__circled-text"> unassigned this ticket</span>
|
||||||
<span className="ticket-action__circled-date"> on {this.props.date}</span>
|
<span className="ticket-event__circled-date"> on {this.props.date}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderClosed() {
|
renderClosed() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__circled">
|
<div className="ticket-event__circled">
|
||||||
<span className="ticket-action__circled-author">{this.props.author.name}</span>
|
<span className="ticket-event__circled-author">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__circled-text"> closed this ticket</span>
|
<span className="ticket-event__circled-text"> closed this ticket</span>
|
||||||
<span className="ticket-action__circled-date"> on {this.props.date}</span>
|
<span className="ticket-event__circled-date"> on {this.props.date}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderReOpened() {
|
renderReOpened() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__circled">
|
<div className="ticket-event__circled">
|
||||||
<span className="ticket-action__circled-author">{this.props.author.name}</span>
|
<span className="ticket-event__circled-author">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__circled-text"> reopen this ticket</span>
|
<span className="ticket-event__circled-text"> reopen this ticket</span>
|
||||||
<span className="ticket-action__circled-date"> on {this.props.date}</span>
|
<span className="ticket-event__circled-date"> on {this.props.date}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDepartmentChange() {
|
renderDepartmentChange() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__circled">
|
<div className="ticket-event__circled">
|
||||||
<span className="ticket-action__circled-author">{this.props.author.name}</span>
|
<span className="ticket-event__circled-author">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__circled-text"> change department to</span>
|
<span className="ticket-event__circled-text"> change department to</span>
|
||||||
<span className="ticket-action__circled-indication"> {this.props.content}</span>
|
<span className="ticket-event__circled-indication"> {this.props.content}</span>
|
||||||
<span className="ticket-action__circled-date"> on {this.props.date}</span>
|
<span className="ticket-event__circled-date"> on {this.props.date}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPriorityChange() {
|
renderPriorityChange() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-action__circled">
|
<div className="ticket-event__circled">
|
||||||
<span className="ticket-action__circled-author">{this.props.author.name}</span>
|
<span className="ticket-event__circled-author">{this.props.author.name}</span>
|
||||||
<span className="ticket-action__circled-text"> change priority to</span>
|
<span className="ticket-event__circled-text"> change priority to</span>
|
||||||
<span className="ticket-action__circled-indication"> {this.props.content}</span>
|
<span className="ticket-event__circled-indication"> {this.props.content}</span>
|
||||||
<span className="ticket-action__circled-date"> on {this.props.date}</span>
|
<span className="ticket-event__circled-date"> on {this.props.date}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -177,14 +177,14 @@ class TicketAction extends React.Component {
|
|||||||
};
|
};
|
||||||
const classes = {
|
const classes = {
|
||||||
'row': true,
|
'row': true,
|
||||||
'ticket-action': true,
|
'ticket-event': true,
|
||||||
'ticket-action_staff': this.props.author && this.props.author.staff,
|
'ticket-event_staff': this.props.author && this.props.author.staff,
|
||||||
'ticket-action_circled': circledTypes[this.props.type],
|
'ticket-event_circled': circledTypes[this.props.type],
|
||||||
'ticket-action_unassignment': this.props.type === 'UN_ASSIGN',
|
'ticket-event_unassignment': this.props.type === 'UN_ASSIGN',
|
||||||
'ticket-action_close': this.props.type === 'CLOSE',
|
'ticket-event_close': this.props.type === 'CLOSE',
|
||||||
'ticket-action_reopen': this.props.type === 'RE_OPEN',
|
'ticket-event_reopen': this.props.type === 'RE_OPEN',
|
||||||
'ticket-action_department': this.props.type === 'DEPARTMENT_CHANGED',
|
'ticket-event_department': this.props.type === 'DEPARTMENT_CHANGED',
|
||||||
'ticket-action_priority': this.props.type === 'PRIORITY_CHANGED'
|
'ticket-event_priority': this.props.type === 'PRIORITY_CHANGED'
|
||||||
};
|
};
|
||||||
|
|
||||||
return classNames(classes);
|
return classNames(classes);
|
||||||
@ -225,4 +225,4 @@ class TicketAction extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TicketAction;
|
export default TicketEvent;
|
@ -1,6 +1,6 @@
|
|||||||
@import "../scss/vars";
|
@import "../scss/vars";
|
||||||
|
|
||||||
.ticket-action {
|
.ticket-event {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -91,11 +91,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&_staff {
|
&_staff {
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
background-color: $primary-blue;
|
background-color: $primary-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-action__comment-author-type {
|
.ticket-event__comment-author-type {
|
||||||
color: $primary-blue;
|
color: $primary-blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,11 +104,11 @@
|
|||||||
margin-top: 35px;
|
margin-top: 35px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
|
||||||
.ticket-action__connector {
|
.ticket-event__connector {
|
||||||
top: 28px;
|
top: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: $primary-black;
|
color: $primary-black;
|
||||||
border: 3px solid $light-grey;
|
border: 3px solid $light-grey;
|
||||||
@ -121,48 +121,48 @@
|
|||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-action__circled {
|
.ticket-event__circled {
|
||||||
color: $primary-black;
|
color: $primary-black;
|
||||||
font-size: $font-size--sm;
|
font-size: $font-size--sm;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-action__circled-author {
|
.ticket-event__circled-author {
|
||||||
color: $secondary-blue;
|
color: $secondary-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-action__circled-indication {
|
.ticket-event__circled-indication {
|
||||||
color: $primary-green;
|
color: $primary-green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_unassignment {
|
&_unassignment {
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_close {
|
&_close {
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
padding-left: 9px;
|
padding-left: 9px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_reopen {
|
&_reopen {
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
padding-left: 9px;
|
padding-left: 9px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_department {
|
&_department {
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_priority {
|
&_priority {
|
||||||
.ticket-action__icon {
|
.ticket-event__icon {
|
||||||
padding-left: 11px;
|
padding-left: 11px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
@ -1,22 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import RichTextEditor from 'react-rte-browserify';
|
||||||
|
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
import API from 'lib-app/api-call';
|
import API from 'lib-app/api-call';
|
||||||
import store from 'app/store';
|
import SessionStore from 'lib-app/session-store';
|
||||||
import SessionActions from 'actions/session-actions';
|
|
||||||
|
|
||||||
import TicketAction from 'app-components/ticket-action';
|
import TicketEvent from 'app-components/ticket-event';
|
||||||
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
import Form from 'core-components/form';
|
import Form from 'core-components/form';
|
||||||
import FormField from 'core-components/form-field';
|
import FormField from 'core-components/form-field';
|
||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
|
import DropDown from 'core-components/drop-down';
|
||||||
|
import Button from 'core-components/button';
|
||||||
|
|
||||||
class TicketViewer extends React.Component {
|
class TicketViewer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ticket: React.PropTypes.object
|
ticket: React.PropTypes.object,
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
editable: React.PropTypes.bool,
|
||||||
|
customResponses: React.PropTypes.array,
|
||||||
|
assignmentAllowed: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
editable: false,
|
||||||
ticket: {
|
ticket: {
|
||||||
author: {},
|
author: {},
|
||||||
department: {},
|
department: {},
|
||||||
@ -24,14 +32,11 @@ class TicketViewer extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
loading: false,
|
||||||
|
commentValue: RichTextEditor.createEmptyValue(),
|
||||||
this.state = {
|
commentEdited: false
|
||||||
loading: false
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const ticket = this.props.ticket;
|
const ticket = this.props.ticket;
|
||||||
@ -42,6 +47,92 @@ class TicketViewer extends React.Component {
|
|||||||
<span className="ticket-viewer__number">#{ticket.ticketNumber}</span>
|
<span className="ticket-viewer__number">#{ticket.ticketNumber}</span>
|
||||||
<span className="ticket-viewer__title">{ticket.title}</span>
|
<span className="ticket-viewer__title">{ticket.title}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
|
||||||
|
<div className="ticket-viewer__content">
|
||||||
|
<TicketEvent type="COMMENT" author={ticket.author} content={ticket.content} date={ticket.date} file={ticket.file}/>
|
||||||
|
</div>
|
||||||
|
<div className="ticket-viewer__comments">
|
||||||
|
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
|
||||||
|
</div>
|
||||||
|
<div className="ticket-viewer__response">
|
||||||
|
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
|
||||||
|
{this.renderCustomResponses()}
|
||||||
|
<div className="ticket-viewer__response-field row">
|
||||||
|
<Form {...this.getCommentFormProps()}>
|
||||||
|
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
|
||||||
|
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditableHeaders() {
|
||||||
|
const ticket = this.props.ticket;
|
||||||
|
const departments = SessionStore.getDepartments();
|
||||||
|
const priorities = {
|
||||||
|
'low': 0,
|
||||||
|
'medium': 1,
|
||||||
|
'high': 2
|
||||||
|
};
|
||||||
|
const priorityList = [
|
||||||
|
{content: i18n('LOW')},
|
||||||
|
{content: i18n('MEDIUM')},
|
||||||
|
{content: i18n('HIGH')}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ticket-viewer__headers">
|
||||||
|
<div className="ticket-viewer__info-row-header row">
|
||||||
|
<div className="col-md-4">{i18n('DEPARTMENT')}</div>
|
||||||
|
<div className=" col-md-4">{i18n('AUTHOR')}</div>
|
||||||
|
<div className="col-md-4">{i18n('DATE')}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ticket-viewer__info-row-values row">
|
||||||
|
<div className="col-md-4">
|
||||||
|
<DropDown className="ticket-viewer__editable-dropdown"
|
||||||
|
items={departments.map((department) => {return {content: department.name}})}
|
||||||
|
selectedIndex={_.findIndex(departments, {id: this.props.ticket.department.id})}
|
||||||
|
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4">{ticket.author.name}</div>
|
||||||
|
<div className="col-md-4">{ticket.date}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ticket-viewer__info-row-header row">
|
||||||
|
<div className="col-md-4">{i18n('PRIORITY')}</div>
|
||||||
|
<div className="col-md-4">{i18n('OWNED')}</div>
|
||||||
|
<div className="col-md-4">{i18n('STATUS')}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ticket-viewer__info-row-values row">
|
||||||
|
<div className="col-md-4">
|
||||||
|
<DropDown className="ticket-viewer__editable-dropdown" items={priorityList} selectedIndex={priorities[ticket.priority]} onChange={this.onPriorityDropdownChanged.bind(this)} />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4">
|
||||||
|
<Button type={(ticket.owner) ? 'primary' : 'secondary'} size="extra-small" onClick={this.onAssignClick.bind(this)}>
|
||||||
|
{i18n(ticket.owner ? 'UN_ASSIGN' : 'ASSIGN_TO_ME')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4">
|
||||||
|
<Button type={(ticket.closed) ? 'secondary' : 'primary'} size="extra-small" onClick={this.onCloseClick.bind(this)}>
|
||||||
|
{i18n(ticket.closed ? 'RE_OPEN' : 'CLOSE')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHeaders() {
|
||||||
|
const ticket = this.props.ticket;
|
||||||
|
const priorities = {
|
||||||
|
'low': 'LOW',
|
||||||
|
'medium': 'MEDIUM',
|
||||||
|
'high': 'HIGH'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ticket-viewer__headers">
|
||||||
<div className="ticket-viewer__info-row-header row">
|
<div className="ticket-viewer__info-row-header row">
|
||||||
<div className="ticket-viewer__department col-md-4">{i18n('DEPARTMENT')}</div>
|
<div className="ticket-viewer__department col-md-4">{i18n('DEPARTMENT')}</div>
|
||||||
<div className="ticket-viewer__author col-md-4">{i18n('AUTHOR')}</div>
|
<div className="ticket-viewer__author col-md-4">{i18n('AUTHOR')}</div>
|
||||||
@ -52,31 +143,157 @@ class TicketViewer extends React.Component {
|
|||||||
<div className="ticket-viewer__author col-md-4">{ticket.author.name}</div>
|
<div className="ticket-viewer__author col-md-4">{ticket.author.name}</div>
|
||||||
<div className="ticket-viewer__date col-md-4">{ticket.date}</div>
|
<div className="ticket-viewer__date col-md-4">{ticket.date}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__content">
|
<div className="ticket-viewer__info-row-header row">
|
||||||
<TicketAction type="COMMENT" author={ticket.author} content={ticket.content} date={ticket.date} file={ticket.file}/>
|
<div className="ticket-viewer__department col-md-4">{i18n('PRIORITY')}</div>
|
||||||
|
<div className="ticket-viewer__author col-md-4">{i18n('OWNER')}</div>
|
||||||
|
<div className="ticket-viewer__date col-md-4">{i18n('STATUS')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ticket-viewer__comments">
|
<div className="ticket-viewer__info-row-values row">
|
||||||
{ticket.actions && ticket.actions.map(this.renderAction.bind(this))}
|
<div className="col-md-4">
|
||||||
</div>
|
{i18n(priorities[this.props.ticket.priority || 'low'])}
|
||||||
<div className="ticket-viewer__response">
|
</div>
|
||||||
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
|
<div className="col-md-4">
|
||||||
<div className="ticket-viewer__response-field row">
|
{(ticket.owner) ? ticket.owner.name : i18n('NONE')}
|
||||||
<Form onSubmit={this.onSubmit.bind(this)} loading={this.state.loading}>
|
</div>
|
||||||
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
|
<div className="col-md-4">
|
||||||
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
{this.renderOwnerNode()}
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAction(action, index) {
|
renderOwnerNode() {
|
||||||
|
let ownerNode = null;
|
||||||
|
|
||||||
|
if (this.props.assignmentAllowed && _.isEmpty(this.props.ticket.owner)) {
|
||||||
|
ownerNode = (
|
||||||
|
<Button type="secondary" size="extra-small" onClick={this.onAssignClick.bind(this)}>
|
||||||
|
{i18n('ASSIGN_TO_ME')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ownerNode = i18n((this.props.closed) ? 'CLOSED' : 'OPENED');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ownerNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTicketEvent(options, index) {
|
||||||
return (
|
return (
|
||||||
<TicketAction {...action} key={index} />
|
<TicketEvent {...options} key={index} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCustomResponses() {
|
||||||
|
let customResponsesNode = null;
|
||||||
|
|
||||||
|
if (this.props.customResponses && this.props.editable) {
|
||||||
|
let customResponses = this.props.customResponses.map((customResponse) => {
|
||||||
|
return {
|
||||||
|
content: customResponse.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
customResponses.unshift({
|
||||||
|
content: i18n('SELECT_CUSTOM_RESPONSE')
|
||||||
|
});
|
||||||
|
|
||||||
|
customResponsesNode = (
|
||||||
|
<div className="ticket-viewer__response-custom row">
|
||||||
|
<DropDown items={customResponses} size="medium" onChange={this.onCustomResponsesChanged.bind(this)}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return customResponsesNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommentFormProps() {
|
||||||
|
return {
|
||||||
|
onSubmit: this.onSubmit.bind(this),
|
||||||
|
loading: this.state.loading,
|
||||||
|
onChange: (formState) => {this.setState({
|
||||||
|
commentValue: formState.content,
|
||||||
|
commentEdited: true
|
||||||
|
})},
|
||||||
|
values: {
|
||||||
|
'content': this.state.commentValue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onDepartmentDropdownChanged(event) {
|
||||||
|
AreYouSure.openModal(null, this.changeDepartment.bind(this, event.index));
|
||||||
|
}
|
||||||
|
|
||||||
|
onPriorityDropdownChanged(event) {
|
||||||
|
AreYouSure.openModal(null, this.changePriority.bind(this, event.index));
|
||||||
|
}
|
||||||
|
|
||||||
|
onAssignClick() {
|
||||||
|
API.call({
|
||||||
|
path: (this.props.ticket.owner) ? '/staff/un-assign-ticket' : '/staff/assign-ticket',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.ticket.ticketNumber
|
||||||
|
}
|
||||||
|
}).then(this.onTicketModification.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseClick() {
|
||||||
|
AreYouSure.openModal(null, this.toggleClose.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClose() {
|
||||||
|
API.call({
|
||||||
|
path: (this.props.ticket.closed) ? '/ticket/re-open' : '/ticket/close',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.ticket.ticketNumber
|
||||||
|
}
|
||||||
|
}).then(this.onTicketModification.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDepartment(index) {
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/change-department',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.ticket.ticketNumber,
|
||||||
|
departmentId: SessionStore.getDepartments()[index].id
|
||||||
|
}
|
||||||
|
}).then(this.onTicketModification.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
changePriority(index) {
|
||||||
|
const priorities = [
|
||||||
|
'low',
|
||||||
|
'medium',
|
||||||
|
'high'
|
||||||
|
];
|
||||||
|
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/change-priority',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.ticket.ticketNumber,
|
||||||
|
priority: priorities[index]
|
||||||
|
}
|
||||||
|
}).then(this.onTicketModification.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onCustomResponsesChanged({index}) {
|
||||||
|
let replaceContentWithCustomResponse = () => {
|
||||||
|
this.setState({
|
||||||
|
commentValue: RichTextEditor.createValueFromString(this.props.customResponses[index-1].content || '', 'html'),
|
||||||
|
commentEdited: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.commentEdited && index) {
|
||||||
|
AreYouSure.openModal(null, replaceContentWithCustomResponse);
|
||||||
|
} else {
|
||||||
|
replaceContentWithCustomResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(formState) {
|
onSubmit(formState) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
@ -94,8 +311,8 @@ class TicketViewer extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
store.dispatch(SessionActions.getUserData());
|
this.onTicketModification();
|
||||||
}
|
}
|
||||||
|
|
||||||
onCommentFail() {
|
onCommentFail() {
|
||||||
@ -103,6 +320,12 @@ class TicketViewer extends React.Component {
|
|||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTicketModification() {
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TicketViewer;
|
export default TicketViewer;
|
@ -28,18 +28,15 @@
|
|||||||
&__info-row-values {
|
&__info-row-values {
|
||||||
background-color: $light-grey;
|
background-color: $light-grey;
|
||||||
color: $secondary-blue;
|
color: $secondary-blue;
|
||||||
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__editable-dropdown {
|
||||||
|
margin: auto;
|
||||||
}
|
|
||||||
|
|
||||||
&__author {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&__department {
|
|
||||||
|
|
||||||
|
.drop-down__current-item {
|
||||||
|
background-color: $very-light-grey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
@ -75,5 +72,11 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-custom {
|
||||||
|
background-color: $very-light-grey;
|
||||||
|
padding: 20px 0 0 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -84,7 +84,7 @@ export default (
|
|||||||
<Route path="new-tickets" component={AdminPanelNewTickets} />
|
<Route path="new-tickets" component={AdminPanelNewTickets} />
|
||||||
<Route path="all-tickets" component={AdminPanelAllTickets} />
|
<Route path="all-tickets" component={AdminPanelAllTickets} />
|
||||||
<Route path="custom-responses" component={AdminPanelCustomResponses} />
|
<Route path="custom-responses" component={AdminPanelCustomResponses} />
|
||||||
<Route path="view-ticket" component={AdminPanelViewTicket} />
|
<Route path="view-ticket/:ticketNumber" component={AdminPanelViewTicket} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="users">
|
<Route path="users">
|
||||||
|
@ -1,14 +1,99 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
import i18n from 'lib-app/i18n';
|
||||||
|
import SessionStore from 'lib-app/session-store';
|
||||||
|
|
||||||
|
import TicketViewer from 'app-components/ticket-viewer';
|
||||||
|
import Loading from 'core-components/loading';
|
||||||
|
import Header from 'core-components/header';
|
||||||
|
|
||||||
class AdminPanelViewTicket extends React.Component {
|
class AdminPanelViewTicket extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
loading: true,
|
||||||
|
ticket: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.retrieveTicket();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="admin-panel-view-ticket">
|
||||||
/admin/panel/tickets/view-ticket
|
<Header title={i18n('VIEW_TICKET')} description={i18n('TICKET_VIEW_DESCRIPTION')} />
|
||||||
|
{(this.state.loading) ? this.renderLoading() : this.renderView()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderLoading() {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-view-ticket__loading">
|
||||||
|
<Loading size="large" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
renderView() {
|
||||||
|
return (_.isEmpty(this.state.ticket)) ? this.renderNoPermissionError() : this.renderTicketView();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNoPermissionError() {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-view-ticket__error">
|
||||||
|
{i18n('NO_PERMISSION')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTicketView() {
|
||||||
|
return (
|
||||||
|
<div className="admin-panel-view-ticket__ticket-view">
|
||||||
|
<TicketViewer {...this.getTicketViewProps()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTicketViewProps() {
|
||||||
|
return {
|
||||||
|
ticket: this.state.ticket,
|
||||||
|
onChange: this.retrieveTicket.bind(this),
|
||||||
|
assignmentAllowed: true,
|
||||||
|
customResponses: this.props.customResponses,
|
||||||
|
editable: _.get(this.state.ticket, 'owner.id') === SessionStore.getUserData().id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveTicket() {
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/get',
|
||||||
|
date: {
|
||||||
|
ticketNumber: this.props.params.ticketNumber
|
||||||
|
}
|
||||||
|
}).then(this.onRetrieveSuccess.bind(this)).catch(this.onRetrieveFail.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
onRetrieveSuccess(result) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
ticket: result.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRetrieveFail() {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
ticket: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminPanelViewTicket;
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
customResponses: store.adminData.customResponses
|
||||||
|
};
|
||||||
|
})(AdminPanelViewTicket);
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
@import '../../../../scss/vars';
|
||||||
|
|
||||||
|
.admin-panel-view-ticket {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
background-color: $grey;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ticket-view {
|
||||||
|
margin: 20px 30px;
|
||||||
|
}
|
||||||
|
}
|
@ -42,8 +42,8 @@ class CreateTicketForm extends React.Component {
|
|||||||
{(!this.props.userLogged) ? this.renderEmailAndName() : null}
|
{(!this.props.userLogged) ? this.renderEmailAndName() : null}
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<FormField className="col-md-7" label="Title" name="title" validation="TITLE" required field="input" fieldProps={{size: 'large'}}/>
|
<FormField className="col-md-7" label="Title" name="title" validation="TITLE" required field="input" fieldProps={{size: 'large'}}/>
|
||||||
<FormField className="col-md-5" label="Department" name="departmentId" field="select" fieldProps={{
|
<FormField className="col-md-5" label="Department" name="departmentIndex" field="select" fieldProps={{
|
||||||
items: SessionStore.getDepartments().map((department) => {return {content: department}}),
|
items: SessionStore.getDepartments().map((department) => {return {content: department.name}}),
|
||||||
size: 'medium'
|
size: 'medium'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
@ -92,7 +92,7 @@ class CreateTicketForm extends React.Component {
|
|||||||
API.call({
|
API.call({
|
||||||
path: '/ticket/create',
|
path: '/ticket/create',
|
||||||
data: _.extend({}, formState, {
|
data: _.extend({}, formState, {
|
||||||
departmentId: formState.departmentId + 1
|
departmentId: SessionStore.getDepartments()[formState.departmentIndex].id
|
||||||
})
|
})
|
||||||
}).then(this.onTicketSuccess.bind(this)).catch(this.onTicketFail.bind(this));
|
}).then(this.onTicketSuccess.bind(this)).catch(this.onTicketFail.bind(this));
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import SessionActions from 'actions/session-actions';
|
||||||
import TicketViewer from 'app-components/ticket-viewer';
|
import TicketViewer from 'app-components/ticket-viewer';
|
||||||
|
|
||||||
class DashboardTicketPage extends React.Component {
|
class DashboardTicketPage extends React.Component {
|
||||||
@ -13,7 +14,7 @@ class DashboardTicketPage extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-ticket-page">
|
<div className="dashboard-ticket-page">
|
||||||
<TicketViewer ticket={this.getTicketData()} />
|
<TicketViewer ticket={this.getTicketData()} onChange={this.retrieveUserData.bind(this)}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -21,6 +22,10 @@ class DashboardTicketPage extends React.Component {
|
|||||||
getTicketData() {
|
getTicketData() {
|
||||||
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber});
|
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retrieveUserData() {
|
||||||
|
this.props.dispatch(SessionActions.getUserData());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((store) => {
|
export default connect((store) => {
|
||||||
|
@ -18,6 +18,7 @@ class Button extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
size: React.PropTypes.oneOf([
|
size: React.PropTypes.oneOf([
|
||||||
|
'extra-small',
|
||||||
'small',
|
'small',
|
||||||
'medium',
|
'medium',
|
||||||
'large',
|
'large',
|
||||||
@ -76,6 +77,7 @@ class Button extends React.Component {
|
|||||||
'button_clean': (this.props.type === 'clean'),
|
'button_clean': (this.props.type === 'clean'),
|
||||||
'button_link': (this.props.type === 'link'),
|
'button_link': (this.props.type === 'link'),
|
||||||
|
|
||||||
|
'button_extra-small': (this.props.size === 'extra-small'),
|
||||||
'button_small': (this.props.size === 'small'),
|
'button_small': (this.props.size === 'small'),
|
||||||
'button_medium': (this.props.size === 'medium'),
|
'button_medium': (this.props.size === 'medium'),
|
||||||
'button_large': (this.props.size === 'large'),
|
'button_large': (this.props.size === 'large'),
|
||||||
|
@ -47,6 +47,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_extra-small {
|
||||||
|
text-transform: none;
|
||||||
|
width: 130px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
&_small {
|
&_small {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 47px;
|
height: 47px;
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
&__list-container {
|
&__list-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
z-index: 5;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_closed {
|
&_closed {
|
||||||
|
@ -9,9 +9,9 @@ module.exports = [
|
|||||||
'language': 'en',
|
'language': 'en',
|
||||||
'reCaptchaKey': '6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS',
|
'reCaptchaKey': '6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS',
|
||||||
'departments': [
|
'departments': [
|
||||||
'Sales Support',
|
{id: 1, name: 'Sales Support'},
|
||||||
'Technical Issues',
|
{id: 2, name: 'Technical Issues'},
|
||||||
'System and Administration'
|
{id: 3, name: 'System and Administration'}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -39,11 +39,11 @@ module.exports = [
|
|||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: [
|
data: [
|
||||||
{name: 'Common issue #1', language: 'en', content: 'some content'},
|
{name: 'Common issue #1', language: 'en', content: 'some content 1'},
|
||||||
{name: 'Common issue #2', language: 'en', content: 'some content'},
|
{name: 'Common issue #2', language: 'en', content: 'some content 2'},
|
||||||
{name: 'Common issue #3', language: 'en', content: 'some content'},
|
{name: 'Common issue #3', language: 'en', content: 'some content 3'},
|
||||||
{name: 'Häufiges Problem #1', language: 'de', content: 'einige Inhalte'},
|
{name: 'Häufiges Problem #1', language: 'de', content: 'einige Inhalte 1'},
|
||||||
{name: 'Häufiges Problem #2', language: 'de', content: 'einige Inhalte'}
|
{name: 'Häufiges Problem #2', language: 'de', content: 'einige Inhalte 2'}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -77,5 +77,130 @@ module.exports = [
|
|||||||
data: {}
|
data: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/ticket/get',
|
||||||
|
time: 1000,
|
||||||
|
response: function () {
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
data: {
|
||||||
|
ticketNumber: '878552',
|
||||||
|
title: 'Lorem ipsum door',
|
||||||
|
content: 'I had a problem with the installation of the php server',
|
||||||
|
department: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Sales Support'
|
||||||
|
},
|
||||||
|
date: '20160415',
|
||||||
|
file: 'http://www.opensupports.com/some_file.zip',
|
||||||
|
language: 'en',
|
||||||
|
unread: false,
|
||||||
|
closed: false,
|
||||||
|
priority: 'medium',
|
||||||
|
author: {
|
||||||
|
name: 'Haskell Curry',
|
||||||
|
email: 'haskell@lambda.com'
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
name: 'Steve Jobs'
|
||||||
|
},
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: 'ASSIGN',
|
||||||
|
date: '20150409',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'COMMENT',
|
||||||
|
date: '20150409',
|
||||||
|
content: 'Do you have apache installed? It generally happens if you dont have apache.',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'UN_ASSIGN',
|
||||||
|
date: '20150410',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'DEPARTMENT_CHANGED',
|
||||||
|
date: '20150411',
|
||||||
|
content: 'System support',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'COMMENT',
|
||||||
|
date: '20150412',
|
||||||
|
content: 'I have already installed apache, but the problem persists',
|
||||||
|
author: {
|
||||||
|
name: 'Haskell Curry',
|
||||||
|
steve: 'haskell@lambda.com',
|
||||||
|
staff: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PRIORITY_CHANGED',
|
||||||
|
date: '20150413',
|
||||||
|
content: 'MEDIUM',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'COMMENT',
|
||||||
|
date: '20150511',
|
||||||
|
content: 'Thanks!, I soved it by myself',
|
||||||
|
author: {
|
||||||
|
name: 'Haskell Curry',
|
||||||
|
steve: 'haskell@lambda.com',
|
||||||
|
staff: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'CLOSE',
|
||||||
|
date: '20150513',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'RE_OPEN',
|
||||||
|
date: '20151018',
|
||||||
|
author: {
|
||||||
|
name: 'Haskell Curry',
|
||||||
|
email: 'haskell@lambda.com',
|
||||||
|
staff: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
@ -140,7 +140,7 @@ module.exports = [
|
|||||||
content: 'I had a problem with the installation of the php server',
|
content: 'I had a problem with the installation of the php server',
|
||||||
department: {
|
department: {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Environment Setup'
|
name: 'Technical Issues'
|
||||||
},
|
},
|
||||||
date: '20160416',
|
date: '20160416',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'http://www.opensupports.com/some_file.zip',
|
||||||
@ -158,7 +158,7 @@ module.exports = [
|
|||||||
name: 'Steve Jobs',
|
name: 'Steve Jobs',
|
||||||
email: 'steve@jobs.com'
|
email: 'steve@jobs.com'
|
||||||
},
|
},
|
||||||
actions: [
|
events: [
|
||||||
{
|
{
|
||||||
type: 'ASSIGN',
|
type: 'ASSIGN',
|
||||||
date: '20150409',
|
date: '20150409',
|
||||||
@ -258,8 +258,8 @@ module.exports = [
|
|||||||
title: 'Lorem ipsum door',
|
title: 'Lorem ipsum door',
|
||||||
content: 'I had a problem with the installation of the php server',
|
content: 'I had a problem with the installation of the php server',
|
||||||
department: {
|
department: {
|
||||||
id: 2,
|
id: 1,
|
||||||
name: 'Environment Setup'
|
name: 'Sales Support'
|
||||||
},
|
},
|
||||||
date: '20160415',
|
date: '20160415',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'http://www.opensupports.com/some_file.zip',
|
||||||
@ -274,7 +274,7 @@ module.exports = [
|
|||||||
owner: {
|
owner: {
|
||||||
name: 'Steve Jobs'
|
name: 'Steve Jobs'
|
||||||
},
|
},
|
||||||
actions: [
|
events: [
|
||||||
{
|
{
|
||||||
type: 'ASSIGN',
|
type: 'ASSIGN',
|
||||||
date: '20150409',
|
date: '20150409',
|
||||||
@ -374,8 +374,8 @@ module.exports = [
|
|||||||
title: 'Lorem ipsum door',
|
title: 'Lorem ipsum door',
|
||||||
content: 'I had a problem with the installation of the php server',
|
content: 'I had a problem with the installation of the php server',
|
||||||
department: {
|
department: {
|
||||||
id: 2,
|
id: 1,
|
||||||
name: 'Environment Setup'
|
name: 'Sales Support'
|
||||||
},
|
},
|
||||||
date: '20150409',
|
date: '20150409',
|
||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'http://www.opensupports.com/some_file.zip',
|
||||||
@ -390,7 +390,7 @@ module.exports = [
|
|||||||
owner: {
|
owner: {
|
||||||
name: 'Steve Jobs'
|
name: 'Steve Jobs'
|
||||||
},
|
},
|
||||||
actions: [
|
events: [
|
||||||
{
|
{
|
||||||
type: 'ASSIGN',
|
type: 'ASSIGN',
|
||||||
date: '20150409',
|
date: '20150409',
|
||||||
|
@ -60,6 +60,18 @@ export default {
|
|||||||
'DISCARD_CHANGES': 'Discard changes',
|
'DISCARD_CHANGES': 'Discard changes',
|
||||||
'DELETE': 'Delete',
|
'DELETE': 'Delete',
|
||||||
'LANGUAGE': 'Language',
|
'LANGUAGE': 'Language',
|
||||||
|
'OWNER': 'Owner',
|
||||||
|
'OWNED': 'Owned',
|
||||||
|
'STATUS': 'Status',
|
||||||
|
'NONE': 'None',
|
||||||
|
'OPENED': 'Opened',
|
||||||
|
'CLOSED': 'Closed',
|
||||||
|
'CLOSE': 'Close',
|
||||||
|
'RE_OPEN': 'Re open',
|
||||||
|
'ASSIGN_TO_ME': 'Assign to me',
|
||||||
|
'UN_ASSIGN': 'Unassign',
|
||||||
|
'VIEW_TICKET': 'View Ticket',
|
||||||
|
'SELECT_CUSTOM_RESPONSE': 'Select a custom response...',
|
||||||
|
|
||||||
//VIEW DESCRIPTIONS
|
//VIEW DESCRIPTIONS
|
||||||
'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.',
|
'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.',
|
||||||
@ -70,6 +82,7 @@ export default {
|
|||||||
'SUPPORT_CENTER_DESCRIPTION': 'Welcome to our support center. You can contact us through a tickets system. Your tickets will be answered by our staff.',
|
'SUPPORT_CENTER_DESCRIPTION': 'Welcome to our support center. You can contact us through a tickets system. Your tickets will be answered by our staff.',
|
||||||
'CUSTOM_RESPONSES_DESCRIPTION': 'Custom responses are automated responses for common problems',
|
'CUSTOM_RESPONSES_DESCRIPTION': 'Custom responses are automated responses for common problems',
|
||||||
'MY_TICKETS_DESCRIPTION': 'Here you can view the tickets you are responsible for.',
|
'MY_TICKETS_DESCRIPTION': 'Here you can view the tickets you are responsible for.',
|
||||||
|
'TICKET_VIEW_DESCRIPTION': 'This ticket has been sent by a customer. Here you can respond or assign the ticket',
|
||||||
|
|
||||||
//ERRORS
|
//ERRORS
|
||||||
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
'EMAIL_OR_PASSWORD': 'Email or password invalid',
|
||||||
@ -83,6 +96,7 @@ export default {
|
|||||||
'PASSWORD_NOT_MATCH': 'Password does not match',
|
'PASSWORD_NOT_MATCH': 'Password does not match',
|
||||||
'INVALID_RECOVER': 'Invalid recover data',
|
'INVALID_RECOVER': 'Invalid recover data',
|
||||||
'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.',
|
'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.',
|
||||||
|
'NO_PERMISSION': 'You\'ve no permission to access to this page.',
|
||||||
|
|
||||||
//MESSAGES
|
//MESSAGES
|
||||||
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import Reducer from 'reducers/reducer';
|
import Reducer from 'reducers/reducer';
|
||||||
|
import sessionStore from 'lib-app/session-store';
|
||||||
|
|
||||||
class AdminDataReducer extends Reducer {
|
class AdminDataReducer extends Reducer {
|
||||||
|
|
||||||
@ -16,18 +17,30 @@ class AdminDataReducer extends Reducer {
|
|||||||
getTypeHandlers() {
|
getTypeHandlers() {
|
||||||
return {
|
return {
|
||||||
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
|
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
|
||||||
|
'SESSION_CHECKED': this.onSessionChecked
|
||||||
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
|
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
|
||||||
'MY_TICKETS_PENDING': this.onMyTicketsPending
|
'MY_TICKETS_PENDING': this.onMyTicketsPending
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onCustomResponses(state, payload) {
|
onCustomResponses(state, payload) {
|
||||||
|
sessionStore.setItem('customResponses', JSON.stringify(payload.data));
|
||||||
|
|
||||||
return _.extend({}, state, {
|
return _.extend({}, state, {
|
||||||
customResponses: payload.data,
|
customResponses: payload.data,
|
||||||
customResponsesLoaded: true
|
customResponsesLoaded: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSessionChecked(state) {
|
||||||
|
const customResponses = sessionStore.getItem('customResponses');
|
||||||
|
|
||||||
|
return _.extend({}, state, {
|
||||||
|
customResponses: JSON.parse(customResponses),
|
||||||
|
customResponsesLoaded: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMyTicketsRetrieved(state, payload) {
|
onMyTicketsRetrieved(state, payload) {
|
||||||
return _.extend({}, state, {
|
return _.extend({}, state, {
|
||||||
myTickets: payload.data,
|
myTickets: payload.data,
|
||||||
|
@ -34,6 +34,7 @@ class AssignStaffController extends Controller {
|
|||||||
} else {
|
} else {
|
||||||
$this->user->sharedTicketList->add($this->ticket);
|
$this->user->sharedTicketList->add($this->ticket);
|
||||||
$this->ticket->owner = $this->user;
|
$this->ticket->owner = $this->user;
|
||||||
|
$this->ticket->unread = true;
|
||||||
$this->ticket->store();
|
$this->ticket->store();
|
||||||
$this->user->store();
|
$this->user->store();
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ class UnAssignStaffController extends Controller {
|
|||||||
$user->sharedTicketList->remove($ticket);
|
$user->sharedTicketList->remove($ticket);
|
||||||
$user->store();
|
$user->store();
|
||||||
$ticket->owner = null;
|
$ticket->owner = null;
|
||||||
|
$ticket->unread = true;
|
||||||
$ticket->store();
|
$ticket->store();
|
||||||
Response::respondSuccess();
|
Response::respondSuccess();
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,6 +10,7 @@ include 'ticket/change-department.php';
|
|||||||
include 'ticket/close.php';
|
include 'ticket/close.php';
|
||||||
include 'ticket/re-open.php';
|
include 'ticket/re-open.php';
|
||||||
include 'ticket/change-priority.php';
|
include 'ticket/change-priority.php';
|
||||||
|
include 'ticket/seen.php';
|
||||||
|
|
||||||
$ticketControllers = new ControllerGroup();
|
$ticketControllers = new ControllerGroup();
|
||||||
$ticketControllers->setGroupPath('/ticket');
|
$ticketControllers->setGroupPath('/ticket');
|
||||||
@ -25,5 +26,6 @@ $ticketControllers->addController(new ChangeDepartmentController);
|
|||||||
$ticketControllers->addController(new CloseController);
|
$ticketControllers->addController(new CloseController);
|
||||||
$ticketControllers->addController(new ReOpenController);
|
$ticketControllers->addController(new ReOpenController);
|
||||||
$ticketControllers->addController(new ChangePriorityController);
|
$ticketControllers->addController(new ChangePriorityController);
|
||||||
|
$ticketControllers->addController(new SeenController);
|
||||||
|
|
||||||
$ticketControllers->finalize();
|
$ticketControllers->finalize();
|
@ -34,6 +34,7 @@ class ChangeDepartmentController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$ticket->department = $department;
|
$ticket->department = $department;
|
||||||
|
$ticket->unread = true;
|
||||||
$ticket->store();
|
$ticket->store();
|
||||||
Response::respondSuccess();
|
Response::respondSuccess();
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ class ChangePriorityController extends Controller {
|
|||||||
|
|
||||||
if($ticket->owner && $user->id === $ticket->owner->id) {
|
if($ticket->owner && $user->id === $ticket->owner->id) {
|
||||||
$ticket->priority = $priority;
|
$ticket->priority = $priority;
|
||||||
|
$ticket->unread = true;
|
||||||
$ticket->store();
|
$ticket->store();
|
||||||
Response::respondSuccess();
|
Response::respondSuccess();
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,7 +31,12 @@ class CloseController extends Controller {
|
|||||||
Response::respondError(ERRORS::NO_PERMISSION);
|
Response::respondError(ERRORS::NO_PERMISSION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Controller::isStaffLogged()) {
|
||||||
|
$ticket->unread = true;
|
||||||
|
} else {
|
||||||
|
$ticket->unreadStaff = true;
|
||||||
|
}
|
||||||
$ticket->closed = true;
|
$ticket->closed = true;
|
||||||
$ticket->store();
|
$ticket->store();
|
||||||
Response::respondSuccess();
|
Response::respondSuccess();
|
||||||
|
@ -51,11 +51,11 @@ class CommentController extends Controller {
|
|||||||
));
|
));
|
||||||
|
|
||||||
if(Controller::isStaffLogged()) {
|
if(Controller::isStaffLogged()) {
|
||||||
$comment->authorUser = Controller::getLoggedUser();
|
$this->ticket->unread = true;
|
||||||
} else {
|
} else {
|
||||||
$comment->authorUser = Controller::getLoggedUser();
|
$this->ticket->unreadStaff = true;
|
||||||
}
|
}
|
||||||
|
$comment->authorUser = Controller::getLoggedUser();
|
||||||
$this->ticket->addEvent($comment);
|
$this->ticket->addEvent($comment);
|
||||||
$this->ticket->store();
|
$this->ticket->store();
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ class CreateController extends Controller {
|
|||||||
'file' => '',
|
'file' => '',
|
||||||
'date' => Date::getCurrentDate(),
|
'date' => Date::getCurrentDate(),
|
||||||
'unread' => false,
|
'unread' => false,
|
||||||
|
'unreadStaff' => true,
|
||||||
'closed' => false,
|
'closed' => false,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -30,7 +30,11 @@ class ReOpenController extends Controller {
|
|||||||
Response::respondError(ERRORS::NO_PERMISSION);
|
Response::respondError(ERRORS::NO_PERMISSION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(Controller::isStaffLogged()) {
|
||||||
|
$ticket->unread = true;
|
||||||
|
} else {
|
||||||
|
$ticket->unreadStaff = true;
|
||||||
|
}
|
||||||
$ticket->closed = false;
|
$ticket->closed = false;
|
||||||
$ticket->store();
|
$ticket->store();
|
||||||
Response::respondSuccess();
|
Response::respondSuccess();
|
||||||
|
39
server/controllers/ticket/seen.php
Normal file
39
server/controllers/ticket/seen.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class SeenController extends Controller {
|
||||||
|
const PATH = '/seen';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return [
|
||||||
|
'permission' => 'user',
|
||||||
|
'requestData' => [
|
||||||
|
'ticketNumber' => [
|
||||||
|
'validation' => DataValidator::validTicketNumber(),
|
||||||
|
'error' => ERRORS::INVALID_TICKET
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
$ticketnumber = Controller::request('ticketNumber');
|
||||||
|
$user = Controller::getLoggedUser();
|
||||||
|
$ticket = Ticket::getByTicketNumber($ticketnumber);
|
||||||
|
|
||||||
|
if (Controller::isStaffLogged() && $ticket->owner && $ticket->owner->id === $user->id) {
|
||||||
|
$ticket->unreadStaff = false;
|
||||||
|
$ticket->store();
|
||||||
|
Response::respondSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Controller::isStaffLogged() && $ticket->author && $user->id === $ticket->author->id) {
|
||||||
|
$ticket->unread = false;
|
||||||
|
$ticket->store();
|
||||||
|
Response::respondSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Response::respondError(ERRORS::NO_PERMISSION);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,8 @@ class Ticket extends DataStore {
|
|||||||
'priority',
|
'priority',
|
||||||
'author',
|
'author',
|
||||||
'owner',
|
'owner',
|
||||||
'ownTicketeventList'
|
'ownTicketeventList',
|
||||||
|
'unreadStaff'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +34,8 @@ class Ticket extends DataStore {
|
|||||||
public function getDefaultProps() {
|
public function getDefaultProps() {
|
||||||
return array(
|
return array(
|
||||||
'priority' => 'low',
|
'priority' => 'low',
|
||||||
|
'unread' => false,
|
||||||
|
'unreadStaff' => true,
|
||||||
'ticketNumber' => $this->generateUniqueTicketNumber()
|
'ticketNumber' => $this->generateUniqueTicketNumber()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ describe '/staff/assign-ticket' do
|
|||||||
|
|
||||||
(ticket['owner_id']).should.equal('1')
|
(ticket['owner_id']).should.equal('1')
|
||||||
|
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
|
|
||||||
staff_ticket = $database.getRow('staff_ticket', 1 , 'id')
|
staff_ticket = $database.getRow('staff_ticket', 1 , 'id')
|
||||||
|
|
||||||
(staff_ticket['staff_id']).should.equal('1')
|
(staff_ticket['staff_id']).should.equal('1')
|
||||||
|
@ -21,6 +21,7 @@ describe '/staff/un-assign-ticket' do
|
|||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
|
|
||||||
(ticket['owner_id']).should.equal(nil)
|
(ticket['owner_id']).should.equal(nil)
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
|
|
||||||
staff_ticket = $database.getRow('staff_ticket', 1 , 'id')
|
staff_ticket = $database.getRow('staff_ticket', 1 , 'id')
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ describe '/ticket/change-department' do
|
|||||||
(result['status']).should.equal('success')
|
(result['status']).should.equal('success')
|
||||||
|
|
||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
(ticket['department_id']).should.equal('2')
|
(ticket['department_id']).should.equal('2')
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -18,6 +18,7 @@ describe '/ticket/change-priority' do
|
|||||||
|
|
||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
(ticket['priority']).should.equal('high')
|
(ticket['priority']).should.equal('high')
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should change priority to medium if everything is okey' do
|
it 'should change priority to medium if everything is okey' do
|
||||||
@ -34,6 +35,7 @@ describe '/ticket/change-priority' do
|
|||||||
|
|
||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
(ticket['priority']).should.equal('medium')
|
(ticket['priority']).should.equal('medium')
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should change priority to low if everything is okey' do
|
it 'should change priority to low if everything is okey' do
|
||||||
@ -50,6 +52,7 @@ describe '/ticket/change-priority' do
|
|||||||
|
|
||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
(ticket['priority']).should.equal('low')
|
(ticket['priority']).should.equal('low')
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -17,6 +17,7 @@ describe '/ticket/close' do
|
|||||||
|
|
||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
(ticket['closed']).should.equal('1')
|
(ticket['closed']).should.equal('1')
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -72,6 +72,7 @@ describe '/ticket/comment/' do
|
|||||||
(comment['content']).should.equal('some comment content')
|
(comment['content']).should.equal('some comment content')
|
||||||
(comment['type']).should.equal('COMMENT')
|
(comment['type']).should.equal('COMMENT')
|
||||||
(comment['author_user_id']).should.equal($csrf_userid)
|
(comment['author_user_id']).should.equal($csrf_userid)
|
||||||
|
(ticket['unread_staff']).should.equal('1')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should fail if user is not the author nor owner' do
|
it 'should fail if user is not the author nor owner' do
|
||||||
|
@ -17,6 +17,7 @@ describe '/ticket/re-open' do
|
|||||||
|
|
||||||
ticket = $database.getRow('ticket', 1 , 'id')
|
ticket = $database.getRow('ticket', 1 , 'id')
|
||||||
(ticket['closed']).should.equal('0')
|
(ticket['closed']).should.equal('0')
|
||||||
|
(ticket['unread']).should.equal('1')
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
39
tests/ticket/seen.rb
Normal file
39
tests/ticket/seen.rb
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
describe '/ticket/seen' do
|
||||||
|
|
||||||
|
describe 'when a staff is logged' do
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
|
||||||
|
it 'should change unread if everything is okey ' do
|
||||||
|
ticket = $database.getRow('ticket', 1, 'id')
|
||||||
|
result = request('/ticket/seen', {
|
||||||
|
ticketNumber: ticket['ticket_number'],
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
ticket = $database.getRow('ticket', 1, 'id')
|
||||||
|
(ticket['unreadStaff']).should.equal('0')
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a user is logged' do
|
||||||
|
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login()
|
||||||
|
it 'should change unread if everything is okey ' do
|
||||||
|
ticket = $database.getRow('ticket', 1, 'id')
|
||||||
|
result = request('/ticket/seen', {
|
||||||
|
ticketNumber: ticket['ticket_number'],
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
|
||||||
|
(result['status']).should.equal('success')
|
||||||
|
ticket = $database.getRow('ticket', 1, 'id')
|
||||||
|
(ticket['unread']).should.equal('0')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user