Ivan - Add all ticket search and server pagination [skip ci]

This commit is contained in:
ivan 2016-11-18 01:40:43 -03:00
parent cc1b5785fa
commit 89837b0726
10 changed files with 235 additions and 99 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
.admin-panel-my-tickets {
&__search-box {
padding: 0 50px;
margin-bottom: 30px;
}
}

View 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;

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

View File

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

View File

@ -50,7 +50,7 @@
}
&__loading-wrapper {
min-height: 200px;
min-height: 380px;
position: relative;
background-color: $grey;
}

View File

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

View File

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