mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-27 15:54:23 +02:00
Merged in OS-65-table-pagination (pull request #49)
OS-65 table pagination
This commit is contained in:
commit
c86d04e0cd
@ -13,6 +13,7 @@ const Widget = require('core-components/widget');
|
|||||||
const DropDown = require('core-components/drop-down');
|
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');
|
||||||
|
|
||||||
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 = [
|
||||||
@ -51,7 +52,7 @@ let DemoPage = React.createClass({
|
|||||||
{
|
{
|
||||||
title: 'Checkbox',
|
title: 'Checkbox',
|
||||||
render: (
|
render: (
|
||||||
<Checkbox label="Remember me" />
|
<Checkbox label="Remember me" value={true} />
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -101,6 +102,30 @@ let DemoPage = React.createClass({
|
|||||||
Open Modal
|
Open Modal
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Table',
|
||||||
|
render: (
|
||||||
|
<Table headers={[
|
||||||
|
{value:'Title First', key: 'title1'},
|
||||||
|
{value:'Title Second', key: 'title2'}
|
||||||
|
]} rows={[
|
||||||
|
{title1: 'Row1', title2: 'Example'},
|
||||||
|
{title1: 'Row2', title2: 'Example'},
|
||||||
|
{title1: 'Row3', title2: 'Example'},
|
||||||
|
{title1: 'Row4', title2: 'Example'},
|
||||||
|
{title1: 'Row5', title2: 'Example'},
|
||||||
|
{title1: 'Row6', title2: 'Example'},
|
||||||
|
{title1: 'Row7', title2: 'Example'},
|
||||||
|
{title1: 'Row8', title2: 'Example'},
|
||||||
|
{title1: 'Row9', title2: 'Example'},
|
||||||
|
{title1: 'Row10', title2: 'Example'},
|
||||||
|
{title1: 'Row11', title2: 'Example'},
|
||||||
|
{title1: 'Row12', title2: 'Example'},
|
||||||
|
{title1: 'Row13', title2: 'Example'},
|
||||||
|
{title1: 'Row14', title2: 'Example'}
|
||||||
|
]} pageSize={3}/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -117,7 +142,7 @@ let DemoPage = React.createClass({
|
|||||||
renderElements: function () {
|
renderElements: function () {
|
||||||
return this.elements.map((element) => {
|
return this.elements.map((element) => {
|
||||||
return (
|
return (
|
||||||
<div className="demo-element col-md-3">
|
<div className="demo-element col-md-4">
|
||||||
<h4>
|
<h4>
|
||||||
{element.title}
|
{element.title}
|
||||||
</h4>
|
</h4>
|
||||||
|
@ -21,7 +21,7 @@ class DashboardListTicketsPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="dashboard-ticket-list">
|
<div className="dashboard-ticket-list">
|
||||||
<Header title={i18n('TICKET_LIST')} description={i18n('TICKET_LIST_DESCRIPTION')} />
|
<Header title={i18n('TICKET_LIST')} description={i18n('TICKET_LIST_DESCRIPTION')} />
|
||||||
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} />
|
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} pageSize={10} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
108
client/src/core-components/__tests__/table-test.js
Normal file
108
client/src/core-components/__tests__/table-test.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
const Menu = ReactMock();
|
||||||
|
const Table = requireUnit('core-components/table', {
|
||||||
|
'core-components/menu': Menu
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Table component', function () {
|
||||||
|
let table, menu, tr, th;
|
||||||
|
|
||||||
|
function renderTable(props = {}) {
|
||||||
|
let headers = [
|
||||||
|
{value: 'Header 1', key: 'header1'},
|
||||||
|
{value: 'Header 2', key: 'header2'}
|
||||||
|
];
|
||||||
|
let rows = [
|
||||||
|
{header1: 'Row1 header1', header2: 'Row1 header2'},
|
||||||
|
{header1: 'Row2 header1', header2: 'Row2 header2'},
|
||||||
|
{header1: 'Row3 header1', header2: 'Row3 header2'},
|
||||||
|
{header1: 'Row4 header1', header2: 'Row4 header2'},
|
||||||
|
{header1: 'Row5 header1', header2: 'Row5 header2'},
|
||||||
|
{header1: 'Row6 header1', header2: 'Row6 header2'},
|
||||||
|
{header1: 'Row7 header1', header2: 'Row7 header2'},
|
||||||
|
{header1: 'Row8 header1', header2: 'Row8 header2'}
|
||||||
|
];
|
||||||
|
|
||||||
|
table = TestUtils.renderIntoDocument(
|
||||||
|
<Table headers={headers} rows={rows} {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
menu = TestUtils.scryRenderedComponentsWithType(table, Menu)[0];
|
||||||
|
tr = TestUtils.scryRenderedDOMComponentsWithTag(table, 'tr');
|
||||||
|
th = TestUtils.scryRenderedDOMComponentsWithTag(table, 'th');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
it('should render a table of elements', function () {
|
||||||
|
renderTable();
|
||||||
|
expect(tr.length).to.equal(9);
|
||||||
|
|
||||||
|
expect(tr[0].children[0]).to.equal(th[0]);
|
||||||
|
expect(tr[0].children[1]).to.equal(th[1]);
|
||||||
|
|
||||||
|
expect(tr[1].children[0].textContent).to.equal('Row1 header1');
|
||||||
|
expect(tr[1].children[1].textContent).to.equal('Row1 header2');
|
||||||
|
|
||||||
|
expect(tr[2].children[0].textContent).to.equal('Row2 header1');
|
||||||
|
expect(tr[2].children[1].textContent).to.equal('Row2 header2');
|
||||||
|
|
||||||
|
expect(tr[3].children[0].textContent).to.equal('Row3 header1');
|
||||||
|
expect(tr[3].children[1].textContent).to.equal('Row3 header2');
|
||||||
|
|
||||||
|
expect(tr[4].children[0].textContent).to.equal('Row4 header1');
|
||||||
|
expect(tr[4].children[1].textContent).to.equal('Row4 header2');
|
||||||
|
|
||||||
|
expect(tr[5].children[0].textContent).to.equal('Row5 header1');
|
||||||
|
expect(tr[5].children[1].textContent).to.equal('Row5 header2');
|
||||||
|
|
||||||
|
expect(tr[6].children[0].textContent).to.equal('Row6 header1');
|
||||||
|
expect(tr[6].children[1].textContent).to.equal('Row6 header2');
|
||||||
|
|
||||||
|
expect(tr[7].children[0].textContent).to.equal('Row7 header1');
|
||||||
|
expect(tr[7].children[1].textContent).to.equal('Row7 header2');
|
||||||
|
|
||||||
|
expect(tr[8].children[0].textContent).to.equal('Row8 header1');
|
||||||
|
expect(tr[8].children[1].textContent).to.equal('Row8 header2');
|
||||||
|
|
||||||
|
expect(menu).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a table limited by page size with menu', function () {
|
||||||
|
renderTable({pageSize: 3});
|
||||||
|
expect(tr.length).to.equal(4);
|
||||||
|
|
||||||
|
expect(tr[0].children[0]).to.equal(th[0]);
|
||||||
|
expect(tr[0].children[1]).to.equal(th[1]);
|
||||||
|
|
||||||
|
expect(tr[1].children[0].textContent).to.equal('Row1 header1');
|
||||||
|
expect(tr[1].children[1].textContent).to.equal('Row1 header2');
|
||||||
|
|
||||||
|
expect(tr[2].children[0].textContent).to.equal('Row2 header1');
|
||||||
|
expect(tr[2].children[1].textContent).to.equal('Row2 header2');
|
||||||
|
|
||||||
|
expect(tr[3].children[0].textContent).to.equal('Row3 header1');
|
||||||
|
expect(tr[3].children[1].textContent).to.equal('Row3 header2');
|
||||||
|
|
||||||
|
expect(menu.props.type).to.equal('navigation');
|
||||||
|
expect(menu.props.items).to.deep.equal([{content: 1}, {content: 2}, {content: 3}]);
|
||||||
|
|
||||||
|
menu.props.onItemClick(1);
|
||||||
|
tr = TestUtils.scryRenderedDOMComponentsWithTag(table, 'tr');
|
||||||
|
|
||||||
|
expect(tr.length).to.equal(4);
|
||||||
|
expect(tr[1].children[0].textContent).to.equal('Row4 header1');
|
||||||
|
expect(tr[1].children[1].textContent).to.equal('Row4 header2');
|
||||||
|
expect(tr[2].children[0].textContent).to.equal('Row5 header1');
|
||||||
|
expect(tr[2].children[1].textContent).to.equal('Row5 header2');
|
||||||
|
expect(tr[3].children[0].textContent).to.equal('Row6 header1');
|
||||||
|
expect(tr[3].children[1].textContent).to.equal('Row6 header2');
|
||||||
|
|
||||||
|
menu.props.onItemClick(2);
|
||||||
|
tr = TestUtils.scryRenderedDOMComponentsWithTag(table, 'tr');
|
||||||
|
|
||||||
|
expect(tr.length).to.equal(3);
|
||||||
|
expect(tr[1].children[0].textContent).to.equal('Row7 header1');
|
||||||
|
expect(tr[1].children[1].textContent).to.equal('Row7 header2');
|
||||||
|
expect(tr[2].children[0].textContent).to.equal('Row8 header1');
|
||||||
|
expect(tr[2].children[1].textContent).to.equal('Row8 header2');
|
||||||
|
});
|
||||||
|
});
|
@ -58,7 +58,7 @@ class CheckBox extends React.Component {
|
|||||||
getClass() {
|
getClass() {
|
||||||
let classes = {
|
let classes = {
|
||||||
'checkbox': true,
|
'checkbox': true,
|
||||||
'checkbox_checked': this.state.checked,
|
'checkbox_checked': this.getValue(),
|
||||||
|
|
||||||
[this.props.className]: (this.props.className)
|
[this.props.className]: (this.props.className)
|
||||||
};
|
};
|
||||||
|
@ -11,9 +11,9 @@ class Menu extends React.Component {
|
|||||||
id: React.PropTypes.string,
|
id: React.PropTypes.string,
|
||||||
itemsRole: React.PropTypes.string,
|
itemsRole: React.PropTypes.string,
|
||||||
header: React.PropTypes.string,
|
header: React.PropTypes.string,
|
||||||
type: React.PropTypes.oneOf(['primary', 'secondary']),
|
type: React.PropTypes.oneOf(['primary', 'secondary', 'navigation']),
|
||||||
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||||
content: React.PropTypes.string.isRequired,
|
content: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
|
||||||
icon: React.PropTypes.string
|
icon: React.PropTypes.string
|
||||||
})).isRequired,
|
})).isRequired,
|
||||||
selectedIndex: React.PropTypes.number,
|
selectedIndex: React.PropTypes.number,
|
||||||
@ -22,10 +22,13 @@ class Menu extends React.Component {
|
|||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
selectedIndex: 0,
|
|
||||||
tabbable: false
|
tabbable: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selectedIndex: this.props.selectedIndex || 0
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={this.getClass()}>
|
<div className={this.getClass()}>
|
||||||
@ -80,7 +83,8 @@ class Menu extends React.Component {
|
|||||||
getClass() {
|
getClass() {
|
||||||
let classes = {
|
let classes = {
|
||||||
'menu': true,
|
'menu': true,
|
||||||
'menu_secondary': (this.props.type === 'secondary')
|
'menu_secondary': (this.props.type === 'secondary'),
|
||||||
|
'menu_navigation': (this.props.type === 'navigation')
|
||||||
};
|
};
|
||||||
|
|
||||||
classes[this.props.className] = true;
|
classes[this.props.className] = true;
|
||||||
@ -90,7 +94,7 @@ class Menu extends React.Component {
|
|||||||
|
|
||||||
getItemProps(index) {
|
getItemProps(index) {
|
||||||
return {
|
return {
|
||||||
id: this.props.id + '__' + index,
|
id: (this.props.id) ? this.props.id + '__' + index : null,
|
||||||
className: this.getItemClass(index),
|
className: this.getItemClass(index),
|
||||||
onClick: this.onItemClick.bind(this, index),
|
onClick: this.onItemClick.bind(this, index),
|
||||||
tabIndex: (this.props.tabbable) ? '0' : null,
|
tabIndex: (this.props.tabbable) ? '0' : null,
|
||||||
@ -103,7 +107,7 @@ class Menu extends React.Component {
|
|||||||
getItemClass(index) {
|
getItemClass(index) {
|
||||||
let classes = {
|
let classes = {
|
||||||
'menu__list-item': true,
|
'menu__list-item': true,
|
||||||
'menu__list-item_selected': (this.props.selectedIndex === index)
|
'menu__list-item_selected': (this.getSelectedIndex() === index)
|
||||||
};
|
};
|
||||||
|
|
||||||
return classNames(classes);
|
return classNames(classes);
|
||||||
@ -118,7 +122,15 @@ class Menu extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectedIndex() {
|
||||||
|
return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
onItemClick(index) {
|
onItemClick(index) {
|
||||||
|
this.setState({
|
||||||
|
selectedIndex: index
|
||||||
|
});
|
||||||
|
|
||||||
if (this.props.onItemClick) {
|
if (this.props.onItemClick) {
|
||||||
this.props.onItemClick(index);
|
this.props.onItemClick(index);
|
||||||
}
|
}
|
||||||
|
@ -42,4 +42,40 @@
|
|||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_navigation {
|
||||||
|
text-align: center;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
.menu__list {
|
||||||
|
background-color: transparent;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu__list-item {
|
||||||
|
background-color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu__list-item_selected {
|
||||||
|
background-color: $grey;
|
||||||
|
color: $primary-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu__list-item:hover {
|
||||||
|
color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import Menu from 'core-components/menu';
|
||||||
|
|
||||||
class Table extends React.Component {
|
class Table extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
headers: React.PropTypes.arrayOf(React.PropTypes.shape({
|
headers: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||||
@ -9,6 +12,7 @@ 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,
|
||||||
type: React.PropTypes.oneOf(['default'])
|
type: React.PropTypes.oneOf(['default'])
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -16,18 +20,25 @@ class Table extends React.Component {
|
|||||||
type: 'default'
|
type: 'default'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
page: 1
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<table className="table table-responsive">
|
<div className="table__wrapper">
|
||||||
<thead>
|
<table className="table table-responsive">
|
||||||
<tr className="table__header">
|
<thead>
|
||||||
{this.props.headers.map(this.renderHeaderColumn.bind(this))}
|
<tr className="table__header">
|
||||||
</tr>
|
{this.props.headers.map(this.renderHeaderColumn.bind(this))}
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{this.props.rows.map(this.renderRow.bind(this))}
|
<tbody>
|
||||||
</tbody>
|
{this.props.rows.map(this.renderRow.bind(this))}
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{(this.props.pageSize && this.props.rows.length > this.props.pageSize) ? this.renderNavigation() : null}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,13 +54,16 @@ class Table extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRow(row, index) {
|
renderRow(row, index) {
|
||||||
let 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 maxIndex = this.props.pageSize * this.state.page;
|
||||||
|
const shouldRenderRow = !this.props.pageSize || (index >= minIndex && index < maxIndex);
|
||||||
|
|
||||||
return (
|
return (shouldRenderRow) ? (
|
||||||
<tr className={this.getRowClass(row)} key={index}>
|
<tr className={this.getRowClass(row)} key={index}>
|
||||||
{headersKeys.map(this.renderCell.bind(this, row))}
|
{headersKeys.map(this.renderCell.bind(this, row))}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCell(row, key, index) {
|
renderCell(row, key, index) {
|
||||||
@ -62,6 +76,21 @@ class Table extends React.Component {
|
|||||||
<td className={classNames(classes)} key={key}>{row[key]}</td>
|
<td className={classNames(classes)} key={key}>{row[key]}</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderNavigation() {
|
||||||
|
const pages = Math.ceil(this.props.rows.length / this.props.pageSize) + 1;
|
||||||
|
const items = _.range(1, pages).map((index) => {return {content: index};});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu className="table__navigation" type="navigation" items={items} onItemClick={this.onNavigationItemClick.bind(this)}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNavigationItemClick(index) {
|
||||||
|
this.setState({
|
||||||
|
page: index + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getRowClass(row) {
|
getRowClass(row) {
|
||||||
let classes = {
|
let classes = {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
@import "../scss/vars";
|
@import "../scss/vars";
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
background-color: $primary-blue;
|
background-color: $primary-blue;
|
||||||
@ -43,4 +44,8 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__navigation {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user