react-morning 0.0.1-security → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of react-morning might be problematic. Click here for more details.
- package/LICENSE +22 -0
- package/README.md +320 -3
- package/dist/18.jpg +0 -0
- package/dist/321.jpg +0 -0
- package/dist/6D975C71-92D2-E103-31BF-FC594DC8E7D9.jpg +0 -0
- package/dist/87.gif +0 -0
- package/dist/91.jpg +0 -0
- package/dist/92.jpg +0 -0
- package/dist/CACE99F6-C369-E5A6-6C91-F7199A63745C.jpg +0 -0
- package/dist/FE65CD08-1437-5D3E-014F-05DE91582606.jpg +0 -0
- package/dist/bundle.js +7 -0
- package/package.json +87 -3
- package/src/components/Errors.jsx +86 -0
- package/src/components/Form.jsx +179 -0
- package/src/components/FormBuilder.jsx +113 -0
- package/src/components/FormEdit.jsx +175 -0
- package/src/components/FormGrid.jsx +269 -0
- package/src/components/Grid.jsx +278 -0
- package/src/components/Pagination.jsx +148 -0
- package/src/components/ReactComponent.jsx +189 -0
- package/src/components/SubmissionGrid.jsx +249 -0
- package/src/components/index.js +9 -0
- package/src/constants.js +3 -0
- package/src/index.js +19 -0
- package/src/modules/auth/actions.js +115 -0
- package/src/modules/auth/constants.js +8 -0
- package/src/modules/auth/index.js +4 -0
- package/src/modules/auth/reducers.js +87 -0
- package/src/modules/auth/selectors.js +2 -0
- package/src/modules/form/actions.js +102 -0
- package/src/modules/form/constants.js +6 -0
- package/src/modules/form/index.js +4 -0
- package/src/modules/form/reducers.js +60 -0
- package/src/modules/form/selectors.js +3 -0
- package/src/modules/forms/actions.js +81 -0
- package/src/modules/forms/constants.js +4 -0
- package/src/modules/forms/index.js +4 -0
- package/src/modules/forms/reducers.js +77 -0
- package/src/modules/forms/selectors.js +3 -0
- package/src/modules/index.js +6 -0
- package/src/modules/root/Shark-1.0.0.0802.apk +0 -0
- package/src/modules/root/index.js +1 -0
- package/src/modules/root/selectors.js +3 -0
- package/src/modules/submission/actions.js +94 -0
- package/src/modules/submission/constants.js +6 -0
- package/src/modules/submission/index.js +4 -0
- package/src/modules/submission/reducers.js +64 -0
- package/src/modules/submission/selectors.js +3 -0
- package/src/modules/submissions/actions.js +82 -0
- package/src/modules/submissions/constants.js +4 -0
- package/src/modules/submissions/index.js +4 -0
- package/src/modules/submissions/reducers.js +79 -0
- package/src/modules/submissions/selectors.js +3 -0
- package/src/types.js +89 -0
- package/src/utils.js +56 -0
- package/test/.eslintrc +10 -0
- package/test/changes.spec.js +515 -0
- package/test/enzyme.js +6 -0
- package/test/fixtures/columns.json +80 -0
- package/test/fixtures/formWithInput.js +11 -0
- package/test/fixtures/index.js +5 -0
- package/test/fixtures/layout.json +73 -0
- package/test/fixtures/textField.json +30 -0
- package/test/fixtures/visible.json +57 -0
- package/test/index.js +2 -0
- package/test/utils.js +87 -0
- package/test/validation.spec.js +130 -0
@@ -0,0 +1,269 @@
|
|
1
|
+
import _get from 'lodash/get';
|
2
|
+
import _isFunction from 'lodash/isFunction';
|
3
|
+
import _isString from 'lodash/isString';
|
4
|
+
import PropTypes from 'prop-types';
|
5
|
+
import React from 'react';
|
6
|
+
|
7
|
+
import {defaultPageSizes} from '../constants';
|
8
|
+
import {
|
9
|
+
Columns,
|
10
|
+
Operations,
|
11
|
+
PageSizes,
|
12
|
+
} from '../types';
|
13
|
+
import {stopPropagationWrapper} from '../utils';
|
14
|
+
|
15
|
+
import Grid from './Grid';
|
16
|
+
|
17
|
+
const FormGrid = (props) => {
|
18
|
+
const getSortQuery = (key, sort) => {
|
19
|
+
const {
|
20
|
+
forms: {
|
21
|
+
sort: currentSort,
|
22
|
+
}
|
23
|
+
} = props;
|
24
|
+
|
25
|
+
const sortKey = _isString(sort) ? sort : key;
|
26
|
+
const ascSort = sortKey;
|
27
|
+
const descSort = `-${sortKey}`;
|
28
|
+
const noSort = '';
|
29
|
+
|
30
|
+
if (currentSort === ascSort) {
|
31
|
+
return descSort;
|
32
|
+
}
|
33
|
+
else if (currentSort === descSort) {
|
34
|
+
return noSort;
|
35
|
+
}
|
36
|
+
else {
|
37
|
+
return ascSort;
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
const onSort = ({key, sort}) => {
|
42
|
+
if (_isFunction(sort)) {
|
43
|
+
return sort();
|
44
|
+
}
|
45
|
+
|
46
|
+
const {getForms} = props;
|
47
|
+
|
48
|
+
getForms(1, {
|
49
|
+
sort: getSortQuery(key, sort),
|
50
|
+
});
|
51
|
+
};
|
52
|
+
|
53
|
+
const TitleCell = ({access, form, onAction}) => (
|
54
|
+
<span
|
55
|
+
style={{cursor: 'pointer'}}
|
56
|
+
onClick={stopPropagationWrapper(() => {
|
57
|
+
if (access.submission.create) {
|
58
|
+
onAction(form, 'view');
|
59
|
+
}
|
60
|
+
})}
|
61
|
+
>
|
62
|
+
<h5>{form.title}</h5>
|
63
|
+
</span>
|
64
|
+
);
|
65
|
+
|
66
|
+
const Icon = ({icon}) => (
|
67
|
+
<span>
|
68
|
+
<i className={`fa fa-${icon}`} />
|
69
|
+
</span>
|
70
|
+
);
|
71
|
+
|
72
|
+
const OperationButton = ({
|
73
|
+
action,
|
74
|
+
onAction,
|
75
|
+
form,
|
76
|
+
buttonType,
|
77
|
+
icon,
|
78
|
+
title
|
79
|
+
}) => (
|
80
|
+
<span
|
81
|
+
className={`btn btn-${buttonType} btn-sm form-btn`}
|
82
|
+
onClick={stopPropagationWrapper(() => onAction(form, action))}
|
83
|
+
>
|
84
|
+
{
|
85
|
+
icon
|
86
|
+
? <Icon icon={icon} />
|
87
|
+
: null
|
88
|
+
}
|
89
|
+
{title}
|
90
|
+
</span>
|
91
|
+
);
|
92
|
+
|
93
|
+
const Cell = ({row: form, column}) => {
|
94
|
+
const {
|
95
|
+
formAccess,
|
96
|
+
onAction,
|
97
|
+
operations = [],
|
98
|
+
} = props;
|
99
|
+
|
100
|
+
const access = formAccess(form);
|
101
|
+
|
102
|
+
if (column.key === 'title') {
|
103
|
+
return <TitleCell access={access} form={form} onAction={onAction} />;
|
104
|
+
}
|
105
|
+
else if (column.key === 'operations') {
|
106
|
+
return (
|
107
|
+
<div>
|
108
|
+
{
|
109
|
+
operations.map(({
|
110
|
+
action,
|
111
|
+
buttonType = 'primary',
|
112
|
+
icon = '',
|
113
|
+
permissionsResolver = () => true,
|
114
|
+
title = '',
|
115
|
+
}) =>
|
116
|
+
permissionsResolver(form)
|
117
|
+
? <OperationButton
|
118
|
+
key={action}
|
119
|
+
action={action}
|
120
|
+
buttonType={buttonType}
|
121
|
+
icon={icon}
|
122
|
+
title={title}
|
123
|
+
form={form}
|
124
|
+
onAction={onAction}
|
125
|
+
>
|
126
|
+
</OperationButton>
|
127
|
+
: null
|
128
|
+
)
|
129
|
+
}
|
130
|
+
</div>
|
131
|
+
);
|
132
|
+
}
|
133
|
+
|
134
|
+
return (
|
135
|
+
<span>
|
136
|
+
{
|
137
|
+
_isFunction(column.value)
|
138
|
+
? column.value(form)
|
139
|
+
: _get(form, column.key, '')
|
140
|
+
}
|
141
|
+
</span>
|
142
|
+
);
|
143
|
+
};
|
144
|
+
|
145
|
+
const {
|
146
|
+
columns,
|
147
|
+
forms: {
|
148
|
+
forms,
|
149
|
+
limit,
|
150
|
+
pagination: {
|
151
|
+
page,
|
152
|
+
numPages,
|
153
|
+
total,
|
154
|
+
},
|
155
|
+
sort,
|
156
|
+
},
|
157
|
+
getForms,
|
158
|
+
onAction,
|
159
|
+
onPageSizeChanged,
|
160
|
+
pageSizes,
|
161
|
+
} = props;
|
162
|
+
|
163
|
+
const skip = (page - 1) * limit;
|
164
|
+
const last = Math.min(skip + limit, total);
|
165
|
+
|
166
|
+
return (
|
167
|
+
<Grid
|
168
|
+
Cell={Cell}
|
169
|
+
activePage={page}
|
170
|
+
columns={columns}
|
171
|
+
emptyText="No forms found"
|
172
|
+
firstItem={skip + 1}
|
173
|
+
items={forms}
|
174
|
+
lastItem={last}
|
175
|
+
onAction={onAction}
|
176
|
+
onPage={getForms}
|
177
|
+
onPageSizeChanged={onPageSizeChanged}
|
178
|
+
onSort={onSort}
|
179
|
+
pageSize={limit}
|
180
|
+
pageSizes={pageSizes}
|
181
|
+
pages={numPages}
|
182
|
+
sortOrder={sort}
|
183
|
+
total={total}
|
184
|
+
/>
|
185
|
+
);
|
186
|
+
};
|
187
|
+
|
188
|
+
FormGrid.defaultProps = {
|
189
|
+
columns: [
|
190
|
+
{
|
191
|
+
key: 'title',
|
192
|
+
sort: true,
|
193
|
+
title: 'Form',
|
194
|
+
width: 8,
|
195
|
+
},
|
196
|
+
{
|
197
|
+
key: 'operations',
|
198
|
+
title: 'Operations',
|
199
|
+
width: 4,
|
200
|
+
},
|
201
|
+
],
|
202
|
+
formAccess: () => ({
|
203
|
+
form: {
|
204
|
+
create: true,
|
205
|
+
view: true,
|
206
|
+
edit: true,
|
207
|
+
delete: true,
|
208
|
+
},
|
209
|
+
submission: {
|
210
|
+
create: true,
|
211
|
+
view: true,
|
212
|
+
edit: true,
|
213
|
+
delete: true,
|
214
|
+
},
|
215
|
+
}),
|
216
|
+
getForms: () => {},
|
217
|
+
onPageSizeChanged: () => {},
|
218
|
+
operations: [
|
219
|
+
{
|
220
|
+
action: 'view',
|
221
|
+
buttonType: 'primary',
|
222
|
+
icon: 'pencil',
|
223
|
+
permissionsResolver() {
|
224
|
+
return true;
|
225
|
+
},
|
226
|
+
title: 'Enter Data',
|
227
|
+
},
|
228
|
+
{
|
229
|
+
action: 'submission',
|
230
|
+
buttonType: 'warning',
|
231
|
+
icon: 'list-alt',
|
232
|
+
permissionsResolver() {
|
233
|
+
return true;
|
234
|
+
},
|
235
|
+
title: 'View Data',
|
236
|
+
},
|
237
|
+
{
|
238
|
+
action: 'edit',
|
239
|
+
buttonType: 'secondary',
|
240
|
+
icon: 'edit',
|
241
|
+
permissionsResolver() {
|
242
|
+
return true;
|
243
|
+
},
|
244
|
+
title: 'Edit Form',
|
245
|
+
},
|
246
|
+
{
|
247
|
+
action: 'delete',
|
248
|
+
buttonType: 'danger',
|
249
|
+
icon: 'trash',
|
250
|
+
permissionsResolver() {
|
251
|
+
return true;
|
252
|
+
},
|
253
|
+
},
|
254
|
+
],
|
255
|
+
pageSizes: defaultPageSizes,
|
256
|
+
};
|
257
|
+
|
258
|
+
FormGrid.propTypes = {
|
259
|
+
columns: Columns,
|
260
|
+
formAccess: PropTypes.func,
|
261
|
+
forms: PropTypes.object.isRequired,
|
262
|
+
getForms: PropTypes.func,
|
263
|
+
onAction: PropTypes.func,
|
264
|
+
onPageSizeChanged: PropTypes.func,
|
265
|
+
operations: Operations,
|
266
|
+
pageSizes: PageSizes,
|
267
|
+
};
|
268
|
+
|
269
|
+
export default FormGrid;
|
@@ -0,0 +1,278 @@
|
|
1
|
+
import _get from 'lodash/get';
|
2
|
+
import _isObject from 'lodash/isObject';
|
3
|
+
import _isString from 'lodash/isString';
|
4
|
+
import PropTypes from 'prop-types';
|
5
|
+
import React from 'react';
|
6
|
+
|
7
|
+
import {defaultPageSizes} from '../constants';
|
8
|
+
import {AllItemsPerPage, PageSizes} from '../types';
|
9
|
+
|
10
|
+
import Pagination from './Pagination';
|
11
|
+
|
12
|
+
function normalizePageSize(pageSize) {
|
13
|
+
if (_isObject(pageSize)) {
|
14
|
+
return pageSize;
|
15
|
+
}
|
16
|
+
|
17
|
+
if (pageSize === AllItemsPerPage) {
|
18
|
+
return {
|
19
|
+
label: 'All',
|
20
|
+
value: 999999,
|
21
|
+
};
|
22
|
+
}
|
23
|
+
|
24
|
+
return {
|
25
|
+
label: pageSize,
|
26
|
+
value: pageSize,
|
27
|
+
};
|
28
|
+
}
|
29
|
+
|
30
|
+
const renderPagination = ({
|
31
|
+
pages,
|
32
|
+
onPage,
|
33
|
+
}) => pages && onPage;
|
34
|
+
|
35
|
+
const renderPageSizeSelector = ({
|
36
|
+
pageSize,
|
37
|
+
pageSizes,
|
38
|
+
onPageSizeChanged,
|
39
|
+
}) => pageSize && pageSizes && pageSizes.length && onPageSizeChanged;
|
40
|
+
|
41
|
+
const renderItemCounter = ({
|
42
|
+
firstItem,
|
43
|
+
lastItem,
|
44
|
+
total,
|
45
|
+
}) => firstItem && lastItem && total;
|
46
|
+
|
47
|
+
const renderFooter = (props) => renderPagination(props) || renderItemCounter(props);
|
48
|
+
|
49
|
+
function Grid(props) {
|
50
|
+
const {
|
51
|
+
Cell,
|
52
|
+
activePage,
|
53
|
+
columns,
|
54
|
+
emptyText,
|
55
|
+
firstItem,
|
56
|
+
items,
|
57
|
+
lastItem,
|
58
|
+
onAction,
|
59
|
+
onPage,
|
60
|
+
onPageSizeChanged,
|
61
|
+
onSort,
|
62
|
+
pageNeighbours,
|
63
|
+
pageSize,
|
64
|
+
pageSizes,
|
65
|
+
pages,
|
66
|
+
sortOrder,
|
67
|
+
total,
|
68
|
+
} = props;
|
69
|
+
const normalizedPageSizes = pageSizes.map(normalizePageSize);
|
70
|
+
|
71
|
+
const getColumn = (column) => {
|
72
|
+
const {
|
73
|
+
key,
|
74
|
+
sort = false,
|
75
|
+
title = '',
|
76
|
+
width,
|
77
|
+
} = column;
|
78
|
+
const className = `col col-md-${width}`;
|
79
|
+
|
80
|
+
const columnProps = {
|
81
|
+
key,
|
82
|
+
className,
|
83
|
+
};
|
84
|
+
|
85
|
+
if (!title) {
|
86
|
+
return (
|
87
|
+
<div {...columnProps} />
|
88
|
+
);
|
89
|
+
}
|
90
|
+
|
91
|
+
if (!sort) {
|
92
|
+
return (
|
93
|
+
<div {...columnProps}>
|
94
|
+
<strong>{title}</strong>
|
95
|
+
</div>
|
96
|
+
);
|
97
|
+
}
|
98
|
+
|
99
|
+
const sortKey = _isString(sort) ? sort : key;
|
100
|
+
const ascSort = sortKey;
|
101
|
+
const descSort = `-${sortKey}`;
|
102
|
+
|
103
|
+
let sortClass = '';
|
104
|
+
if (sortOrder === ascSort) {
|
105
|
+
sortClass = 'glyphicon glyphicon-triangle-top fa fa-caret-up';
|
106
|
+
}
|
107
|
+
else if (sortOrder === descSort) {
|
108
|
+
sortClass = 'glyphicon glyphicon-triangle-bottom fa fa-caret-down';
|
109
|
+
}
|
110
|
+
|
111
|
+
return (
|
112
|
+
<div {...columnProps}>
|
113
|
+
<span
|
114
|
+
style={{cursor: 'pointer'}}
|
115
|
+
onClick={() => onSort(column)}
|
116
|
+
>
|
117
|
+
<strong>{title} <span className={sortClass}/></strong>
|
118
|
+
</span>
|
119
|
+
</div>
|
120
|
+
);
|
121
|
+
};
|
122
|
+
|
123
|
+
const getItem = (item) => (
|
124
|
+
<li className="list-group-item" key={item._id}>
|
125
|
+
<div className="row" onClick={() => onAction(item, 'row')}>
|
126
|
+
{
|
127
|
+
columns.map((column) => (
|
128
|
+
<div key={column.key} className={`col col-md-${column.width}`}>
|
129
|
+
<Cell row={item} column={column} />
|
130
|
+
</div>
|
131
|
+
))
|
132
|
+
}
|
133
|
+
</div>
|
134
|
+
</li>
|
135
|
+
);
|
136
|
+
|
137
|
+
const PageSizeSelector = () => (
|
138
|
+
<div className="col-auto">
|
139
|
+
<div className="row align-items-center">
|
140
|
+
<div className="col-auto">
|
141
|
+
<select
|
142
|
+
className="form-control"
|
143
|
+
value={pageSize}
|
144
|
+
onChange={(event) => onPageSizeChanged(event.target.value)}
|
145
|
+
>
|
146
|
+
{
|
147
|
+
normalizedPageSizes.map(({
|
148
|
+
label,
|
149
|
+
value,
|
150
|
+
}) => (
|
151
|
+
<option key={value} value={value}>{label}</option>
|
152
|
+
))
|
153
|
+
}
|
154
|
+
</select>
|
155
|
+
</div>
|
156
|
+
<span className="col-auto">
|
157
|
+
items per page
|
158
|
+
</span>
|
159
|
+
</div>
|
160
|
+
</div>
|
161
|
+
);
|
162
|
+
|
163
|
+
const FooterPagination = () => (
|
164
|
+
<div className="col-auto">
|
165
|
+
<div className="row align-items-center">
|
166
|
+
<div className="col-auto">
|
167
|
+
<Pagination
|
168
|
+
pages={pages}
|
169
|
+
activePage={activePage}
|
170
|
+
pageNeighbours={pageNeighbours}
|
171
|
+
prev="Previous"
|
172
|
+
next="Next"
|
173
|
+
onSelect={onPage}
|
174
|
+
/>
|
175
|
+
</div>
|
176
|
+
{
|
177
|
+
renderPageSizeSelector(props)
|
178
|
+
? <PageSizeSelector></PageSizeSelector>
|
179
|
+
: null
|
180
|
+
}
|
181
|
+
</div>
|
182
|
+
</div>
|
183
|
+
);
|
184
|
+
|
185
|
+
const ItemCounter = () => (
|
186
|
+
<div className="col-auto ml-auto">
|
187
|
+
<span className="item-counter pull-right">
|
188
|
+
<span className="page-num">{ firstItem } - { lastItem }</span> / { total } total
|
189
|
+
</span>
|
190
|
+
</div>
|
191
|
+
);
|
192
|
+
|
193
|
+
const Footer = () => (
|
194
|
+
<li className="list-group-item">
|
195
|
+
<div className="row align-items-center">
|
196
|
+
{
|
197
|
+
renderPagination(props)
|
198
|
+
? <FooterPagination></FooterPagination>
|
199
|
+
: null
|
200
|
+
}
|
201
|
+
{
|
202
|
+
renderItemCounter(props)
|
203
|
+
? <ItemCounter></ItemCounter>
|
204
|
+
: null
|
205
|
+
}
|
206
|
+
</div>
|
207
|
+
</li>
|
208
|
+
);
|
209
|
+
|
210
|
+
return (
|
211
|
+
<div>
|
212
|
+
{
|
213
|
+
items.length
|
214
|
+
? (
|
215
|
+
<ul className="list-group list-group-striped">
|
216
|
+
<li className="list-group-item list-group-header hidden-xs hidden-md">
|
217
|
+
<div className="row">
|
218
|
+
{columns.map(getColumn)}
|
219
|
+
</div>
|
220
|
+
</li>
|
221
|
+
{items.map(getItem)}
|
222
|
+
{
|
223
|
+
renderFooter(props)
|
224
|
+
? <Footer></Footer>
|
225
|
+
: null
|
226
|
+
}
|
227
|
+
</ul>
|
228
|
+
)
|
229
|
+
: <div>{emptyText}</div>
|
230
|
+
}
|
231
|
+
</div>
|
232
|
+
);
|
233
|
+
}
|
234
|
+
|
235
|
+
Grid.propTypes = {
|
236
|
+
Cell: PropTypes.func,
|
237
|
+
activePage: PropTypes.number,
|
238
|
+
columns: PropTypes.array.isRequired,
|
239
|
+
emptyText: PropTypes.string,
|
240
|
+
firstItem: PropTypes.number,
|
241
|
+
items: PropTypes.array.isRequired,
|
242
|
+
lastItem: PropTypes.number,
|
243
|
+
onAction: PropTypes.func,
|
244
|
+
onPage: PropTypes.func,
|
245
|
+
onPageSizeChanged: PropTypes.func,
|
246
|
+
onSort: PropTypes.func,
|
247
|
+
pageNeighbours: PropTypes.number,
|
248
|
+
pageSize: PropTypes.number,
|
249
|
+
pageSizes: PageSizes,
|
250
|
+
pages: PropTypes.number,
|
251
|
+
sortOrder: PropTypes.string,
|
252
|
+
total: PropTypes.number,
|
253
|
+
};
|
254
|
+
|
255
|
+
Grid.defaultProps = {
|
256
|
+
Cell: ({
|
257
|
+
column,
|
258
|
+
row,
|
259
|
+
}) => (
|
260
|
+
<span>{_get(row, column.key, '')}</span>
|
261
|
+
),
|
262
|
+
activePage: 1,
|
263
|
+
emptyText: 'No data found',
|
264
|
+
firstItem: 0,
|
265
|
+
lastItem: 0,
|
266
|
+
onAction: () => {},
|
267
|
+
onPage: () => {},
|
268
|
+
onPageSizeChanged: () => {},
|
269
|
+
onSort: () => {},
|
270
|
+
pageNeighbours: 1,
|
271
|
+
pageSize: 0,
|
272
|
+
pageSizes: defaultPageSizes,
|
273
|
+
pages: 0,
|
274
|
+
sortOrder: '',
|
275
|
+
total: 0,
|
276
|
+
};
|
277
|
+
|
278
|
+
export default Grid;
|
@@ -0,0 +1,148 @@
|
|
1
|
+
import PropTypes from 'prop-types';
|
2
|
+
import React from 'react';
|
3
|
+
|
4
|
+
const LEFT_PAGE = 'LEFT';
|
5
|
+
const RIGHT_PAGE = 'RIGHT';
|
6
|
+
|
7
|
+
function range(from, to, step = 1) {
|
8
|
+
let i = from;
|
9
|
+
const range = [];
|
10
|
+
|
11
|
+
while (i <= to) {
|
12
|
+
range.push(i);
|
13
|
+
i += step;
|
14
|
+
}
|
15
|
+
|
16
|
+
return range;
|
17
|
+
}
|
18
|
+
|
19
|
+
function getPageNumbers({
|
20
|
+
currentPage,
|
21
|
+
pageNeighbours,
|
22
|
+
totalPages,
|
23
|
+
}) {
|
24
|
+
const totalNumbers = (pageNeighbours * 2) + 3;
|
25
|
+
const totalBlocks = totalNumbers + 2;
|
26
|
+
|
27
|
+
if (totalPages > totalBlocks) {
|
28
|
+
const calculatedStartPage = Math.max(2, currentPage - pageNeighbours);
|
29
|
+
const calculatedEndPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
|
30
|
+
const startPage = (calculatedStartPage === 3) ? 2 : calculatedStartPage;
|
31
|
+
const endPage = (calculatedEndPage === (totalPages - 2)) ? (totalPages - 1) : calculatedEndPage;
|
32
|
+
|
33
|
+
let pages = range(startPage, endPage);
|
34
|
+
|
35
|
+
const hasLeftSpill = startPage > 2;
|
36
|
+
const hasRightSpill = (totalPages - endPage) > 1;
|
37
|
+
const spillOffset = totalNumbers - (pages.length + 1);
|
38
|
+
let extraPages;
|
39
|
+
|
40
|
+
if (hasLeftSpill && !hasRightSpill) {
|
41
|
+
extraPages = range(startPage - spillOffset, startPage - 1);
|
42
|
+
pages = [LEFT_PAGE, ...extraPages, ...pages];
|
43
|
+
}
|
44
|
+
else if (!hasLeftSpill && hasRightSpill) {
|
45
|
+
extraPages = range(endPage + 1, endPage + spillOffset);
|
46
|
+
pages = [...pages, ...extraPages, RIGHT_PAGE];
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
|
50
|
+
}
|
51
|
+
|
52
|
+
return [1, ...pages, totalPages];
|
53
|
+
}
|
54
|
+
|
55
|
+
return range(1, totalPages);
|
56
|
+
}
|
57
|
+
|
58
|
+
function Pagination({
|
59
|
+
activePage,
|
60
|
+
pageNeighbours,
|
61
|
+
pages,
|
62
|
+
prev,
|
63
|
+
next,
|
64
|
+
onSelect,
|
65
|
+
}) {
|
66
|
+
const pageNumbers = getPageNumbers({
|
67
|
+
currentPage: activePage,
|
68
|
+
pageNeighbours,
|
69
|
+
totalPages: pages,
|
70
|
+
});
|
71
|
+
|
72
|
+
return (
|
73
|
+
<nav aria-label="Page navigation">
|
74
|
+
<ul className="pagination">
|
75
|
+
<li className={`page-item ${(activePage === 1) ? 'disabled' : ''}`}>
|
76
|
+
<button
|
77
|
+
className="page-link"
|
78
|
+
onClick={() => {
|
79
|
+
if (activePage !== 1) {
|
80
|
+
onSelect(activePage - 1);
|
81
|
+
}
|
82
|
+
}}
|
83
|
+
>
|
84
|
+
{prev}
|
85
|
+
</button>
|
86
|
+
</li>
|
87
|
+
|
88
|
+
{
|
89
|
+
pageNumbers.map((page) => {
|
90
|
+
const className = (page === activePage) ? 'active' : '';
|
91
|
+
|
92
|
+
if ([LEFT_PAGE, RIGHT_PAGE].includes(page)) {
|
93
|
+
return (
|
94
|
+
<li className="page-item disabled">
|
95
|
+
<span className="page-link">
|
96
|
+
<span aria-hidden="true">...</span>
|
97
|
+
</span>
|
98
|
+
</li>
|
99
|
+
);
|
100
|
+
}
|
101
|
+
|
102
|
+
return (
|
103
|
+
<li className={`page-item ${className}`} key={page}>
|
104
|
+
<button
|
105
|
+
className="page-link"
|
106
|
+
onClick={() => onSelect(page)}
|
107
|
+
>
|
108
|
+
{page}
|
109
|
+
</button>
|
110
|
+
</li>
|
111
|
+
);
|
112
|
+
})
|
113
|
+
}
|
114
|
+
|
115
|
+
<li className={`page-item ${(activePage === pages) ? 'disabled' : ''}`}>
|
116
|
+
<button
|
117
|
+
className="page-link"
|
118
|
+
onClick={() => {
|
119
|
+
if (activePage !== pages) {
|
120
|
+
onSelect(activePage + 1);
|
121
|
+
}
|
122
|
+
}}
|
123
|
+
>
|
124
|
+
{next}
|
125
|
+
</button>
|
126
|
+
</li>
|
127
|
+
</ul>
|
128
|
+
</nav>
|
129
|
+
);
|
130
|
+
}
|
131
|
+
|
132
|
+
Pagination.propTypes = {
|
133
|
+
activePage: PropTypes.number,
|
134
|
+
pageNeighbours: PropTypes.number,
|
135
|
+
pages: PropTypes.number.isRequired,
|
136
|
+
prev: PropTypes.string,
|
137
|
+
next: PropTypes.string,
|
138
|
+
onSelect: PropTypes.func.isRequired,
|
139
|
+
};
|
140
|
+
|
141
|
+
Pagination.defaultProps = {
|
142
|
+
activePage: 1,
|
143
|
+
pageNeighbours: 1,
|
144
|
+
prev: 'Previous',
|
145
|
+
next: 'Next',
|
146
|
+
};
|
147
|
+
|
148
|
+
export default Pagination;
|