Skip to content

Commit c97a457

Browse files
committed
feat: pagination phase 5
1 parent 322b86d commit c97a457

File tree

5 files changed

+244
-2
lines changed

5 files changed

+244
-2
lines changed

packages/decap-cms-core/src/components/Collection/Entries/Entries.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { translate } from 'react-polyglot';
66
import { Loader, lengths } from 'decap-cms-ui-default';
77

88
import EntryListing from './EntryListing';
9+
import Pagination from './Pagination';
910

1011
const PaginationMessage = styled.div`
1112
width: ${lengths.topCardWidth};
@@ -29,6 +30,13 @@ function Entries({
2930
getWorkflowStatus,
3031
getUnpublishedEntries,
3132
filterTerm,
33+
paginationEnabled,
34+
paginationConfig,
35+
currentPage,
36+
pageSize,
37+
totalCount,
38+
onPageChange,
39+
onPageSizeChange,
3240
}) {
3341
const loadingMessages = [
3442
t('collection.entries.loadingEntries'),
@@ -41,6 +49,13 @@ function Entries({
4149
}
4250

4351
const hasEntries = (entries && entries.size > 0) || cursor?.actions?.has('append_next');
52+
53+
// Calculate page count for pagination
54+
const pageCount = paginationEnabled && totalCount > 0 ? Math.ceil(totalCount / pageSize) : 1;
55+
56+
// Show pagination controls only if pagination is enabled and we have entries
57+
const showPagination = paginationEnabled && totalCount > 0 && pageCount > 1;
58+
4459
if (hasEntries) {
4560
return (
4661
<>
@@ -54,10 +69,22 @@ function Entries({
5469
getWorkflowStatus={getWorkflowStatus}
5570
getUnpublishedEntries={getUnpublishedEntries}
5671
filterTerm={filterTerm}
72+
paginationEnabled={paginationEnabled}
5773
/>
5874
{isFetching && page !== undefined && entries.size > 0 ? (
5975
<PaginationMessage>{t('collection.entries.loadingEntries')}</PaginationMessage>
6076
) : null}
77+
{showPagination && !isFetching && (
78+
<Pagination
79+
currentPage={currentPage}
80+
pageCount={pageCount}
81+
pageSize={pageSize}
82+
totalCount={totalCount}
83+
userOptions={paginationConfig?.user_options || null}
84+
onPageChange={onPageChange}
85+
onPageSizeChange={onPageSizeChange}
86+
/>
87+
)}
6188
</>
6289
);
6390
}
@@ -77,6 +104,13 @@ Entries.propTypes = {
77104
getWorkflowStatus: PropTypes.func,
78105
getUnpublishedEntries: PropTypes.func,
79106
filterTerm: PropTypes.string,
107+
paginationEnabled: PropTypes.bool,
108+
paginationConfig: PropTypes.object,
109+
currentPage: PropTypes.number,
110+
pageSize: PropTypes.number,
111+
totalCount: PropTypes.number,
112+
onPageChange: PropTypes.func,
113+
onPageSizeChange: PropTypes.func,
80114
};
81115

82116
export default translate()(Entries);

packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ import { colors } from 'decap-cms-ui-default';
1111
import {
1212
loadEntries as actionLoadEntries,
1313
traverseCollectionCursor as actionTraverseCollectionCursor,
14+
setEntriesPageSize as actionSetEntriesPageSize,
15+
loadEntriesPage as actionLoadEntriesPage,
1416
} from '../../../actions/entries';
1517
import { loadUnpublishedEntries } from '../../../actions/editorialWorkflow';
1618
import {
1719
selectEntries,
1820
selectEntriesLoaded,
1921
selectIsFetching,
2022
selectGroups,
23+
selectEntriesPageSize,
24+
selectEntriesCurrentPage,
25+
selectEntriesTotalCount,
2126
} from '../../../reducers/entries';
27+
import { isPaginationEnabled, getPaginationConfig } from '../../../lib/pagination';
2228
import { selectUnpublishedEntry, selectUnpublishedEntriesByStatus } from '../../../reducers';
2329
import { selectCollectionEntriesCursor } from '../../../reducers/cursors';
2430
import Entries from './Entries';
@@ -78,6 +84,13 @@ export class EntriesCollection extends React.Component {
7884
isEditorialWorkflowEnabled: PropTypes.bool,
7985
getWorkflowStatus: PropTypes.func.isRequired,
8086
getUnpublishedEntries: PropTypes.func.isRequired,
87+
paginationEnabled: PropTypes.bool,
88+
paginationConfig: PropTypes.object,
89+
currentPage: PropTypes.number,
90+
pageSize: PropTypes.number,
91+
totalCount: PropTypes.number,
92+
setEntriesPageSize: PropTypes.func.isRequired,
93+
loadEntriesPage: PropTypes.func.isRequired,
8194
};
8295

8396
componentDidMount() {
@@ -144,6 +157,13 @@ export class EntriesCollection extends React.Component {
144157
getWorkflowStatus,
145158
getUnpublishedEntries,
146159
filterTerm,
160+
paginationEnabled,
161+
paginationConfig,
162+
currentPage,
163+
pageSize,
164+
totalCount,
165+
setEntriesPageSize,
166+
loadEntriesPage,
147167
} = this.props;
148168

149169
const EntriesToRender = ({ entries }) => {
@@ -160,6 +180,13 @@ export class EntriesCollection extends React.Component {
160180
getWorkflowStatus={getWorkflowStatus}
161181
getUnpublishedEntries={getUnpublishedEntries}
162182
filterTerm={filterTerm}
183+
paginationEnabled={paginationEnabled}
184+
paginationConfig={paginationConfig}
185+
currentPage={currentPage}
186+
pageSize={pageSize}
187+
totalCount={totalCount}
188+
onPageChange={page => loadEntriesPage(collection, page)}
189+
onPageSizeChange={pageSize => setEntriesPageSize(collection, pageSize)}
163190
/>
164191
);
165192
};
@@ -227,6 +254,13 @@ function mapStateToProps(state, ownProps) {
227254
? !!state.editorialWorkflow?.getIn(['pages', 'ids'], false)
228255
: true;
229256

257+
// Pagination state
258+
const paginationEnabled = isPaginationEnabled(collection, state.config);
259+
const paginationConfig = paginationEnabled ? getPaginationConfig(collection, state.config) : null;
260+
const currentPage = selectEntriesCurrentPage(state.entries, collection.get('name'));
261+
const pageSize = selectEntriesPageSize(state.entries, collection.get('name'));
262+
const totalCount = selectEntriesTotalCount(state.entries, collection.get('name'));
263+
230264
return {
231265
collection,
232266
collections,
@@ -239,6 +273,11 @@ function mapStateToProps(state, ownProps) {
239273
cursor,
240274
unpublishedEntriesLoaded,
241275
isEditorialWorkflowEnabled,
276+
paginationEnabled,
277+
paginationConfig,
278+
currentPage,
279+
pageSize,
280+
totalCount,
242281
getWorkflowStatus: (collectionName, slug) => {
243282
const unpublishedEntry = selectUnpublishedEntry(state, collectionName, slug);
244283
return unpublishedEntry ? unpublishedEntry.get('status') : null;
@@ -270,6 +309,8 @@ const mapDispatchToProps = {
270309
loadEntries: actionLoadEntries,
271310
traverseCollectionCursor: actionTraverseCollectionCursor,
272311
loadUnpublishedEntries: collections => loadUnpublishedEntries(collections),
312+
setEntriesPageSize: actionSetEntriesPageSize,
313+
loadEntriesPage: actionLoadEntriesPage,
273314
};
274315

275316
const ConnectedEntriesCollection = connect(mapStateToProps, mapDispatchToProps)(EntriesCollection);

packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class EntryListing extends React.Component {
2929
getUnpublishedEntries: PropTypes.func.isRequired,
3030
getWorkflowStatus: PropTypes.func.isRequired,
3131
filterTerm: PropTypes.string,
32+
paginationEnabled: PropTypes.bool,
3233
};
3334

3435
componentDidMount() {
@@ -133,15 +134,15 @@ class EntryListing extends React.Component {
133134
};
134135

135136
render() {
136-
const { collections, page } = this.props;
137+
const { collections, page, paginationEnabled } = this.props;
137138

138139
return (
139140
<div>
140141
<CardsGrid>
141142
{Map.isMap(collections)
142143
? this.renderCardsForSingleCollection()
143144
: this.renderCardsForMultipleCollections()}
144-
{this.hasMore() && <Waypoint key={page} onEnter={this.handleLoadMore} />}
145+
{!paginationEnabled && this.hasMore() && <Waypoint key={page} onEnter={this.handleLoadMore} />}
145146
</CardsGrid>
146147
</div>
147148
);
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import styled from '@emotion/styled';
4+
import { translate } from 'react-polyglot';
5+
import { Icon, colors } from 'decap-cms-ui-default';
6+
7+
const PaginationControls = styled.div`
8+
display: flex;
9+
align-items: center;
10+
justify-content: center;
11+
gap: 0.5rem;
12+
margin: 2rem;
13+
`;
14+
15+
const PaginationInfo = styled.div`
16+
display: flex;
17+
justify-content: center;
18+
align-items: center;
19+
gap: 12px;
20+
`;
21+
22+
const PaginationButton = styled.button`
23+
padding: 6px 6px 4px 6px;
24+
background-color: ${colors.button};
25+
color: ${colors.buttonText};
26+
border: none;
27+
border-radius: 4px;
28+
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
29+
font-size: 12px;
30+
transition: background-color 0.2s;
31+
32+
&:hover:not(:disabled) {
33+
background-color: #555a65;
34+
}
35+
36+
&:disabled {
37+
opacity: 0.5;
38+
}
39+
`;
40+
41+
const PageSizeSelect = styled.select`
42+
padding: 6px 10px;
43+
border: 1px solid ${colors.textFieldBorder};
44+
border-radius: 4px;
45+
background-color: ${colors.inputBackground};
46+
color: ${colors.text};
47+
font-size: 14px;
48+
cursor: pointer;
49+
outline: none;
50+
51+
&:focus {
52+
border-color: ${colors.active};
53+
}
54+
`;
55+
56+
const PageSizeLabel = styled.label`
57+
color: ${colors.text};
58+
font-size: 14px;
59+
display: flex;
60+
align-items: center;
61+
gap: 8px;
62+
`;
63+
64+
function Pagination({
65+
currentPage,
66+
pageCount,
67+
pageSize,
68+
totalCount,
69+
userOptions,
70+
onPageChange,
71+
onPageSizeChange,
72+
t,
73+
}) {
74+
const hasPrevPage = currentPage > 1;
75+
const hasNextPage = currentPage < pageCount;
76+
77+
const startEntry = (currentPage - 1) * pageSize + 1;
78+
const endEntry = Math.min(currentPage * pageSize, totalCount);
79+
80+
return (
81+
<div>
82+
<PaginationControls>
83+
<PaginationButton
84+
disabled={!hasPrevPage}
85+
onClick={() => onPageChange(1)}
86+
title={t('collection.pagination.first')}
87+
>
88+
<Icon type="chevron-double" size="small" direction="left" />
89+
</PaginationButton>
90+
<PaginationButton
91+
disabled={!hasPrevPage}
92+
onClick={() => onPageChange(currentPage - 1)}
93+
title={t('collection.pagination.previous')}
94+
>
95+
<Icon type="chevron" size="small" direction="left" />
96+
</PaginationButton>
97+
98+
<span style={{ color: colors.text, fontSize: '14px', padding: '0 8px' }}>
99+
{t('collection.pagination.page', { current: currentPage, total: pageCount })}
100+
</span>
101+
102+
<PaginationButton
103+
disabled={!hasNextPage}
104+
onClick={() => onPageChange(currentPage + 1)}
105+
title={t('collection.pagination.next')}
106+
>
107+
<Icon type="chevron" size="small" direction="right" />
108+
</PaginationButton>
109+
<PaginationButton
110+
disabled={!hasNextPage}
111+
onClick={() => onPageChange(pageCount)}
112+
title={t('collection.pagination.last')}
113+
>
114+
<Icon type="chevron-double" size="small" direction="right" />
115+
</PaginationButton>
116+
</PaginationControls>
117+
118+
<PaginationInfo>
119+
<span>
120+
{t('collection.pagination.showing', {
121+
start: startEntry,
122+
end: endEntry,
123+
total: totalCount,
124+
})}
125+
</span>
126+
{userOptions && userOptions.length > 0 && (
127+
<PageSizeLabel>
128+
{t('collection.pagination.itemsPerPage')}:
129+
<PageSizeSelect
130+
value={pageSize}
131+
onChange={e => onPageSizeChange(Number(e.target.value))}
132+
>
133+
{userOptions.map(option => (
134+
<option key={option} value={option}>
135+
{option}
136+
</option>
137+
))}
138+
</PageSizeSelect>
139+
</PageSizeLabel>
140+
)}
141+
</PaginationInfo>
142+
</div>
143+
);
144+
}
145+
146+
Pagination.propTypes = {
147+
currentPage: PropTypes.number.isRequired,
148+
pageCount: PropTypes.number.isRequired,
149+
pageSize: PropTypes.number.isRequired,
150+
totalCount: PropTypes.number.isRequired,
151+
userOptions: PropTypes.arrayOf(PropTypes.number),
152+
onPageChange: PropTypes.func.isRequired,
153+
onPageSizeChange: PropTypes.func.isRequired,
154+
t: PropTypes.func.isRequired,
155+
};
156+
157+
export default translate()(Pagination);

packages/decap-cms-locales/src/en/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ const en = {
5757
longerLoading: 'This might take several minutes',
5858
noEntries: 'No Entries',
5959
},
60+
pagination: {
61+
showing: 'Showing %{start}-%{end} of %{total}',
62+
itemsPerPage: 'Items per page',
63+
page: 'Page %{current} of %{total}',
64+
first: 'First',
65+
previous: 'Previous',
66+
next: 'Next',
67+
last: 'Last',
68+
},
6069
groups: {
6170
other: 'Other',
6271
negateLabel: 'Not %{label}',

0 commit comments

Comments
 (0)