Merge branch 'master' into OS-87-path/staff/get-all-tickets

This commit is contained in:
ivan 2016-11-03 15:05:22 -03:00
commit de161c8956
22 changed files with 1251 additions and 52 deletions

View File

@ -10,5 +10,35 @@ export default {
data: {}
})
};
},
retrieveMyTickets() {
return {
type: 'MY_TICKETS',
payload: API.call({
path: '/staff/get-tickets',
data: {}
})
};
},
retrieveNewTickets() {
return {
type: 'NEW_TICKETS',
payload: API.call({
path: '/staff/get-new-tickets',
data: {}
})
};
},
retrieveAllTickets() {
return {
type: 'ALL_TICKETS',
payload: API.call({
path: '/staff/get-all-tickets',
data: {}
})
};
}
};

View File

@ -1,9 +1,8 @@
import API from 'lib-app/api-call';
import AdminDataActions from 'actions/admin-data-actions';
import sessionStore from 'lib-app/session-store';
import store from 'app/store';
import ConfigActions from 'actions/config-actions';
export default {
login(loginData) {
return {
@ -13,6 +12,10 @@ export default {
data: loginData
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
if(result.data.staff) {
store.dispatch(AdminDataActions.retrieveCustomResponses());
}
return result;
})

View File

@ -0,0 +1,188 @@
const _ = require('lodash');
const TicketInfo = ReactMock();
const Table = ReactMock();
const Button = ReactMock();
const Tooltip = ReactMock();
const Dropdown = ReactMock();
const i18n = stub().returnsArg(0);
const TicketList = requireUnit('app-components/ticket-list', {
'app-components/ticket-info': TicketInfo,
'core-components/table': Table,
'core-components/button': Button,
'core-components/tooltip': Tooltip,
'core-components/drop-down': Dropdown,
'lib-app/i18n': i18n
});
describe('TicketList component', function () {
let ticketList, table, dropdown;
let tickets = (function() {
let ticket = {
unread: false,
closed: false,
title: 'This is not working',
ticketNumber: 123124,
date: '20160215',
department: {
id: 1,
name: 'Sales Support'
},
priority: 'low',
author: {
id: 3,
name: 'Francisco Villegas'
}
};
let list = _.range(5).map(() => ticket);
list = list.concat(_.range(5).map(() => {
return _.extend({}, ticket, {
department: {
id: 2,
name: 'Tech Help'
}
})
}));
return list;
})();
function renderTicketList(props = {}) {
ticketList = TestUtils.renderIntoDocument(
<TicketList tickets={tickets} {...props}/>
);
table = TestUtils.scryRenderedComponentsWithType(ticketList, Table);
dropdown = TestUtils.scryRenderedComponentsWithType(ticketList, Dropdown);
}
it('should pass correct props to Table', function () {
renderTicketList();
expect(table[0].props.loading).to.equal(false);
expect(table[0].props.pageSize).to.equal(10);
expect(table[0].props.headers).to.deep.equal([
{
key: 'number',
value: i18n('NUMBER'),
className: 'ticket-list__number col-md-1'
},
{
key: 'title',
value: i18n('TITLE'),
className: 'ticket-list__title col-md-6'
},
{
key: 'department',
value: i18n('DEPARTMENT'),
className: 'ticket-list__department col-md-3'
},
{
key: 'date',
value: i18n('DATE'),
className: 'ticket-list__date col-md-2'
}
]);
});
it('should pass loading to Table', function () {
renderTicketList({loading: true});
expect(table[0].props.loading).to.equal(true);
});
it('should pass correct compare function to Table', function () {
let minCompare = table[0].props.comp;
let row1 = {
closed: false,
unread: false,
date: '20160405'
};
let row2 = {
closed: false,
unread: false,
date: '20160406'
};
expect(minCompare(row1, row2)).to.equal(1);
row1.unread = true;
expect(minCompare(row1, row2)).to.equal(-1);
row2.unread = true;
expect(minCompare(row1, row2)).to.equal(1);
row2.date = '20160401';
expect(minCompare(row1, row2)).to.equal(-1);
});
describe('when using secondary type', function () {
beforeEach(function () {
renderTicketList({
type: 'secondary',
departments: [
{id: 1, name: 'Sales Support'},
{id: 2, name: 'Tech Help'}
]
});
});
it('should pass correct props to Table', function () {
expect(table[0].props.headers).to.deep.equal([
{
key: 'number',
value: i18n('NUMBER'),
className: 'ticket-list__number col-md-1'
},
{
key: 'title',
value: i18n('TITLE'),
className: 'ticket-list__title col-md-4'
},
{
key: 'priority',
value: i18n('PRIORITY'),
className: 'ticket-list__priority col-md-1'
},
{
key: 'department',
value: i18n('DEPARTMENT'),
className: 'ticket-list__department col-md-2'
},
{
key: 'author',
value: i18n('AUTHOR'),
className: 'ticket-list__author col-md-2'
},
{
key: 'date',
value: i18n('DATE'),
className: 'ticket-list__date col-md-2'
}
]);
});
it('should pass correct props to dropdown', function () {
expect(dropdown[0].props.items).to.deep.equal([
{content: i18n('ALL_DEPARTMENTS')},
{content: 'Sales Support'},
{content: 'Tech Help'}
]);
expect(dropdown[0].props.size).to.equal('medium');
});
it('should filter tickets by department when DropDown changes', function () {
dropdown[0].props.onChange({index: 1});
_.forEach(table[0].props.rows, function (row) {
expect(row.department).to.equal('Sales Support');
});
dropdown[0].props.onChange({index: 2});
_.forEach(table[0].props.rows, function (row) {
expect(row.department).to.equal('Tech Help');
});
dropdown[0].props.onChange({index: 0});
expect(table[0].props.rows.length).to.equal(10);
});
});
});

View File

@ -17,7 +17,7 @@ class TicketEvent extends React.Component {
]),
author: React.PropTypes.object,
content: React.PropTypes.string,
date: React.PropTypes.number
date: React.PropTypes.string
};
render() {

View File

@ -1,16 +1,20 @@
import React from 'react';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import DateTransformer from 'lib-core/date-transformer';
import TicketInfo from 'app-components/ticket-info';
import Table from 'core-components/table';
import Button from 'core-components/button';
import Tooltip from 'core-components/tooltip';
import TicketInfo from 'app-components/ticket-info';
import DateTransformer from 'lib-core/date-transformer';
import DropDown from 'core-components/drop-down';
class TicketList extends React.Component {
static propTypes = {
departments: React.PropTypes.array,
loading: React.PropTypes.bool,
ticketPath: React.PropTypes.string,
tickets: React.PropTypes.arrayOf(React.PropTypes.object),
type: React.PropTypes.oneOf([
'primary',
@ -19,18 +23,58 @@ class TicketList extends React.Component {
};
static defaultProps = {
loading: false,
tickets: [],
departments: [],
ticketPath: '/dashboard/ticket/',
type: 'primary'
};
state = {
selectedDepartment: 0
};
render() {
return (
<div className="ticket-list">
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} pageSize={10} comp={this.compareFunction} />
{(this.props.type === 'secondary') ? this.renderDepartmentsDropDown() : null}
<Table loading={this.props.loading} headers={this.getTableHeaders()} rows={this.getTableRows()} pageSize={10} comp={this.compareFunction} />
</div>
);
}
renderDepartmentsDropDown() {
return (
<div className="ticket-list__department-selector">
<DropDown {...this.getDepartmentDropdownProps()} />
</div>
);
}
getDepartmentDropdownProps() {
return {
items: this.getDepartments(),
onChange: (event) => {
this.setState({
selectedDepartment: event.index && this.props.departments[event.index - 1].id
});
},
size: 'medium'
};
}
getDepartments() {
let departments = this.props.departments.map((department) => {
return {content: department.name};
});
departments.unshift({
content: i18n('ALL_DEPARTMENTS')
});
return departments;
}
getTableHeaders() {
if (this.props.type == 'primary' ) {
return [
@ -92,7 +136,13 @@ class TicketList extends React.Component {
}
getTableRows() {
return this.props.tickets.map(this.gerTicketTableObject.bind(this));
return this.getTickets().map(this.gerTicketTableObject.bind(this));
}
getTickets() {
return (this.state.selectedDepartment) ? _.filter(this.props.tickets, (ticket) => {
return ticket.department.id == this.state.selectedDepartment
}) : this.props.tickets;
}
gerTicketTableObject(ticket) {
@ -105,7 +155,7 @@ class TicketList extends React.Component {
</Tooltip>
),
title: (
<Button className="ticket-list__title-link" type="clean" route={{to: '/dashboard/ticket/' + ticket.ticketNumber}}>
<Button className="ticket-list__title-link" type="clean" route={{to: this.props.ticketPath + ticket.ticketNumber}}>
{titleText}
</Button>
),
@ -137,8 +187,6 @@ class TicketList extends React.Component {
}
compareFunction(row1, row2) {
let ans = 0;
if (row1.closed == row2.closed) {
if (row1.unread == row2.unread) {
let s1 = row1.date;

View File

@ -2,6 +2,10 @@
.ticket-list {
&__department-selector {
margin-bottom: 25px;
}
&__number {
text-align: left;
}

View File

@ -1,11 +1,10 @@
import React from 'react';
import _ from 'lodash';
import RichTextEditor from 'react-rte-browserify';
import i18n from 'lib-app/i18n';
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 TicketEvent from 'app-components/ticket-event';
import AreYouSure from 'app-components/are-you-sure';
@ -20,6 +19,7 @@ class TicketViewer extends React.Component {
ticket: React.PropTypes.object,
onChange: React.PropTypes.func,
editable: React.PropTypes.bool,
customResponses: React.PropTypes.array,
assignmentAllowed: React.PropTypes.bool
};
@ -32,13 +32,11 @@ class TicketViewer extends React.Component {
}
};
constructor(props) {
super(props);
this.state = {
loading: false
};
}
state = {
loading: false,
commentValue: RichTextEditor.createEmptyValue(),
commentEdited: false
};
render() {
const ticket = this.props.ticket;
@ -58,8 +56,9 @@ class TicketViewer extends React.Component {
</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 onSubmit={this.onSubmit.bind(this)} loading={this.state.loading}>
<Form {...this.getCommentFormProps()}>
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
</Form>
@ -186,6 +185,44 @@ class TicketViewer extends React.Component {
);
}
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));
}
@ -242,6 +279,21 @@ class TicketViewer extends React.Component {
}).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) {
this.setState({
loading: true

View File

@ -72,5 +72,11 @@
padding: 20px;
text-align: left;
}
&-custom {
background-color: $very-light-grey;
padding: 20px 0 0 20px;
text-align: left;
}
}
}

View File

@ -1,14 +1,47 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import AdminDataAction from 'actions/admin-data-actions';
import Header from 'core-components/header';
import TicketList from 'app-components/ticket-list';
class AdminPanelAllTickets extends React.Component {
static defaultProps = {
departments: [],
tickets: []
};
componentDidMount() {
this.props.dispatch(AdminDataAction.retrieveAllTickets());
}
render() {
return (
<div>
/admin/panel/tickets/all-tickets
<div className="admin-panel-my-tickets">
<Header title={i18n('ALL_TICKETS')} description={i18n('ALL_TICKETS_DESCRIPTION')} />
<TicketList {...this.getProps()}/>
</div>
);
}
getProps() {
return {
departments: this.props.departments,
tickets: this.props.tickets,
type: 'secondary',
loading: this.props.loading,
ticketPath: '/admin/panel/tickets/view-ticket/'
};
}
}
export default AdminPanelAllTickets;
export default connect((store) => {
return {
departments: store.session.userDepartments,
tickets: store.adminData.allTickets,
loading: !store.adminData.allTicketsLoaded
};
})(AdminPanelAllTickets);

View File

@ -1,14 +1,47 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import AdminDataAction from 'actions/admin-data-actions';
import Header from 'core-components/header';
import TicketList from 'app-components/ticket-list';
class AdminPanelMyTickets extends React.Component {
static defaultProps = {
departments: [],
tickets: []
};
componentDidMount() {
this.props.dispatch(AdminDataAction.retrieveMyTickets());
}
render() {
return (
<div>
/admin/panel/tickets/my-tickets
<div className="admin-panel-my-tickets">
<Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} />
<TicketList {...this.getProps()}/>
</div>
);
}
getProps() {
return {
departments: this.props.departments,
tickets: this.props.tickets,
type: 'secondary',
loading: this.props.loading,
ticketPath: '/admin/panel/tickets/view-ticket/'
};
}
}
export default AdminPanelMyTickets;
export default connect((store) => {
return {
departments: store.session.userDepartments,
tickets: store.adminData.myTickets,
loading: !store.adminData.myTicketsLoaded
};
})(AdminPanelMyTickets);

View File

@ -1,14 +1,47 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import AdminDataAction from 'actions/admin-data-actions';
import Header from 'core-components/header';
import TicketList from 'app-components/ticket-list';
class AdminPanelNewTickets extends React.Component {
static defaultProps = {
departments: [],
tickets: []
};
componentDidMount() {
this.props.dispatch(AdminDataAction.retrieveNewTickets());
}
render() {
return (
<div>
/admin/panel/tickets/new-tickets
<div className="admin-panel-my-tickets">
<Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} />
<TicketList {...this.getProps()}/>
</div>
);
}
getProps() {
return {
departments: this.props.departments,
tickets: this.props.tickets,
type: 'secondary',
loading: this.props.loading,
ticketPath: '/admin/panel/tickets/view-ticket/'
};
}
}
export default AdminPanelNewTickets;
export default connect((store) => {
return {
departments: store.session.userDepartments,
tickets: store.adminData.newTickets,
loading: !store.adminData.newTicketsLoaded
};
})(AdminPanelNewTickets);

View File

@ -1,5 +1,6 @@
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';
@ -62,19 +63,15 @@ class AdminPanelViewTicket extends React.Component {
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() {
this.setState({
loading: true,
ticket: {}
});
API.call({
path: '/ticket/get',
date: {
data: {
ticketNumber: this.props.params.ticketNumber
}
}).then(this.onRetrieveSuccess.bind(this)).catch(this.onRetrieveFail.bind(this))
@ -85,6 +82,15 @@ class AdminPanelViewTicket extends React.Component {
loading: false,
ticket: result.data
});
if(result.data.unreadStaff) {
API.call({
path: '/ticket/seen',
data: {
ticketNumber: this.props.params.ticketNumber
}
})
}
}
onRetrieveFail() {
@ -95,4 +101,8 @@ class AdminPanelViewTicket extends React.Component {
}
}
export default AdminPanelViewTicket;
export default connect((store) => {
return {
customResponses: store.adminData.customResponses
};
})(AdminPanelViewTicket);

View File

@ -15,6 +15,7 @@ const DropDown = require('core-components/drop-down');
const Menu = require('core-components/menu');
const Tooltip = require('core-components/tooltip');
const Table = require('core-components/table');
const InfoTooltip = require('core-components/info-tooltip');
let dropDownItems = [{content: 'English'}, {content: 'Spanish'}, {content: 'German'}, {content: 'Portuguese'}, {content: 'Japanese'}];
let secondaryMenuItems = [
@ -168,6 +169,12 @@ let DemoPage = React.createClass({
return ans;
}}/>
)
},
{
title: 'InfoTooltip',
render: (
<InfoTooltip type="warning" text="No staff member is assigned to this department." />
)
}
],
@ -197,4 +204,4 @@ let DemoPage = React.createClass({
}
});
export default DemoPage;
export default DemoPage;

View File

@ -2,6 +2,9 @@ import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionActions from 'actions/session-actions';
import TicketViewer from 'app-components/ticket-viewer';
@ -11,16 +14,35 @@ class DashboardTicketPage extends React.Component {
tickets: React.PropTypes.array
};
componentDidMount() {
let ticket = this.getTicketData();
if(ticket.unread) {
API.call({
path: '/ticket/seen',
data: {
ticketNumber: ticket.ticketNumber
}
});
}
}
render() {
let ticketView = i18n('NO_PERMISSION');
if(!_.isEmpty(this.getTicketData())) {
ticketView = <TicketViewer ticket={this.getTicketData()} onChange={this.retrieveUserData.bind(this)}/>;
}
return (
<div className="dashboard-ticket-page">
<TicketViewer ticket={this.getTicketData()} onChange={this.retrieveUserData.bind(this)}/>
{ticketView}
</div>
);
}
getTicketData() {
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber});
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber}) || {};
}
retrieveUserData() {
@ -32,4 +54,4 @@ export default connect((store) => {
return {
tickets: store.session.userTickets
};
})(DashboardTicketPage);
})(DashboardTicketPage);

