diff --git a/client/src/actions/admin-data-actions.js b/client/src/actions/admin-data-actions.js index 624f0b82..f9ac73f7 100644 --- a/client/src/actions/admin-data-actions.js +++ b/client/src/actions/admin-data-actions.js @@ -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: {} + }) + }; } }; \ No newline at end of file diff --git a/client/src/actions/session-actions.js b/client/src/actions/session-actions.js index 08c95be8..2d1b7995 100644 --- a/client/src/actions/session-actions.js +++ b/client/src/actions/session-actions.js @@ -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; }) diff --git a/client/src/app-components/__tests__/ticket-list-test.js b/client/src/app-components/__tests__/ticket-list-test.js new file mode 100644 index 00000000..8e591a48 --- /dev/null +++ b/client/src/app-components/__tests__/ticket-list-test.js @@ -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( + + ); + + 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); + }); + }); +}); \ No newline at end of file diff --git a/client/src/app-components/ticket-event.js b/client/src/app-components/ticket-event.js index dd99b24c..3130b91a 100644 --- a/client/src/app-components/ticket-event.js +++ b/client/src/app-components/ticket-event.js @@ -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() { diff --git a/client/src/app-components/ticket-list.js b/client/src/app-components/ticket-list.js index 14b8a99f..3f9b2e61 100644 --- a/client/src/app-components/ticket-list.js +++ b/client/src/app-components/ticket-list.js @@ -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 (
- + {(this.props.type === 'secondary') ? this.renderDepartmentsDropDown() : null} +
); } + renderDepartmentsDropDown() { + return ( +
+ +
+ ); + } + + 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 { ), title: ( - ), @@ -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; diff --git a/client/src/app-components/tiket-list.scss b/client/src/app-components/ticket-list.scss similarity index 92% rename from client/src/app-components/tiket-list.scss rename to client/src/app-components/ticket-list.scss index 2355c6e4..416d55c5 100644 --- a/client/src/app-components/tiket-list.scss +++ b/client/src/app-components/ticket-list.scss @@ -2,6 +2,10 @@ .ticket-list { + &__department-selector { + margin-bottom: 25px; + } + &__number { text-align: left; } diff --git a/client/src/app-components/ticket-viewer.js b/client/src/app-components/ticket-viewer.js index d45dd4b4..536730f3 100644 --- a/client/src/app-components/ticket-viewer.js +++ b/client/src/app-components/ticket-viewer.js @@ -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 {
{i18n('RESPOND')}
+ {this.renderCustomResponses()}
-
+ {i18n('RESPOND_TICKET')} @@ -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 = ( +
+ +
+ ); + } + + 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 diff --git a/client/src/app-components/ticket-viewer.scss b/client/src/app-components/ticket-viewer.scss index c4ec3865..0b885aa2 100644 --- a/client/src/app-components/ticket-viewer.scss +++ b/client/src/app-components/ticket-viewer.scss @@ -72,5 +72,11 @@ padding: 20px; text-align: left; } + + &-custom { + background-color: $very-light-grey; + padding: 20px 0 0 20px; + text-align: left; + } } } \ No newline at end of file diff --git a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js index 93a4f327..490a15cf 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js @@ -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 ( -
- /admin/panel/tickets/all-tickets +
+
+
); } + + 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; \ No newline at end of file +export default connect((store) => { + return { + departments: store.session.userDepartments, + tickets: store.adminData.allTickets, + loading: !store.adminData.allTicketsLoaded + }; +})(AdminPanelAllTickets); diff --git a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js index 03a0ffa2..512454b1 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js @@ -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 ( -
- /admin/panel/tickets/my-tickets +
+
+
); } + + 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; \ No newline at end of file +export default connect((store) => { + return { + departments: store.session.userDepartments, + tickets: store.adminData.myTickets, + loading: !store.adminData.myTicketsLoaded + }; +})(AdminPanelMyTickets); diff --git a/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js index 42d250a8..819e9b56 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js @@ -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 ( -
- /admin/panel/tickets/new-tickets +
+
+
); } + + 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; \ No newline at end of file +export default connect((store) => { + return { + departments: store.session.userDepartments, + tickets: store.adminData.newTickets, + loading: !store.adminData.newTicketsLoaded + }; +})(AdminPanelNewTickets); diff --git a/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js b/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js index 141938f9..5d2583ec 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js +++ b/client/src/app/admin/panel/tickets/admin-panel-view-ticket.js @@ -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; \ No newline at end of file +export default connect((store) => { + return { + customResponses: store.adminData.customResponses + }; +})(AdminPanelViewTicket); diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 1d5da38f..ef679cbd 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -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: ( + + ) } ], @@ -197,4 +204,4 @@ let DemoPage = React.createClass({ } }); -export default DemoPage; \ No newline at end of file +export default DemoPage; diff --git a/client/src/app/main/dashboard/dashboard-ticket/dashboard-ticket-page.js b/client/src/app/main/dashboard/dashboard-ticket/dashboard-ticket-page.js index c6ea17bc..959fe32f 100644 --- a/client/src/app/main/dashboard/dashboard-ticket/dashboard-ticket-page.js +++ b/client/src/app/main/dashboard/dashboard-ticket/dashboard-ticket-page.js @@ -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 = ; + } + return (
- + {ticketView}
); } 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); \ No newline at end of file +})(DashboardTicketPage); diff --git a/client/src/core-components/info-tooltip.js b/client/src/core-components/info-tooltip.js new file mode 100644 index 00000000..92c6ca24 --- /dev/null +++ b/client/src/core-components/info-tooltip.js @@ -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 ( +
+ + + + + +
+ ); + } + + renderText() { + let message = (this.props.type === 'default') ? i18n('INFO') : i18n('WARNING'); + return ( +
+
+ {message} +
+ {this.props.text} +
+ ); + } + + getClass() { + let classes = { + 'info-tooltip': true, + 'info-tooltip_warning': (this.props.type === 'warning') + }; + + return classNames(classes); + } +} + +export default InfoTooltip; diff --git a/client/src/core-components/info-tooltip.scss b/client/src/core-components/info-tooltip.scss new file mode 100644 index 00000000..63691bad --- /dev/null +++ b/client/src/core-components/info-tooltip.scss @@ -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; + } + } + } +} diff --git a/client/src/core-components/table.scss b/client/src/core-components/table.scss index 62dd409c..54a73615 100644 --- a/client/src/core-components/table.scss +++ b/client/src/core-components/table.scss @@ -51,6 +51,8 @@ &__loading-wrapper { min-height: 200px; + position: relative; + background-color: $grey; } &__loading { diff --git a/client/src/core-components/tooltip.scss b/client/src/core-components/tooltip.scss index 50bd126d..bbe42bdb 100644 --- a/client/src/core-components/tooltip.scss +++ b/client/src/core-components/tooltip.scss @@ -17,6 +17,7 @@ background-color: #F7F7F7; color: black; padding: 10px; + z-index: 1000; } &__pointer { diff --git a/client/src/data/fixtures/staff-fixtures.js b/client/src/data/fixtures/staff-fixtures.js index ea6afbcd..4d1686cd 100644 --- a/client/src/data/fixtures/staff-fixtures.js +++ b/client/src/data/fixtures/staff-fixtures.js @@ -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: [] + } + ] + } + } } ]; \ No newline at end of file diff --git a/client/src/data/fixtures/ticket-fixtures.js b/client/src/data/fixtures/ticket-fixtures.js index 5d05e16c..b83a8a76 100644 --- a/client/src/data/fixtures/ticket-fixtures.js +++ b/client/src/data/fixtures/ticket-fixtures.js @@ -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 = [ }; } } -]; \ No newline at end of file +]; diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index aa7b778f..e964ceec 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -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.' -}; \ No newline at end of file +}; diff --git a/client/src/reducers/admin-data-reducer.js b/client/src/reducers/admin-data-reducer.js index 60c59d20..6ed135ee 100644 --- a/client/src/reducers/admin-data-reducer.js +++ b/client/src/reducers/admin-data-reducer.js @@ -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(); \ No newline at end of file