Skip to content

Commit 745f94c

Browse files
committed
Add ListTabel component
1 parent 99b0dba commit 745f94c

File tree

8 files changed

+108
-79
lines changed

8 files changed

+108
-79
lines changed

client/src/components/Posts/PostList.js

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import React, { Component, PropTypes } from 'react';
22
import { Link } from 'react-router';
33
import { connect } from 'react-redux';
44
import { get, find, keyBy } from 'lodash';
5-
import { Button, Table } from 'reactstrap';
5+
import { Button } from 'reactstrap';
66

77
import { fetchList, getMap, getMany } from '../../store/api';
88
import { withResourceList } from '../../hocs';
9-
import { ListHeader, Pagination } from '../UI';
9+
import { ListHeader, ListTable } from '../UI';
1010
import PostListFilter from './PostListFilter';
1111

1212
const formatDate = date => (new Date(date)).toLocaleString();
@@ -24,7 +24,27 @@ export class PostList extends Component {
2424
}
2525

2626
render() {
27-
const { resourceList, onFilter, onSort, categories } = this.props;
27+
const { resourceList, onFilter, categories } = this.props;
28+
29+
const columns = [
30+
{
31+
header: 'Category',
32+
minWidth: '50px',
33+
rowRender: post => this.getCategoryForPost(post).name,
34+
},
35+
{
36+
attribute: 'title',
37+
header: 'Title',
38+
rowRender: post => <Link to={`/posts/${post.id}`}>{post.title}</Link>,
39+
sortable: true,
40+
},
41+
{
42+
attribute: 'createdAt',
43+
header: 'Created At',
44+
rowRender: post => formatDate(post.createdAt),
45+
sortable: true,
46+
},
47+
];
2848

2949
return (
3050
<div>
@@ -36,46 +56,7 @@ export class PostList extends Component {
3656
categories={categories}>
3757
</PostListFilter>
3858

39-
<Table>
40-
<thead>
41-
<tr>
42-
<th>
43-
Category
44-
</th>
45-
<th>
46-
Title&nbsp;
47-
<select name="sort" value={resourceList.params.sort} onChange={onSort}>
48-
<option value="title">Asc</option>
49-
<option value="-title">Desc</option>
50-
</select>
51-
</th>
52-
<th>
53-
Created at&nbsp;
54-
<select name="sort" value={resourceList.params.sort} onChange={onSort}>
55-
<option value="createdAt">Asc</option>
56-
<option value="-createdAt">Desc</option>
57-
</select>
58-
</th>
59-
</tr>
60-
</thead>
61-
<tbody>
62-
{resourceList.data.map(post =>
63-
<tr key={post.id}>
64-
<td>
65-
{this.getCategoryForPost(post).name}
66-
</td>
67-
<td>
68-
<Link to={`/posts/${post.id}`}>{post.title}</Link>
69-
</td>
70-
<td>
71-
{formatDate(post.createdAt)}
72-
</td>
73-
</tr>
74-
)}
75-
</tbody>
76-
</Table>
77-
<Pagination {...this.props}></Pagination>
78-
{resourceList.empty && resourceList.loading && <p>Loading...</p>}
59+
<ListTable {...this.props} columns={columns} />
7960
</div>
8061
);
8162
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { Component } from 'react';
2+
import { Table } from 'reactstrap';
3+
4+
import { Pagination } from './';
5+
6+
const columnKey = (column, postfix) => `${column.accessor || column.header}-${postfix}`;
7+
8+
export default (props) => {
9+
const { columns, resourceList, onPageSize, onPageNumber, onSort } = props;
10+
const { sort } = resourceList.params;
11+
const sortedAsc = sort && sort[0] !== '-';
12+
13+
const sorted = (attribute) => attribute === sort || `-${attribute}` === sort;
14+
15+
const toggleSort = (attribute) => (e) => {
16+
if (attribute === sort) {
17+
onSort(sortedAsc ? `-${attribute}` : attribute);
18+
} else {
19+
onSort(attribute);
20+
}
21+
};
22+
23+
return (
24+
<div>
25+
<Table>
26+
<thead>
27+
<tr>
28+
{columns.map(column =>
29+
<th
30+
key={columnKey(column, 'header')}
31+
style={{minWidth: column.minWidth}}
32+
onClick={column.sortable && toggleSort(column.attribute)}
33+
>
34+
{column.header}&nbsp;
35+
{column.sortable && !sorted(column.attribute) && <i className="fa fa-sort"></i>}
36+
{column.sortable && sorted(column.attribute) && !sortedAsc && <i className="fa fa-sort-desc"></i>}
37+
{column.sortable && sorted(column.attribute) && sortedAsc && <i className="fa fa-sort-asc"></i>}
38+
</th>
39+
)}
40+
</tr>
41+
</thead>
42+
<tbody>
43+
{resourceList.data.map(item =>
44+
<tr key={item.id}>
45+
{columns.map(column =>
46+
<td key={columnKey(column, 'row')} style={{minWidth: column.minWidth}}>
47+
{column.rowRender ? column.rowRender(item) : item[column.attribute]}
48+
</td>
49+
)}
50+
</tr>
51+
)}
52+
</tbody>
53+
</Table>
54+
55+
<Pagination
56+
resourceList={resourceList}
57+
onPageNumber={onPageNumber}
58+
onPageSize={onPageSize}>
59+
</Pagination>
60+
61+
{resourceList.empty && resourceList.loading && <p>Loading...</p>}
62+
</div>
63+
);
64+
}

client/src/components/UI/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export CardSingle from './CardSingle';
22
export EditHeader from './EditHeader';
3+
export ListTable from './ListTable';
34
export Pagination from './Pagination';

client/src/components/Users/UserList.js

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React, { Component, PropTypes } from 'react';
22
import { Link } from 'react-router';
33
import { find, keyBy } from 'lodash';
4-
import { Table } from 'reactstrap';
54

6-
import { Pagination } from '../UI';
5+
import { ListTable } from '../UI';
76
import { withResourceList } from '../../hocs';
87

98
const formatDate = date => (new Date(date)).toLocaleString();
@@ -14,37 +13,23 @@ export class UserList extends Component {
1413
}
1514

1615
render() {
17-
const { resourceList } = this.props;
16+
const columns = [
17+
{
18+
attribute: 'email',
19+
header: 'Email',
20+
rowRender: user => <Link to={`/users/${user.id}`}>{user.email}</Link>,
21+
sortable: true,
22+
},
23+
{
24+
attribute: 'confirmedAt',
25+
header: 'Confirmed At',
26+
rowRender: user => formatDate(user.confirmedAt),
27+
sortable: true,
28+
},
29+
];
1830

1931
return (
20-
<div>
21-
<Table>
22-
<thead>
23-
<tr>
24-
<th>
25-
Email
26-
</th>
27-
<th>
28-
Confirmed at
29-
</th>
30-
</tr>
31-
</thead>
32-
<tbody>
33-
{resourceList.data.map(user =>
34-
<tr key={user.id}>
35-
<td>
36-
<Link to={`/users/${user.id}`}>{user.email}</Link>
37-
</td>
38-
<td>
39-
{formatDate(user.confirmedAt)}
40-
</td>
41-
</tr>
42-
)}
43-
</tbody>
44-
</Table>
45-
<Pagination {...this.props}></Pagination>
46-
{resourceList.empty && resourceList.loading && <p>Loading...</p>}
47-
</div>
32+
<ListTable {...this.props} columns={columns} />
4833
);
4934
}
5035
}

client/src/hocs/withResourceList.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ const withResourceList = resourceKey => (WrappedComponent) => {
2222
const number = 1;
2323
fetchResourceList({ ...params, page: { number, size }, filter: omitBy(filter, isEmpty) });
2424
},
25-
onSort: props => (event) => {
25+
onSort: props => (sort) => {
2626
const { resourceList: { params }, fetchResourceList } = props;
27-
const sort = event.target.value;
2827
fetchResourceList({ ...params, sort });
2928
},
3029
onPageSize: props => (event) => {

client/src/index.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<link rel="shortcut icon" href="assets/favicon.ico">
7+
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
78
<% for (var key in htmlWebpackPlugin.files.css) { %>
89
<link href="<%= htmlWebpackPlugin.files.css[key] %>" rel="stylesheet">
910
<% } %>

client/src/store/api/reducer.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,9 @@ export default (state = initialState, action) => {
4949
return addNormalized(newState, payload);
5050
}
5151
case actionType(GET_LIST, STARTED): {
52-
console.log('loading');
5352
return imm.set(newState, [key, list, 'loading'], true);
5453
}
5554
case actionType(GET_LIST, SUCCESS): {
56-
console.log('loaded');
5755
newState = addNormalized(newState, payload);
5856
newState = imm.set(newState, [key, list, 'ids'], map(payload.data, 'id'));
5957
newState = imm.set(newState, [key, list, 'params'], payload.params);

client/src/store/api/selectors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const getMany = (state, resourceName, ids) => {
1919
export const getList = (state, resourceName, listName = 'list') => {
2020
const byId = get(state, ['api', resourceName, 'byId']) || {};
2121
const list = get(state, ['api', resourceName, listName]) || {};
22-
return isEmpty(list.ids)
22+
return !list.ids
2323
? { data: [], ids: [], links: {}, params: { page: {}, filter: {} }, loading: true, empty: true }
2424
: { ...list, empty: false, data: list.ids.map(id => byId[id]) };
2525
};

0 commit comments

Comments
 (0)