View File

@ -0,0 +1,54 @@
import React from 'react';
import classNames from 'classnames';
import i18n from 'lib-app/i18n';
import Icon from 'core-components/icon';
import Tooltip from 'core-components/tooltip';
class InfoTooltip extends React.Component {
static propTypes = {
type: React.PropTypes.oneOf(['default', 'warning']),
text: React.PropTypes.string.isRequired
};
static defaultProps = {
type: 'default'
};
render() {
let name = (this.props.type === 'default') ? 'question-circle' : 'exclamation-triangle';
return (
<div className={this.getClass()}>
<Tooltip content={this.renderText()} openOnHover>
<span className="info-tooltip__icon">
<Icon name={name}/>
</span>
</Tooltip>
</div>
);
}
renderText() {
let message = (this.props.type === 'default') ? i18n('INFO') : i18n('WARNING');
return (
<div className="info-tooltip__text">
<div className="info-tooltip__text-title">
{message}
</div>
{this.props.text}
</div>
);
}
getClass() {
let classes = {
'info-tooltip': true,
'info-tooltip_warning': (this.props.type === 'warning')
};
return classNames(classes);
}
}
export default InfoTooltip;

View File

@ -0,0 +1,26 @@
@import "../scss/vars";
.info-tooltip {
&__text {
&-title {
color: $secondary-blue;
font-size: $font-size--md;
}
}
&__icon {
color: $secondary-blue;
}
&_warning {
.info-tooltip__icon {
color: $primary-red;
}
.info-tooltip__text {
&-title {
color: $primary-red;
}
}
}
}

