Ivan - Add all ticket search and server pagination [skip ci]
This commit is contained in:
parent
cc1b5785fa
commit
89837b0726
|
@ -32,12 +32,22 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
retrieveAllTickets() {
|
||||
retrieveAllTickets(page) {
|
||||
return {
|
||||
type: 'ALL_TICKETS',
|
||||
payload: API.call({
|
||||
path: '/staff/get-all-tickets',
|
||||
data: {}
|
||||
data: {page}
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
searchTickets(query, page) {
|
||||
return {
|
||||
type: 'ALL_TICKETS',
|
||||
payload: API.call({
|
||||
path: '/staff/search-tickets',
|
||||
data: {query, page}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ class TicketList extends React.Component {
|
|||
departments: React.PropTypes.array,
|
||||
loading: React.PropTypes.bool,
|
||||
ticketPath: React.PropTypes.string,
|
||||
showDepartmentDropdown: React.PropTypes.bool,
|
||||
tickets: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
type: React.PropTypes.oneOf([
|
||||
'primary',
|
||||
|
@ -23,6 +24,7 @@ class TicketList extends React.Component {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
showDepartmentDropdown: true,
|
||||
loading: false,
|
||||
tickets: [],
|
||||
departments: [],
|
||||
|
@ -37,8 +39,8 @@ class TicketList extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="ticket-list">
|
||||
{(this.props.type === 'secondary') ? this.renderDepartmentsDropDown() : null}
|
||||
<Table loading={this.props.loading} 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>
|
||||
);
|
||||
}
|
||||
|
@ -62,6 +64,19 @@ class TicketList extends React.Component {
|
|||
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) => {
|
||||
|
|
|
@ -6,6 +6,7 @@ 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 {
|
||||
|
||||
|
@ -14,6 +15,11 @@ class AdminPanelAllTickets extends React.Component {
|
|||
tickets: []
|
||||
};
|
||||
|
||||
state = {
|
||||
page: 1,
|
||||
query: ''
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(AdminDataAction.retrieveAllTickets());
|
||||
}
|
||||
|
@ -22,26 +28,51 @@ class AdminPanelAllTickets extends React.Component {
|
|||
return (
|
||||
<div className="admin-panel-my-tickets">
|
||||
<Header title={i18n('ALL_TICKETS')} description={i18n('ALL_TICKETS_DESCRIPTION')} />
|
||||
<TicketList {...this.getProps()}/>
|
||||
<div className="admin-panel-my-tickets__search-box">
|
||||
<SearchBox onSearch={this.onSearch.bind(this)} />
|
||||
</div>
|
||||
<TicketList {...this.getTicketListProps()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getProps() {
|
||||
getTicketListProps() {
|
||||
return {
|
||||
showDepartmentDropdown: false,
|
||||
departments: this.props.departments,
|
||||
tickets: this.props.tickets,
|
||||
type: 'secondary',
|
||||
loading: this.props.loading,
|
||||
ticketPath: '/admin/panel/tickets/view-ticket/'
|
||||
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 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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
|
||||
})),
|
||||
rows: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
pageSize: React.PropTypes.number,
|
||||
loading: React.PropTypes.bool,
|
||||
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
|
||||
};
|
||||
|
||||
|
@ -41,7 +44,7 @@ class Table extends React.Component {
|
|||
</tbody>
|
||||
</table>
|
||||
{(this.props.loading) ? this.renderLoading() : null}
|
||||
{(this.props.pageSize && this.props.rows.length > this.props.pageSize) ? this.renderNavigation() : null}
|
||||
{this.renderPagination()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -59,8 +62,8 @@ class Table extends React.Component {
|
|||
|
||||
renderRow(row, index) {
|
||||
const headersKeys = this.props.headers.map(header => header.key);
|
||||
const minIndex = this.props.pageSize * (this.state.page - 1);
|
||||
const maxIndex = this.props.pageSize * this.state.page;
|
||||
const minIndex = this.props.pageSize * ((this.props.page) ? 0 : this.state.page - 1);
|
||||
const maxIndex = this.props.pageSize * ((this.props.page) ? 1 : this.state.page);
|
||||
const shouldRenderRow = !this.props.pageSize || (index >= minIndex && index < maxIndex);
|
||||
|
||||
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() {
|
||||
const pages = Math.ceil(this.props.rows.length / this.props.pageSize) + 1;
|
||||
const items = _.range(1, pages).map((index) => {return {content: index};});
|
||||
const items = _.range(1, this.getPages()).map((index) => {return {content: index};});
|
||||
|
||||
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({
|
||||
page: index + 1
|
||||
});
|
||||
|
||||
if(this.props.onPageChange) {
|
||||
this.props.onPageChange({target: {value: index + 1}});
|
||||
}
|
||||
}
|
||||
|
||||
getRowClass(row) {
|
||||
|
@ -114,11 +124,19 @@ class Table extends React.Component {
|
|||
}
|
||||
|
||||
getRows() {
|
||||
let v = _.clone(this.props.rows);
|
||||
v.sort(this.props.comp);
|
||||
return v;
|
||||
let sortedRows = _.clone(this.props.rows);
|
||||
sortedRows.sort(this.props.comp);
|
||||
|
||||
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;
|
|
@ -50,7 +50,7 @@
|
|||
}
|
||||
|
||||
&__loading-wrapper {
|
||||
min-height: 200px;
|
||||
min-height: 380px;
|
||||
position: relative;
|
||||
background-color: $grey;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
path: '/staff/get',
|
||||
|
@ -498,90 +500,81 @@ module.exports = [
|
|||
},
|
||||
{
|
||||
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: [
|
||||
{
|
||||
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: []
|
||||
}
|
||||
]
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,8 @@ class AdminDataReducer extends Reducer {
|
|||
|
||||
onAllTicketsRetrieved(state, payload) {
|
||||
return _.extend({}, state, {
|
||||
allTickets: payload.data,
|
||||
allTickets: payload.data.tickets,
|
||||
allTicketsPages: payload.data.pages,
|
||||
allTicketsLoaded: true
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue