diff --git a/client/src/actions/admin-data-actions.js b/client/src/actions/admin-data-actions.js index f9ac73f7..aea9550e 100644 --- a/client/src/actions/admin-data-actions.js +++ b/client/src/actions/admin-data-actions.js @@ -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} }) }; } diff --git a/client/src/app-components/ticket-list.js b/client/src/app-components/ticket-list.js index 3f9b2e61..fa6f8aac 100644 --- a/client/src/app-components/ticket-list.js +++ b/client/src/app-components/ticket-list.js @@ -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 (
- {(this.props.type === 'secondary') ? this.renderDepartmentsDropDown() : null} - + {(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartmentsDropDown() : null} +
); } @@ -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) => { 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 490a15cf..00dbc3a4 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 @@ -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 (
- +
+ +
+
); } - 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); diff --git a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.scss b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.scss new file mode 100644 index 00000000..d09f84bc --- /dev/null +++ b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.scss @@ -0,0 +1,6 @@ +.admin-panel-my-tickets { + &__search-box { + padding: 0 50px; + margin-bottom: 30px; + } +} \ No newline at end of file diff --git a/client/src/core-components/search-box.js b/client/src/core-components/search-box.js new file mode 100644 index 00000000..13489a75 --- /dev/null +++ b/client/src/core-components/search-box.js @@ -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 ( +
+ + + + +
+ ); + } + + 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; \ No newline at end of file diff --git a/client/src/core-components/search-box.scss b/client/src/core-components/search-box.scss new file mode 100644 index 00000000..3a09ac49 --- /dev/null +++ b/client/src/core-components/search-box.scss @@ -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; + } +} \ No newline at end of file diff --git a/client/src/core-components/table.js b/client/src/core-components/table.js index b145e1c1..58494e05 100644 --- a/client/src/core-components/table.js +++ b/client/src/core-components/table.js @@ -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 {
{(this.props.loading) ? this.renderLoading() : null} - {(this.props.pageSize && this.props.rows.length > this.props.pageSize) ? this.renderNavigation() : null} + {this.renderPagination()}
); } @@ -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 ( - + ); } @@ -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; \ No newline at end of file diff --git a/client/src/core-components/table.scss b/client/src/core-components/table.scss index 54a73615..fe29d346 100644 --- a/client/src/core-components/table.scss +++ b/client/src/core-components/table.scss @@ -50,7 +50,7 @@ } &__loading-wrapper { - min-height: 200px; + min-height: 380px; position: relative; background-color: $grey; } diff --git a/client/src/data/fixtures/staff-fixtures.js b/client/src/data/fixtures/staff-fixtures.js index f84cef51..4aadb436 100644 --- a/client/src/data/fixtures/staff-fixtures.js +++ b/client/src/data/fixtures/staff-fixtures.js @@ -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 + } } } } diff --git a/client/src/reducers/admin-data-reducer.js b/client/src/reducers/admin-data-reducer.js index 6ed135ee..d3d753dc 100644 --- a/client/src/reducers/admin-data-reducer.js +++ b/client/src/reducers/admin-data-reducer.js @@ -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 }) }