View File

@ -51,6 +51,8 @@
&__loading-wrapper {
min-height: 200px;
position: relative;
background-color: $grey;
}
&__loading {

View File

@ -17,6 +17,7 @@
background-color: #F7F7F7;
color: black;
padding: 10px;
z-index: 1000;
}
&__pointer {

View File

@ -13,11 +13,576 @@ module.exports = [
staff: true,
departments: [
{id: 1, name: 'Sales Support'},
{id: 2, name: 'Technical Issues'},
{id: 3, name: 'System and Administration'}
{id: 2, name: 'Technical Issues'}
]
}
};
}
},
{
path: '/staff/get-tickets',
time: 300,
response: function () {
return {
status: 'success',
data: [
{
ticketNumber: '445441',
title: 'Problem with installation',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Technical Issues'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
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 solved 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
}
}
]
},
{
ticketNumber: '878552',
title: 'Lorem ipsum door',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Technical Issues'
},
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
}
}
]
},
{
ticketNumber: '118551',
title: 'Lorem ipsum door',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Technical Issues'
},
date: '20150409',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: false,
closed: false,
priority: 'high',
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
}
}
]
},
{
ticketNumber: '445441',
title: 'Inscription ACM ICPC',
content: 'I had a problem with the installation of the php server',
department: {
id: 1,
name: 'Sales Support'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: false,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
}
]
}
}
},
{
path: '/staff/get-new-tickets',
time: 300,
response: function () {
return {
status: 'success',
data: [
{
ticketNumber: '445441',
title: 'Inscription ACM ICPC',
content: 'I had a problem with the installation of the php server',
department: {
id: 1,
name: 'Sales Support'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
},
{
ticketNumber: '445441',
title: 'Inscription ACM ICPC',
content: 'I had a problem with the installation of the php server',
department: {
id: 1,
name: 'Sales Support'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
},
{
ticketNumber: '445441',
title: 'Code jam is awesome',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Technical Issues'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
}
]
}
}
},
{
path: '/staff/get-all-tickets',
time: 300,
response: function () {
return {
status: 'success',
data: [
{
ticketNumber: '445441',
title: 'Inscription ACM ICPC',
content: 'I had a problem with the installation of the php server',
department: {
id: 1,
name: 'Sales Support'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
},
{
ticketNumber: '445441',
title: 'Inscription ACM ICPC',
content: 'I had a problem with the installation of the php server',
department: {
id: 1,
name: 'Sales Support'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
},
{
ticketNumber: '445441',
title: 'Code jam is awesome',
content: 'I had a problem with the installation of the php server',
department: {
id: 2,
name: 'Technical Issues'
},
date: '20160416',
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: true,
closed: false,
priority: 'low',
author: {
id: 12,
name: 'Haskell Curry',
email: 'haskell@lambda.com'
},
owner: {
id: 15,
name: 'Steve Jobs',
email: 'steve@jobs.com'
},
events: []
}
]
}
}
}
];

View File

@ -39,11 +39,11 @@ module.exports = [
return {
status: 'success',
data: [
{name: 'Common issue #1', language: 'en', content: 'some content'},
{name: 'Common issue #2', language: 'en', content: 'some content'},
{name: 'Common issue #3', language: 'en', content: 'some content'},
{name: 'Häufiges Problem #1', language: 'de', content: 'einige Inhalte'},
{name: 'Häufiges Problem #2', language: 'de', content: 'einige Inhalte'}
{name: 'Common issue #1', language: 'en', content: 'some content 1'},
{name: 'Common issue #2', language: 'en', content: 'some content 2'},
{name: 'Common issue #3', language: 'en', content: 'some content 3'},
{name: 'Häufiges Problem #1', language: 'de', content: 'einige Inhalte 1'},
{name: 'Häufiges Problem #2', language: 'de', content: 'einige Inhalte 2'}
]
};
}
@ -78,6 +78,16 @@ module.exports = [
};
}
},
{
path: '/ticket/seen',
time: 200,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/ticket/get',
time: 1000,
@ -96,6 +106,7 @@ module.exports = [
file: 'http://www.opensupports.com/some_file.zip',
language: 'en',
unread: false,
unreadStaff: true,
closed: false,
priority: 'medium',
author: {
@ -203,4 +214,4 @@ module.exports = [
};
}
}
];
];

