mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-27 15:54:23 +02:00
Merge branch 'master' into OS-116-Ticket-events
# Conflicts: # tests/init.rb
This commit is contained in:
commit
c8ee619000
@ -10,5 +10,45 @@ export default {
|
|||||||
data: {}
|
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(page) {
|
||||||
|
return {
|
||||||
|
type: 'ALL_TICKETS',
|
||||||
|
payload: API.call({
|
||||||
|
path: '/staff/get-all-tickets',
|
||||||
|
data: {page}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
searchTickets(query, page) {
|
||||||
|
return {
|
||||||
|
type: 'ALL_TICKETS',
|
||||||
|
payload: API.call({
|
||||||
|
path: '/staff/search-tickets',
|
||||||
|
data: {query, page}
|
||||||
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -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;
|
||||||
})
|
})
|
||||||
|
188
client/src/app-components/__tests__/ticket-list-test.js
Normal file
188
client/src/app-components/__tests__/ticket-list-test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -17,7 +17,7 @@ class TicketEvent 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() {
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import i18n from 'lib-app/i18n';
|
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 Table from 'core-components/table';
|
||||||
import Button from 'core-components/button';
|
import Button from 'core-components/button';
|
||||||
import Tooltip from 'core-components/tooltip';
|
import Tooltip from 'core-components/tooltip';
|
||||||
import TicketInfo from 'app-components/ticket-info';
|
import DropDown from 'core-components/drop-down';
|
||||||
|
|
||||||
import DateTransformer from 'lib-core/date-transformer';
|
|
||||||
|
|
||||||
class TicketList extends React.Component {
|
class TicketList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
departments: React.PropTypes.array,
|
||||||
|
loading: React.PropTypes.bool,
|
||||||
|
ticketPath: React.PropTypes.string,
|
||||||
|
showDepartmentDropdown: React.PropTypes.bool,
|
||||||
tickets: React.PropTypes.arrayOf(React.PropTypes.object),
|
tickets: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
type: React.PropTypes.oneOf([
|
type: React.PropTypes.oneOf([
|
||||||
'primary',
|
'primary',
|
||||||
@ -19,18 +24,72 @@ class TicketList extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
showDepartmentDropdown: true,
|
||||||
|
loading: false,
|
||||||
tickets: [],
|
tickets: [],
|
||||||
|
departments: [],
|
||||||
|
ticketPath: '/dashboard/ticket/',
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selectedDepartment: 0
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="ticket-list">
|
<div className="ticket-list">
|
||||||
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} pageSize={10} comp={this.compareFunction} />
|
{(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartmentsDropDown() : null}
|
||||||
|
<Table {...this.getTableProps()} />
|
||||||
</div>
|
</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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableProps() {
|
||||||
|
return {
|
||||||
|
loading: this.props.loading,
|
||||||
|
headers: this.getTableHeaders(),
|
||||||
|
rows: this.getTableRows(),
|
||||||
|
pageSize: 10,
|
||||||
|
comp: this.compareFunction,
|
||||||
|
page: this.props.page,
|
||||||
|
pages: this.props.pages,
|
||||||
|
onPageChange: this.props.onPageChange
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getDepartments() {
|
||||||
|
let departments = this.props.departments.map((department) => {
|
||||||
|
return {content: department.name};
|
||||||
|
});
|
||||||
|
|
||||||
|
departments.unshift({
|
||||||
|
content: i18n('ALL_DEPARTMENTS')
|
||||||
|
});
|
||||||
|
|
||||||
|
return departments;
|
||||||
|
}
|
||||||
|
|
||||||
getTableHeaders() {
|
getTableHeaders() {
|
||||||
if (this.props.type == 'primary' ) {
|
if (this.props.type == 'primary' ) {
|
||||||
return [
|
return [
|
||||||
@ -92,7 +151,13 @@ class TicketList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTableRows() {
|
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) {
|
gerTicketTableObject(ticket) {
|
||||||
@ -105,7 +170,7 @@ class TicketList extends React.Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
title: (
|
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}
|
{titleText}
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
@ -137,8 +202,6 @@ class TicketList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compareFunction(row1, row2) {
|
compareFunction(row1, row2) {
|
||||||
let ans = 0;
|
|
||||||
|
|
||||||
if (row1.closed == row2.closed) {
|
if (row1.closed == row2.closed) {
|
||||||
if (row1.unread == row2.unread) {
|
if (row1.unread == row2.unread) {
|
||||||
let s1 = row1.date;
|
let s1 = row1.date;
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
.ticket-list {
|
.ticket-list {
|
||||||
|
|
||||||
|
&__department-selector {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
&__number {
|
&__number {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
@ -1,11 +1,10 @@
|
|||||||
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 SessionStore from 'lib-app/session-store';
|
||||||
import SessionActions from 'actions/session-actions';
|
|
||||||
|
|
||||||
import TicketEvent from 'app-components/ticket-event';
|
import TicketEvent from 'app-components/ticket-event';
|
||||||
import AreYouSure from 'app-components/are-you-sure';
|
import AreYouSure from 'app-components/are-you-sure';
|
||||||
@ -20,6 +19,7 @@ class TicketViewer extends React.Component {
|
|||||||
ticket: React.PropTypes.object,
|
ticket: React.PropTypes.object,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
editable: React.PropTypes.bool,
|
editable: React.PropTypes.bool,
|
||||||
|
customResponses: React.PropTypes.array,
|
||||||
assignmentAllowed: React.PropTypes.bool
|
assignmentAllowed: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,13 +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;
|
||||||
@ -58,8 +56,9 @@ class TicketViewer extends React.Component {
|
|||||||
</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>
|
||||||
|
{this.renderCustomResponses()}
|
||||||
<div className="ticket-viewer__response-field row">
|
<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" />
|
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
|
||||||
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
|
||||||
</Form>
|
</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) {
|
onDepartmentDropdownChanged(event) {
|
||||||
AreYouSure.openModal(null, this.changeDepartment.bind(this, event.index));
|
AreYouSure.openModal(null, this.changeDepartment.bind(this, event.index));
|
||||||
}
|
}
|
||||||
@ -242,6 +279,21 @@ class TicketViewer extends React.Component {
|
|||||||
}).then(this.onTicketModification.bind(this));
|
}).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
|
||||||
|
@ -72,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,78 @@
|
|||||||
import React from 'react';
|
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';
|
||||||
|
import SearchBox from 'core-components/search-box';
|
||||||
|
|
||||||
class AdminPanelAllTickets extends React.Component {
|
class AdminPanelAllTickets extends React.Component {
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
departments: [],
|
||||||
|
tickets: []
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
page: 1,
|
||||||
|
query: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(AdminDataAction.retrieveAllTickets());
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="admin-panel-my-tickets">
|
||||||
/admin/panel/tickets/all-tickets
|
<Header title={i18n('ALL_TICKETS')} description={i18n('ALL_TICKETS_DESCRIPTION')} />
|
||||||
|
<div className="admin-panel-my-tickets__search-box">
|
||||||
|
<SearchBox onSearch={this.onSearch.bind(this)} />
|
||||||
|
</div>
|
||||||
|
<TicketList {...this.getTicketListProps()}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTicketListProps() {
|
||||||
|
return {
|
||||||
|
showDepartmentDropdown: false,
|
||||||
|
departments: this.props.departments,
|
||||||
|
tickets: this.props.tickets,
|
||||||
|
type: 'secondary',
|
||||||
|
loading: this.props.loading,
|
||||||
|
ticketPath: '/admin/panel/tickets/view-ticket/',
|
||||||
|
onPageChange: this.onPageChange.bind(this),
|
||||||
|
page: this.state.page,
|
||||||
|
pages: this.props.pages
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearch(query) {
|
||||||
|
this.setState({query, page: 1});
|
||||||
|
this.props.dispatch(AdminDataAction.searchTickets(query));
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(event) {
|
||||||
|
this.setState({
|
||||||
|
page: event.target.value
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this.state.query) {
|
||||||
|
this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value));
|
||||||
|
} else {
|
||||||
|
this.props.dispatch(AdminDataAction.retrieveAllTickets(event.target.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminPanelAllTickets;
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
departments: store.session.userDepartments,
|
||||||
|
tickets: store.adminData.allTickets,
|
||||||
|
pages: store.adminData.allTicketsPages,
|
||||||
|
loading: !store.adminData.allTicketsLoaded
|
||||||
|
};
|
||||||
|
})(AdminPanelAllTickets);
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
.admin-panel-my-tickets {
|
||||||
|
&__search-box {
|
||||||
|
padding: 0 50px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,47 @@
|
|||||||
import React from 'react';
|
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 {
|
class AdminPanelMyTickets extends React.Component {
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
departments: [],
|
||||||
|
tickets: []
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(AdminDataAction.retrieveMyTickets());
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="admin-panel-my-tickets">
|
||||||
/admin/panel/tickets/my-tickets
|
<Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} />
|
||||||
|
<TicketList {...this.getProps()}/>
|
||||||
</div>
|
</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);
|
||||||
|
@ -1,14 +1,47 @@
|
|||||||
import React from 'react';
|
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 {
|
class AdminPanelNewTickets extends React.Component {
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
departments: [],
|
||||||
|
tickets: []
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(AdminDataAction.retrieveNewTickets());
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="admin-panel-my-tickets">
|
||||||
/admin/panel/tickets/new-tickets
|
<Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} />
|
||||||
|
<TicketList {...this.getProps()}/>
|
||||||
</div>
|
</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);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import API from 'lib-app/api-call';
|
import API from 'lib-app/api-call';
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
@ -62,19 +63,15 @@ class AdminPanelViewTicket extends React.Component {
|
|||||||
ticket: this.state.ticket,
|
ticket: this.state.ticket,
|
||||||
onChange: this.retrieveTicket.bind(this),
|
onChange: this.retrieveTicket.bind(this),
|
||||||
assignmentAllowed: true,
|
assignmentAllowed: true,
|
||||||
|
customResponses: this.props.customResponses,
|
||||||
editable: _.get(this.state.ticket, 'owner.id') === SessionStore.getUserData().id
|
editable: _.get(this.state.ticket, 'owner.id') === SessionStore.getUserData().id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveTicket() {
|
retrieveTicket() {
|
||||||
this.setState({
|
|
||||||
loading: true,
|
|
||||||
ticket: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
API.call({
|
API.call({
|
||||||
path: '/ticket/get',
|
path: '/ticket/get',
|
||||||
date: {
|
data: {
|
||||||
ticketNumber: this.props.params.ticketNumber
|
ticketNumber: this.props.params.ticketNumber
|
||||||
}
|
}
|
||||||
}).then(this.onRetrieveSuccess.bind(this)).catch(this.onRetrieveFail.bind(this))
|
}).then(this.onRetrieveSuccess.bind(this)).catch(this.onRetrieveFail.bind(this))
|
||||||
@ -85,6 +82,15 @@ class AdminPanelViewTicket extends React.Component {
|
|||||||
loading: false,
|
loading: false,
|
||||||
ticket: result.data
|
ticket: result.data
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(result.data.unreadStaff) {
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/seen',
|
||||||
|
data: {
|
||||||
|
ticketNumber: this.props.params.ticketNumber
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRetrieveFail() {
|
onRetrieveFail() {
|
||||||
@ -95,4 +101,8 @@ class AdminPanelViewTicket extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminPanelViewTicket;
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
customResponses: store.adminData.customResponses
|
||||||
|
};
|
||||||
|
})(AdminPanelViewTicket);
|
||||||
|
@ -15,6 +15,7 @@ const DropDown = require('core-components/drop-down');
|
|||||||
const Menu = require('core-components/menu');
|
const Menu = require('core-components/menu');
|
||||||
const Tooltip = require('core-components/tooltip');
|
const Tooltip = require('core-components/tooltip');
|
||||||
const Table = require('core-components/table');
|
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 dropDownItems = [{content: 'English'}, {content: 'Spanish'}, {content: 'German'}, {content: 'Portuguese'}, {content: 'Japanese'}];
|
||||||
let secondaryMenuItems = [
|
let secondaryMenuItems = [
|
||||||
@ -168,6 +169,12 @@ let DemoPage = React.createClass({
|
|||||||
return ans;
|
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;
|
||||||
|
@ -2,6 +2,9 @@ import React from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {connect} from 'react-redux';
|
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 SessionActions from 'actions/session-actions';
|
||||||
import TicketViewer from 'app-components/ticket-viewer';
|
import TicketViewer from 'app-components/ticket-viewer';
|
||||||
|
|
||||||
@ -11,16 +14,35 @@ class DashboardTicketPage extends React.Component {
|
|||||||
tickets: React.PropTypes.array
|
tickets: React.PropTypes.array
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
let ticket = this.getTicketData();
|
||||||
|
|
||||||
|
if(ticket.unread) {
|
||||||
|
API.call({
|
||||||
|
path: '/ticket/seen',
|
||||||
|
data: {
|
||||||
|
ticketNumber: ticket.ticketNumber
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let ticketView = i18n('NO_PERMISSION');
|
||||||
|
|
||||||
|
if(!_.isEmpty(this.getTicketData())) {
|
||||||
|
ticketView = <TicketViewer ticket={this.getTicketData()} onChange={this.retrieveUserData.bind(this)}/>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-ticket-page">
|
<div className="dashboard-ticket-page">
|
||||||
<TicketViewer ticket={this.getTicketData()} onChange={this.retrieveUserData.bind(this)}/>
|
{ticketView}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTicketData() {
|
getTicketData() {
|
||||||
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber});
|
return _.find(this.props.tickets, {ticketNumber: this.props.params.ticketNumber}) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveUserData() {
|
retrieveUserData() {
|
||||||
@ -32,4 +54,4 @@ export default connect((store) => {
|
|||||||
return {
|
return {
|
||||||
tickets: store.session.userTickets
|
tickets: store.session.userTickets
|
||||||
};
|
};
|
||||||
})(DashboardTicketPage);
|
})(DashboardTicketPage);
|
||||||
|
54
client/src/core-components/info-tooltip.js
Normal file
54
client/src/core-components/info-tooltip.js
Normal 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;
|
26
client/src/core-components/info-tooltip.scss
Normal file
26
client/src/core-components/info-tooltip.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
client/src/core-components/search-box.js
Normal file
41
client/src/core-components/search-box.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Input from 'core-components/input';
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
import keyCode from 'keycode';
|
||||||
|
|
||||||
|
class SearchBox extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onSearch: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
value: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="search-box">
|
||||||
|
<Input className="search-box__text" value={this.state.value} onChange={this.onChange.bind(this)} onKeyDown={this.onKeyDown.bind(this)} />
|
||||||
|
<span className="search-box__icon">
|
||||||
|
<Icon name="search" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(event) {
|
||||||
|
this.setState({
|
||||||
|
value: event.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
if(keyCode(event) === 'enter' && this.props.onSearch) {
|
||||||
|
this.props.onSearch(this.state.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBox;
|
21
client/src/core-components/search-box.scss
Normal file
21
client/src/core-components/search-box.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
@import "../scss/vars";
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
position: relative;
|
||||||
|
color: $dark-grey;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
width: 100%;
|
||||||
|
font-size: $font-size--lg;
|
||||||
|
|
||||||
|
.input__text {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
}
|
@ -13,9 +13,12 @@ class Table extends React.Component {
|
|||||||
className: React.PropTypes.string
|
className: React.PropTypes.string
|
||||||
})),
|
})),
|
||||||
rows: React.PropTypes.arrayOf(React.PropTypes.object),
|
rows: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
pageSize: React.PropTypes.number,
|
|
||||||
loading: React.PropTypes.bool,
|
loading: React.PropTypes.bool,
|
||||||
type: React.PropTypes.oneOf(['default']),
|
type: React.PropTypes.oneOf(['default']),
|
||||||
|
page: React.PropTypes.number,
|
||||||
|
pages: React.PropTypes.number,
|
||||||
|
pageSize: React.PropTypes.number,
|
||||||
|
onPageChange: React.PropTypes.func,
|
||||||
comp: React.PropTypes.func
|
comp: React.PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ class Table extends React.Component {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{(this.props.loading) ? this.renderLoading() : null}
|
{(this.props.loading) ? this.renderLoading() : null}
|
||||||
{(this.props.pageSize && this.props.rows.length > this.props.pageSize) ? this.renderNavigation() : null}
|
{this.renderPagination()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -59,8 +62,8 @@ class Table extends React.Component {
|
|||||||
|
|
||||||
renderRow(row, index) {
|
renderRow(row, index) {
|
||||||
const headersKeys = this.props.headers.map(header => header.key);
|
const headersKeys = this.props.headers.map(header => header.key);
|
||||||
const minIndex = this.props.pageSize * (this.state.page - 1);
|
const minIndex = this.props.pageSize * ((this.props.page) ? 0 : this.state.page - 1);
|
||||||
const maxIndex = this.props.pageSize * this.state.page;
|
const maxIndex = this.props.pageSize * ((this.props.page) ? 1 : this.state.page);
|
||||||
const shouldRenderRow = !this.props.pageSize || (index >= minIndex && index < maxIndex);
|
const shouldRenderRow = !this.props.pageSize || (index >= minIndex && index < maxIndex);
|
||||||
|
|
||||||
return (shouldRenderRow) ? (
|
return (shouldRenderRow) ? (
|
||||||
@ -81,12 +84,15 @@ class Table extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderPagination() {
|
||||||
|
return (this.props.pages || (this.props.pageSize && this.props.rows.length > this.props.pageSize)) ? this.renderNavigation() : null
|
||||||
|
}
|
||||||
|
|
||||||
renderNavigation() {
|
renderNavigation() {
|
||||||
const pages = Math.ceil(this.props.rows.length / this.props.pageSize) + 1;
|
const items = _.range(1, this.getPages()).map((index) => {return {content: index};});
|
||||||
const items = _.range(1, pages).map((index) => {return {content: index};});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu className="table__navigation" type="navigation" items={items} onItemClick={this.onNavigationItemClick.bind(this)}/>
|
<Menu className="table__navigation" type="navigation" items={items} selectedIndex={this.getPageNumber() - 1} onItemClick={this.onNavigationItemClick.bind(this)} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +108,10 @@ class Table extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
page: index + 1
|
page: index + 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(this.props.onPageChange) {
|
||||||
|
this.props.onPageChange({target: {value: index + 1}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getRowClass(row) {
|
getRowClass(row) {
|
||||||
@ -114,11 +124,19 @@ class Table extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRows() {
|
getRows() {
|
||||||
let v = _.clone(this.props.rows);
|
let sortedRows = _.clone(this.props.rows);
|
||||||
v.sort(this.props.comp);
|
sortedRows.sort(this.props.comp);
|
||||||
return v;
|
|
||||||
|
return sortedRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPages() {
|
||||||
|
return (this.props.pages !== undefined) ? this.props.pages + 1 : Math.ceil(this.props.rows.length / this.props.pageSize) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPageNumber() {
|
||||||
|
return (this.props.page !== undefined) ? this.props.page: this.state.page;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
@ -50,7 +50,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__loading-wrapper {
|
&__loading-wrapper {
|
||||||
min-height: 200px;
|
min-height: 380px;
|
||||||
|
position: relative;
|
||||||
|
background-color: $grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__loading {
|
&__loading {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
background-color: #F7F7F7;
|
background-color: #F7F7F7;
|
||||||
color: black;
|
color: black;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pointer {
|
&__pointer {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
path: '/staff/get',
|
path: '/staff/get',
|
||||||
@ -8,16 +10,572 @@ module.exports = [
|
|||||||
data: {
|
data: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'staff@opensupports.com',
|
email: 'staff@opensupports.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
level: 1,
|
level: 1,
|
||||||
staff: true,
|
staff: true,
|
||||||
departments: [
|
departments: [
|
||||||
{id: 1, name: 'Sales Support'},
|
{id: 1, name: 'Sales Support'},
|
||||||
{id: 2, name: 'Technical Issues'},
|
{id: 2, name: 'Technical Issues'}
|
||||||
{id: 3, name: 'System and Administration'}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'UN_ASSIGN',
|
||||||
|
date: '20150410',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'DEPARTMENT_CHANGED',
|
||||||
|
date: '20150411',
|
||||||
|
content: 'System support',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'UN_ASSIGN',
|
||||||
|
date: '20150410',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'DEPARTMENT_CHANGED',
|
||||||
|
date: '20150411',
|
||||||
|
content: 'System support',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'UN_ASSIGN',
|
||||||
|
date: '20150410',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
|
staff: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'DEPARTMENT_CHANGED',
|
||||||
|
date: '20150411',
|
||||||
|
content: 'System support',
|
||||||
|
author: {
|
||||||
|
name: 'Emilia Clarke',
|
||||||
|
email: 'jobs@steve.com',
|
||||||
|
profilePic: 'http://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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://www.opensupports.com/profilepic.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: 1000,
|
||||||
|
response: function () {
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
data: {
|
||||||
|
tickets: _.range(0, 10).map(() => {
|
||||||
|
return {
|
||||||
|
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: []
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
pages: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/staff/search-tickets',
|
||||||
|
time: 300,
|
||||||
|
response: function () {
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
data: {
|
||||||
|
tickets: _.range(0, 10).map(() => {
|
||||||
|
return {
|
||||||
|
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: []
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
pages: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
@ -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'}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -78,6 +78,16 @@ module.exports = [
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/ticket/seen',
|
||||||
|
time: 200,
|
||||||
|
response: function () {
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
data: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/ticket/get',
|
path: '/ticket/get',
|
||||||
time: 1000,
|
time: 1000,
|
||||||
@ -96,6 +106,7 @@ module.exports = [
|
|||||||
file: 'http://www.opensupports.com/some_file.zip',
|
file: 'http://www.opensupports.com/some_file.zip',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
unread: false,
|
unread: false,
|
||||||
|
unreadStaff: true,
|
||||||
closed: false,
|
closed: false,
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
author: {
|
author: {
|
||||||
@ -112,7 +123,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -123,7 +134,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -133,7 +144,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -144,7 +155,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -165,7 +176,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -185,7 +196,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -203,4 +214,4 @@ module.exports = [
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -165,7 +165,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -176,7 +176,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -186,7 +186,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -197,7 +197,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -218,7 +218,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -238,7 +238,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -281,7 +281,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -292,7 +292,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -302,7 +302,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -313,7 +313,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -334,7 +334,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -354,7 +354,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -397,7 +397,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -408,7 +408,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -418,7 +418,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -429,7 +429,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -450,7 +450,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -470,7 +470,7 @@ module.exports = [
|
|||||||
author: {
|
author: {
|
||||||
name: 'Emilia Clarke',
|
name: 'Emilia Clarke',
|
||||||
email: 'jobs@steve.com',
|
email: 'jobs@steve.com',
|
||||||
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
|
profilePic: 'http://www.opensupports.com/profilepic.jpg',
|
||||||
staff: true
|
staff: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -71,6 +71,10 @@ export default {
|
|||||||
'ASSIGN_TO_ME': 'Assign to me',
|
'ASSIGN_TO_ME': 'Assign to me',
|
||||||
'UN_ASSIGN': 'Unassign',
|
'UN_ASSIGN': 'Unassign',
|
||||||
'VIEW_TICKET': 'View Ticket',
|
'VIEW_TICKET': 'View Ticket',
|
||||||
|
'SELECT_CUSTOM_RESPONSE': 'Select a custom response...',
|
||||||
|
'WARNING': 'Warning',
|
||||||
|
'INFO': 'Information',
|
||||||
|
'ALL_DEPARTMENTS': 'All Departments',
|
||||||
|
|
||||||
//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.',
|
||||||
@ -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.',
|
'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',
|
||||||
|
'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',
|
'TICKET_VIEW_DESCRIPTION': 'This ticket has been sent by a customer. Here you can respond or assign the ticket',
|
||||||
|
|
||||||
//ERRORS
|
//ERRORS
|
||||||
@ -107,4 +114,4 @@ export default {
|
|||||||
'OLD_PASSWORD_INCORRECT': 'Old password is incorrect',
|
'OLD_PASSWORD_INCORRECT': 'Old password is incorrect',
|
||||||
'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.',
|
'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.',
|
||||||
'WILL_DELETE_CUSTOM_RESPONSE': 'The custom response will be deleted.'
|
'WILL_DELETE_CUSTOM_RESPONSE': 'The custom response will be deleted.'
|
||||||
};
|
};
|
||||||
|
@ -1,28 +1,93 @@
|
|||||||
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 {
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
customResponses: [],
|
customResponses: [],
|
||||||
customResponsesLoaded: false
|
customResponsesLoaded: false,
|
||||||
|
myTickets: [],
|
||||||
|
myTicketsLoaded: false,
|
||||||
|
newTickets: [],
|
||||||
|
newTicketsLoaded: false,
|
||||||
|
allTickets: [],
|
||||||
|
allTicketsLoaded: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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) {
|
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) {
|
||||||
|
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.tickets,
|
||||||
|
allTicketsPages: payload.data.pages,
|
||||||
|
allTicketsLoaded: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onAllTicketsPending(state) {
|
||||||
|
return _.extend({}, state, {
|
||||||
|
allTicketsLoaded: false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminDataReducer.getInstance();
|
export default AdminDataReducer.getInstance();
|
@ -4,6 +4,8 @@ require_once 'staff/assign-ticket.php';
|
|||||||
require_once 'staff/un-assign-ticket.php';
|
require_once 'staff/un-assign-ticket.php';
|
||||||
require_once 'staff/get-tickets.php';
|
require_once 'staff/get-tickets.php';
|
||||||
require_once 'staff/get-new-tickets.php';
|
require_once 'staff/get-new-tickets.php';
|
||||||
|
require_once 'staff/get-all-tickets.php';
|
||||||
|
require_once 'staff/search-tickets.php';
|
||||||
|
|
||||||
$systemControllerGroup = new ControllerGroup();
|
$systemControllerGroup = new ControllerGroup();
|
||||||
$systemControllerGroup->setGroupPath('/staff');
|
$systemControllerGroup->setGroupPath('/staff');
|
||||||
@ -13,5 +15,7 @@ $systemControllerGroup->addController(new AssignStaffController);
|
|||||||
$systemControllerGroup->addController(new UnAssignStaffController);
|
$systemControllerGroup->addController(new UnAssignStaffController);
|
||||||
$systemControllerGroup->addController(new GetTicketStaffController);
|
$systemControllerGroup->addController(new GetTicketStaffController);
|
||||||
$systemControllerGroup->addController(new GetNewTicketsStaffController);
|
$systemControllerGroup->addController(new GetNewTicketsStaffController);
|
||||||
|
$systemControllerGroup->addController(new GetAllTicketsStaffController);
|
||||||
|
$systemControllerGroup->addController(new SearchTicketStaffController);
|
||||||
|
|
||||||
$systemControllerGroup->finalize();
|
$systemControllerGroup->finalize();
|
54
server/controllers/staff/get-all-tickets.php
Normal file
54
server/controllers/staff/get-all-tickets.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class GetAllTicketsStaffController extends Controller {
|
||||||
|
const PATH = '/get-all-tickets';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return[
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'page' => [
|
||||||
|
'validation' => DataValidator::numeric(),
|
||||||
|
'error' => ERRORS::INVALID_PAGE
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
|
||||||
|
Response::respondSuccess([
|
||||||
|
'tickets' => $this->getTicketList()->toArray(),
|
||||||
|
'pages' => $this->getTotalPages()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTicketList() {
|
||||||
|
$page = Controller::request('page');
|
||||||
|
|
||||||
|
$query = $this->getStaffDepartmentsQueryFilter();
|
||||||
|
$query .= 'ORDER BY id DESC LIMIT 10 OFFSET ' . (($page-1)*10);
|
||||||
|
|
||||||
|
return Ticket::find($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTotalPages() {
|
||||||
|
$query = $this->getStaffDepartmentsQueryFilter();
|
||||||
|
|
||||||
|
return ceil(Ticket::count($query) / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStaffDepartmentsQueryFilter() {
|
||||||
|
$user = Controller::getLoggedUser();
|
||||||
|
|
||||||
|
$query = ' (';
|
||||||
|
foreach ($user->sharedDepartmentList as $department) {
|
||||||
|
$query .= 'department_id=' . $department->id . ' OR ';
|
||||||
|
}
|
||||||
|
$query = substr($query,0,-3);
|
||||||
|
$query .= ') ';
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
74
server/controllers/staff/search-tickets.php
Normal file
74
server/controllers/staff/search-tickets.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
use Respect\Validation\Validator as DataValidator;
|
||||||
|
|
||||||
|
class SearchTicketStaffController extends Controller {
|
||||||
|
const PATH = '/search-tickets';
|
||||||
|
|
||||||
|
public function validations() {
|
||||||
|
return[
|
||||||
|
'permission' => 'staff_1',
|
||||||
|
'requestData' => [
|
||||||
|
'query' => [
|
||||||
|
'validation' => DataValidator::alpha(),
|
||||||
|
'error' => ERRORS::INVALID_QUERY
|
||||||
|
],
|
||||||
|
'page' => [
|
||||||
|
'validation' => DataValidator::numeric(),
|
||||||
|
'error' => ERRORS::INVALID_PAGE
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handler() {
|
||||||
|
Response::respondSuccess([
|
||||||
|
'tickets' => $this->getTicketList()->toArray(),
|
||||||
|
'pages' => $this->getTotalPages()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTicketList() {
|
||||||
|
$query = $this->getSearchQuery();
|
||||||
|
|
||||||
|
return Ticket::find($query, [
|
||||||
|
Controller::request('query') . '%',
|
||||||
|
'%' . Controller::request('query') . '%',
|
||||||
|
Controller::request('query') . '%'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSearchQuery() {
|
||||||
|
$page = Controller::request('page');
|
||||||
|
|
||||||
|
$query = " (title LIKE ? OR title LIKE ?) AND ";
|
||||||
|
$query .= $this->getStaffDepartmentsQueryFilter();
|
||||||
|
$query .= "ORDER BY CASE WHEN (title LIKE ?) THEN 1 ELSE 2 END ASC LIMIT 10 OFFSET " . (($page-1)*10);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTotalPages() {
|
||||||
|
$query = " (title LIKE ? OR title LIKE ?) AND ";
|
||||||
|
$query .= $this->getStaffDepartmentsQueryFilter();
|
||||||
|
|
||||||
|
$ticketQuantity = Ticket::count($query, [
|
||||||
|
Controller::request('query') . '%',
|
||||||
|
'%' . Controller::request('query') . '%'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return ceil($ticketQuantity / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStaffDepartmentsQueryFilter() {
|
||||||
|
$user = Controller::getLoggedUser();
|
||||||
|
|
||||||
|
$query = ' (';
|
||||||
|
foreach ($user->sharedDepartmentList as $department) {
|
||||||
|
$query .= 'department_id=' . $department->id . ' OR ';
|
||||||
|
}
|
||||||
|
$query = substr($query, 0, -3);
|
||||||
|
$query .= ') ';
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
@ -88,7 +88,7 @@ class InitSettingsController extends Controller {
|
|||||||
'name' => 'Emilia Clarke',
|
'name' => 'Emilia Clarke',
|
||||||
'email' => 'staff@opensupports.com',
|
'email' => 'staff@opensupports.com',
|
||||||
'password' => Hashing::hashPassword('staff'),
|
'password' => Hashing::hashPassword('staff'),
|
||||||
'profilePic' => 'http://i65.tinypic.com/9bep95.jpg',
|
'profilePic' => 'http://www.opensupports.com/profilepic.jpg',
|
||||||
'level' => 3,
|
'level' => 3,
|
||||||
'sharedDepartmentList' => Department::getAll(),
|
'sharedDepartmentList' => Department::getAll(),
|
||||||
'sharedTicketList' => []
|
'sharedTicketList' => []
|
||||||
|
@ -16,7 +16,7 @@ class CreateController extends Controller {
|
|||||||
'permission' => 'user',
|
'permission' => 'user',
|
||||||
'requestData' => [
|
'requestData' => [
|
||||||
'title' => [
|
'title' => [
|
||||||
'validation' => DataValidator::length(3, 30),
|
'validation' => DataValidator::length(10, 200),
|
||||||
'error' => ERRORS::INVALID_TITLE
|
'error' => ERRORS::INVALID_TITLE
|
||||||
],
|
],
|
||||||
'content' => [
|
'content' => [
|
||||||
|
@ -19,4 +19,6 @@ class ERRORS {
|
|||||||
const INVALID_LANGUAGE = 'INVALID_LANGUAGE';
|
const INVALID_LANGUAGE = 'INVALID_LANGUAGE';
|
||||||
const TICKET_ALREADY_ASSIGNED = 'TICKET_ALREADY_ASSIGNED';
|
const TICKET_ALREADY_ASSIGNED = 'TICKET_ALREADY_ASSIGNED';
|
||||||
const INVALID_PRIORITY = 'INVALID_PRIORITY';
|
const INVALID_PRIORITY = 'INVALID_PRIORITY';
|
||||||
|
const INVALID_PAGE = 'INVALID_PAGE';
|
||||||
|
const INVALID_QUERY = 'INVALID_QUERY';
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@ abstract class DataStore {
|
|||||||
|
|
||||||
return ($bean) ? new static($bean) : new NullDataStore();
|
return ($bean) ? new static($bean) : new NullDataStore();
|
||||||
}
|
}
|
||||||
public static function count() {
|
|
||||||
return RedBean::count(static::TABLE);
|
public static function count($addSQL = '', $bindings = array()) {
|
||||||
|
return RedBean::count(static::TABLE, $addSQL, $bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAll() {
|
public static function getAll() {
|
||||||
@ -30,10 +31,10 @@ abstract class DataStore {
|
|||||||
|
|
||||||
return $dataStoreList;
|
return $dataStoreList;
|
||||||
}
|
}
|
||||||
public static function find($query) {
|
public static function find($query = '', $matches = []) {
|
||||||
$beanList = RedBean::find(static::TABLE,$query);
|
$beanList = RedBean::find(static::TABLE, $query, $matches);
|
||||||
|
|
||||||
return DataStoreList::getList(ucfirst(static::TABLE),$beanList);
|
return DataStoreList::getList(ucfirst(static::TABLE), $beanList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function validateProp($propToValidate) {
|
private static function validateProp($propToValidate) {
|
||||||
|
@ -45,7 +45,7 @@ class Ticket extends DataStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function generateUniqueTicketNumber() {
|
public function generateUniqueTicketNumber() {
|
||||||
$ticketQuantity = Ticket::count('ticket');
|
$ticketQuantity = Ticket::count();
|
||||||
$minValue = 100000;
|
$minValue = 100000;
|
||||||
$maxValue = 999999;
|
$maxValue = 999999;
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ class Ticket extends DataStore {
|
|||||||
$ticketNumber = Hashing::getRandomTicketNumber($minValue, $maxValue);
|
$ticketNumber = Hashing::getRandomTicketNumber($minValue, $maxValue);
|
||||||
} else {
|
} else {
|
||||||
$firstTicketNumber = Ticket::getTicket(1)->ticketNumber;
|
$firstTicketNumber = Ticket::getTicket(1)->ticketNumber;
|
||||||
$gap = 176611;
|
$gap = 176611; //TODO: USE RANDOM PRIME INSTEAD
|
||||||
|
|
||||||
$ticketNumber = ($firstTicketNumber - $minValue + $ticketQuantity * $gap) % ($maxValue - $minValue + 1) + $minValue;
|
$ticketNumber = ($firstTicketNumber - $minValue + $ticketQuantity * $gap) % ($maxValue - $minValue + 1) + $minValue;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ require './staff/un-assign-ticket.rb'
|
|||||||
require './staff/get-tickets.rb'
|
require './staff/get-tickets.rb'
|
||||||
require './ticket/change-priority.rb'
|
require './ticket/change-priority.rb'
|
||||||
require './staff/get-new-tickets.rb'
|
require './staff/get-new-tickets.rb'
|
||||||
|
require './staff/get-all-tickets.rb'
|
||||||
require './ticket/events.rb'
|
require './ticket/events.rb'
|
||||||
|
|
||||||
|
|
||||||
|
98
tests/staff/get-all-tickets.rb
Normal file
98
tests/staff/get-all-tickets.rb
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
describe 'Retrieve all tickets' do
|
||||||
|
describe '/staff/get-all-tickets' do
|
||||||
|
Scripts.login('login@os4.com', 'loginpass')
|
||||||
|
|
||||||
|
def createTicket(title)
|
||||||
|
request('/ticket/create',{
|
||||||
|
title: title,
|
||||||
|
content: 'The north remembers',
|
||||||
|
departmentId: 1,
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return last tickets tickets' do
|
||||||
|
createTicket('Integer sit amet tellus cursus')
|
||||||
|
createTicket('consequat tortor sed')
|
||||||
|
createTicket('Fusce lacinia felis quis molestie pellentesque')
|
||||||
|
createTicket('Aliquam fringilla dapibus lacus')
|
||||||
|
createTicket('Aenean enim orci')
|
||||||
|
createTicket('luctus in sagittis non')
|
||||||
|
createTicket('consectetur at velit')
|
||||||
|
createTicket('Etiam et maximus quam')
|
||||||
|
createTicket('Donec facilisis pelleipsumntesque feugiat')
|
||||||
|
createTicket('Cras gravida bibendum vehicula')
|
||||||
|
createTicket('Fusce venenatis iaculis commodo')
|
||||||
|
createTicket('quis vulputate lectus feugiat eu')
|
||||||
|
createTicket('ipsum Aenean maximus quis leo et eleifend')
|
||||||
|
createTicket('In vel ex semper nisl sollicitudin')
|
||||||
|
createTicket('volutpat vel nec enim')
|
||||||
|
createTicket('Ut semper viverra nulla')
|
||||||
|
createTicket('Duis consequat nec metus a vestibulum')
|
||||||
|
createTicket('Vestibulum porta justo id sem bibendum lacinia')
|
||||||
|
createTicket('Phasellus erat ipsum')
|
||||||
|
createTicket('imperdiet vel auctor sed')
|
||||||
|
createTicket('placerat id velit')
|
||||||
|
createTicket('Quisque egestas ipsum')
|
||||||
|
|
||||||
|
request('/user/logout')
|
||||||
|
Scripts.login($staff[:email], $staff[:password], true)
|
||||||
|
response = request('/staff/get-all-tickets', {
|
||||||
|
page: 1,
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
|
||||||
|
(response['status']).should.equal('success')
|
||||||
|
(response['data']['pages']).should.equal(4)
|
||||||
|
(response['data']['tickets'].size).should.equal(10)
|
||||||
|
(response['data']['tickets'][0]['title']).should.equal('Quisque egestas ipsum')
|
||||||
|
(response['data']['tickets'][1]['title']).should.equal('placerat id velit')
|
||||||
|
(response['data']['tickets'][2]['title']).should.equal('imperdiet vel auctor sed')
|
||||||
|
(response['data']['tickets'][3]['title']).should.equal('Phasellus erat ipsum')
|
||||||
|
(response['data']['tickets'][4]['title']).should.equal('Vestibulum porta justo id sem bibendum lacinia')
|
||||||
|
(response['data']['tickets'][5]['title']).should.equal('Duis consequat nec metus a vestibulum')
|
||||||
|
(response['data']['tickets'][6]['title']).should.equal('Ut semper viverra nulla')
|
||||||
|
(response['data']['tickets'][7]['title']).should.equal('volutpat vel nec enim')
|
||||||
|
(response['data']['tickets'][8]['title']).should.equal('In vel ex semper nisl sollicitudin')
|
||||||
|
(response['data']['tickets'][9]['title']).should.equal('ipsum Aenean maximus quis leo et eleifend')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should work with pagination' do
|
||||||
|
response = request('/staff/get-all-tickets', {
|
||||||
|
page: 2,
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
|
||||||
|
(response['status']).should.equal('success')
|
||||||
|
(response['data']['pages']).should.equal(4)
|
||||||
|
(response['data']['tickets'].size).should.equal(10)
|
||||||
|
(response['data']['tickets'][0]['title']).should.equal('quis vulputate lectus feugiat eu')
|
||||||
|
(response['data']['tickets'][1]['title']).should.equal('Fusce venenatis iaculis commodo')
|
||||||
|
(response['data']['tickets'][2]['title']).should.equal('Cras gravida bibendum vehicula')
|
||||||
|
(response['data']['tickets'][3]['title']).should.equal('Donec facilisis pelleipsumntesque feugiat')
|
||||||
|
(response['data']['tickets'][4]['title']).should.equal('Etiam et maximus quam')
|
||||||
|
(response['data']['tickets'][5]['title']).should.equal('consectetur at velit')
|
||||||
|
(response['data']['tickets'][6]['title']).should.equal('luctus in sagittis non')
|
||||||
|
(response['data']['tickets'][7]['title']).should.equal('Aenean enim orci')
|
||||||
|
(response['data']['tickets'][8]['title']).should.equal('Aliquam fringilla dapibus lacus')
|
||||||
|
(response['data']['tickets'][9]['title']).should.equal('Fusce lacinia felis quis molestie pellentesque')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '/staff/search-tickets' do
|
||||||
|
response = request('/staff/search-tickets', {
|
||||||
|
query: 'ipsum',
|
||||||
|
page: 1,
|
||||||
|
csrf_userid: $csrf_userid,
|
||||||
|
csrf_token: $csrf_token
|
||||||
|
})
|
||||||
|
|
||||||
|
(response['status']).should.equal('success')
|
||||||
|
(response['data']['pages']).should.equal(4)
|
||||||
|
(response['data']['tickets'].size).should.equal(10)
|
||||||
|
(response['data']['tickets'][0]['title']).should.equal('ipsum Aenean maximus quis leo et eleifend')
|
||||||
|
end
|
||||||
|
end
|
@ -29,8 +29,11 @@ describe '/ticket/create' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'should fail if title is very long' do
|
it 'should fail if title is very long' do
|
||||||
|
long_text = ''
|
||||||
|
300.times {long_text << 'a'}
|
||||||
|
|
||||||
result = request('/ticket/create',{
|
result = request('/ticket/create',{
|
||||||
title: 'I WISH I WAS THE MONSTER YOU THINK I AM. -Tyrion',
|
title: long_text,
|
||||||
departmentId: 1,
|
departmentId: 1,
|
||||||
csrf_userid: $csrf_userid,
|
csrf_userid: $csrf_userid,
|
||||||
csrf_token: $csrf_token
|
csrf_token: $csrf_token
|
||||||
|
Loading…
x
Reference in New Issue
Block a user