Merged in os-84-frontend-ticket-events (pull request #69)

Os 85 Frontend - Staff Ticket view
This commit is contained in:
Ivan Diaz 2016-10-21 19:40:18 +00:00
commit 61ecc00faf
16 changed files with 531 additions and 110 deletions

View File

@ -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',
@ -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;

View File

@ -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;
} }

View File

@ -4,19 +4,27 @@ import _ from 'lodash';
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 store from 'app/store';
import SessionStore from 'lib-app/session-store';
import SessionActions from 'actions/session-actions'; 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,
assignmentAllowed: React.PropTypes.bool
}; };
static defaultProps = { static defaultProps = {
editable: false,
ticket: { ticket: {
author: {}, author: {},
department: {}, department: {},
@ -32,7 +40,6 @@ class TicketViewer extends React.Component {
}; };
} }
render() { render() {
const ticket = this.props.ticket; const ticket = this.props.ticket;
@ -42,21 +49,12 @@ 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>
<div className="ticket-viewer__info-row-header row"> {this.props.editable ? this.renderEditableHeaders() : this.renderHeaders()}
<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__date col-md-4">{i18n('DATE')}</div>
</div>
<div className="ticket-viewer__info-row-values row">
<div className="ticket-viewer__department col-md-4">{ticket.department.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>
<div className="ticket-viewer__content"> <div className="ticket-viewer__content">
<TicketAction type="COMMENT" author={ticket.author} content={ticket.content} date={ticket.date} file={ticket.file}/> <TicketEvent type="COMMENT" author={ticket.author} content={ticket.content} date={ticket.date} file={ticket.file}/>
</div> </div>
<div className="ticket-viewer__comments"> <div className="ticket-viewer__comments">
{ticket.actions && ticket.actions.map(this.renderAction.bind(this))} {ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
</div> </div>
<div className="ticket-viewer__response"> <div className="ticket-viewer__response">
<div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div> <div className="ticket-viewer__response-title row">{i18n('RESPOND')}</div>
@ -71,12 +69,179 @@ class TicketViewer extends React.Component {
); );
} }
renderAction(action, index) { 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 ( return (
<TicketAction {...action} key={index} /> <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__department col-md-4">{i18n('DEPARTMENT')}</div>
<div className="ticket-viewer__author col-md-4">{i18n('AUTHOR')}</div>
<div className="ticket-viewer__date col-md-4">{i18n('DATE')}</div>
</div>
<div className="ticket-viewer__info-row-values row">
<div className="ticket-viewer__department col-md-4">{ticket.department.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>
<div className="ticket-viewer__info-row-header row">
<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 className="ticket-viewer__info-row-values row">
<div className="col-md-4">
{i18n(priorities[this.props.ticket.priority || 'low'])}
</div>
<div className="col-md-4">
{(ticket.owner) ? ticket.owner.name : i18n('NONE')}
</div>
<div className="col-md-4">
{this.renderOwnerNode()}
</div>
</div>
</div>
);
}
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 (
<TicketEvent {...options} key={index} />
);
}
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));
}
onSubmit(formState) { onSubmit(formState) {
this.setState({ this.setState({
loading: true loading: true
@ -94,8 +259,8 @@ class TicketViewer extends React.Component {
this.setState({ this.setState({
loading: false loading: false
}); });
store.dispatch(SessionActions.getUserData()); this.onTicketModification();
} }
onCommentFail() { onCommentFail() {
@ -103,6 +268,12 @@ class TicketViewer extends React.Component {
loading: false loading: false
}); });
} }
onTicketModification() {
if (this.props.onChange) {
this.props.onChange();
}
}
} }
export default TicketViewer; export default TicketViewer;

View File

@ -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 {

View File

@ -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">

View File

@ -1,14 +1,98 @@
import React from 'react'; import React from 'react';
import _ from 'lodash';
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,
editable: _.get(this.state.ticket, 'owner.id') === SessionStore.getUserData().id
};
}
retrieveTicket() {
this.setState({
loading: true,
ticket: {}
});
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 AdminPanelViewTicket;

View File

@ -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;
}
}

View File

@ -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));
} }

View File

@ -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) => {

View File

@ -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'),

View File

@ -47,6 +47,12 @@
} }
} }
&_extra-small {
text-transform: none;
width: 130px;
height: 30px;
}
&_small { &_small {
width: 100px; width: 100px;
height: 47px; height: 47px;

View File

@ -27,7 +27,7 @@
&__list-container { &__list-container {
position: absolute; position: absolute;
width: 150px; width: 150px;
z-index: 5; z-index: 100;
} }
&_closed { &_closed {

View File

@ -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'}
] ]
} }
}; };

View File

@ -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
}
}
]
}
};
}
} }
]; ];

View File

@ -43,7 +43,7 @@ module.exports = [
return { return {
status: 'success', status: 'success',
data: { data: {
sessionActive: false sessionActive: true
} }
}; };
} }
@ -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',

View File

@ -60,6 +60,17 @@ 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',
//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.',
@ -69,6 +80,7 @@ export default {
'ACCOUNT_DESCRIPTION': 'All your tickets are stored in your accounts\'s profile. Keep track off all your tickets you send to our staff team.', 'ACCOUNT_DESCRIPTION': 'All your tickets are stored in your accounts\'s profile. Keep track off all your tickets you send to our staff team.',
'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',
'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',
@ -82,6 +94,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.',