View File

@ -71,6 +71,10 @@ export default {
'ASSIGN_TO_ME': 'Assign to me',
'UN_ASSIGN': 'Unassign',
'VIEW_TICKET': 'View Ticket',
'SELECT_CUSTOM_RESPONSE': 'Select a custom response...',
'WARNING': 'Warning',
'INFO': 'Information',
'ALL_DEPARTMENTS': 'All Departments',
//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.',
@ -80,6 +84,9 @@ 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.',
'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',
'MY_TICKETS_DESCRIPTION': 'Here you can view the tickets you are responsible for.',
'NEW_TICKETS_DESCRIPTION': 'Here you can view all the new tickets that are not assigned by anyone.',
'ALL_TICKETS_DESCRIPTION': 'Here you can view the tickets of the departments you are assigned.',
'TICKET_VIEW_DESCRIPTION': 'This ticket has been sent by a customer. Here you can respond or assign the ticket',
//ERRORS
@ -107,4 +114,4 @@ export default {
'OLD_PASSWORD_INCORRECT': 'Old password is incorrect',
'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.',
'WILL_DELETE_CUSTOM_RESPONSE': 'The custom response will be deleted.'
};
};

View File

@ -1,28 +1,92 @@
import _ from 'lodash';
import Reducer from 'reducers/reducer';
import sessionStore from 'lib-app/session-store';
class AdminDataReducer extends Reducer {
getInitialState() {
return {
customResponses: [],
customResponsesLoaded: false
customResponsesLoaded: false,
myTickets: [],
myTicketsLoaded: false,
newTickets: [],
newTicketsLoaded: false,
allTickets: [],
allTicketsLoaded: false
};
}
getTypeHandlers() {
return {
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
'SESSION_CHECKED': this.onSessionChecked,
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
'MY_TICKETS_PENDING': this.onMyTicketsPending,
'NEW_TICKETS_FULFILLED': this.onNewTicketsRetrieved,
'NEW_TICKETS_PENDING': this.onNewTicketsPending,
'ALL_TICKETS_FULFILLED': this.onAllTicketsRetrieved,
'ALL_TICKETS_PENDING': this.onAllTicketsPending
};
}
onCustomResponses(state, payload) {
sessionStore.setItem('customResponses', JSON.stringify(payload.data));
return _.extend({}, state, {
customResponses: payload.data,
customResponsesLoaded: true
});
}
onSessionChecked(state) {
const customResponses = sessionStore.getItem('customResponses');
return _.extend({}, state, {
customResponses: JSON.parse(customResponses),
customResponsesLoaded: true
});
}
onMyTicketsRetrieved(state, payload) {
return _.extend({}, state, {
myTickets: payload.data,
myTicketsLoaded: true
})
}
onMyTicketsPending(state) {
return _.extend({}, state, {
myTicketsLoaded: false
})
}
onNewTicketsRetrieved(state, payload) {
return _.extend({}, state, {
newTickets: payload.data,
newTicketsLoaded: true
})
}
onNewTicketsPending(state) {
return _.extend({}, state, {
newTicketsLoaded: false
})
}
onAllTicketsRetrieved(state, payload) {
return _.extend({}, state, {
allTickets: payload.data,
allTicketsLoaded: true
})
}
onAllTicketsPending(state) {
return _.extend({}, state, {
allTicketsLoaded: false
})
}
}
export default AdminDataReducer.getInstance();