From 2a3e8e55cf378185fb347449ec3d87f54eef854a Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Thu, 12 Jul 2018 19:03:51 +0530 Subject: [PATCH 01/10] feat(client/containers/layout): Add panel link to recent imports --- src/client/containers/layout.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client/containers/layout.js b/src/client/containers/layout.js index 23e77823cd..d0c82b0938 100644 --- a/src/client/containers/layout.js +++ b/src/client/containers/layout.js @@ -140,6 +140,12 @@ class Layout extends React.Component { {' Statistics '} + {!(homepage || hideSearch) &&
Date: Wed, 1 Aug 2018 02:28:25 +0530 Subject: [PATCH 02/10] feat(scripts): Update build-client-js and watch-client-js to track recentImports --- scripts/build-client-js.sh | 2 ++ scripts/watch-client-js.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/build-client-js.sh b/scripts/build-client-js.sh index afcbf26811..ea9eaa2241 100755 --- a/scripts/build-client-js.sh +++ b/scripts/build-client-js.sh @@ -13,6 +13,7 @@ cross-env BABEL_ENV="browser" browserify -t [babelify] \ revision.js \ search.js \ statistics.js \ + recent-imports.js \ -p [ factor-bundle \ -o ../../../static/js/entity-editor.js \ -o ../../../static/js/editor/edit.js \ @@ -25,5 +26,6 @@ cross-env BABEL_ENV="browser" browserify -t [babelify] \ -o ../../../static/js/revision.js \ -o ../../../static/js/search.js \ -o ../../../static/js/statistics.js \ + -o ../../../static/js/recent-imports.js \ ] > ../../../static/js/bundle.js popd diff --git a/scripts/watch-client-js.sh b/scripts/watch-client-js.sh index e985025705..8075e3b8d3 100755 --- a/scripts/watch-client-js.sh +++ b/scripts/watch-client-js.sh @@ -13,6 +13,7 @@ cross-env BABEL_ENV="browser" watchify -t [babelify] \ revision.js \ search.js \ statistics.js \ + recent-imports.js \ -p [ factor-bundle \ -o ../../../static/js/entity-editor.js \ -o ../../../static/js/editor/edit.js \ @@ -25,5 +26,6 @@ cross-env BABEL_ENV="browser" watchify -t [babelify] \ -o ../../../static/js/revision.js \ -o ../../../static/js/search.js \ -o ../../../static/js/statistics.js \ + -o ../../../static/js/recent-imports.js \ ] -o ../../../static/js/bundle.js -dv popd From c03a14512262d662b755a3b6c351355532b8e9ff Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:29:17 +0530 Subject: [PATCH 03/10] feat(server/routes/imports/recent): Add routes to handle recent imports --- src/server/routes/imports/index.js | 27 ++++++++ src/server/routes/imports/recent.js | 98 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/server/routes/imports/index.js create mode 100644 src/server/routes/imports/recent.js diff --git a/src/server/routes/imports/index.js b/src/server/routes/imports/index.js new file mode 100644 index 0000000000..59ac944eb8 --- /dev/null +++ b/src/server/routes/imports/index.js @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 Shivam Tripathi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import ImportRecentRouter from './recent'; +import express from 'express'; + + +const importRouter = express.Router(); + +importRouter.use('/recent', ImportRecentRouter); + +export default importRouter; diff --git a/src/server/routes/imports/recent.js b/src/server/routes/imports/recent.js new file mode 100644 index 0000000000..bf7976cba5 --- /dev/null +++ b/src/server/routes/imports/recent.js @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 Shivam Tripathi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import * as propHelpers from '../../../client/helpers/props'; +import {escapeProps, generateProps} from '../../helpers/props'; +import Layout from '../../../client/containers/layout'; +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import RecentImports from '../../../client/components/pages/recent-imports'; +import express from 'express'; + + +// The limit for number of results fetched from database +const LIMIT = 10; + +const router = express.Router(); + +/* Function to fetch data from the database and create the object to be sent as + response */ +function fetchRecentImportsData(orm, page) { + let pageNumber = page; + + return orm.bookshelf.transaction(async (transacting) => { + // First fetch total imports + const totalResults = + await orm.func.imports.getTotalImports(transacting); + + if (totalResults < ((pageNumber - 1) * LIMIT)) { + pageNumber = Math.ceil(totalResults / LIMIT); + } + const offset = (pageNumber - 1) * LIMIT; + + // Now fetch recent imports according to the generated offset + const recentImports = await orm.func.imports.getRecentImports( + orm, transacting, LIMIT, offset + ); + + return { + currentPage: pageNumber, + limit: LIMIT, + offset, + recentImports, + totalResults + }; + }); +} + +// This handles the router to send initial container to hold recentImports data +function recentImportsRoute(req, res) { + const queryPage = parseInt(req.query.page, 10) || 1; + const props = generateProps(req, res, { + currentPage: queryPage, limit: LIMIT + }); + + const markup = ReactDOMServer.renderToString( + + + + ); + + res.render('target', { + markup, + props: escapeProps(props), + script: '/js/recent-imports.js', + title: 'Recent Imports' + }); +} + +// This handles the data fetching route for recent imports, sends JSON as res +async function rawRecentImportsRoute(req, res) { + const {orm} = req.app.locals; + const queryPage = parseInt(req.query.page, 10) || 1; + const recentImportsData = await fetchRecentImportsData(orm, queryPage); + + res.send(recentImportsData); +} + +router.get('/', recentImportsRoute); +router.get('/raw', rawRecentImportsRoute); + +export default router; From b9e598a018e20739ca7aa39a5268adfa30692b38 Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:30:38 +0530 Subject: [PATCH 04/10] feat(client/controllers/recentImports): Add recentImports controller --- src/client/controllers/recent-imports.js | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/client/controllers/recent-imports.js diff --git a/src/client/controllers/recent-imports.js b/src/client/controllers/recent-imports.js new file mode 100644 index 0000000000..7c9b6f117d --- /dev/null +++ b/src/client/controllers/recent-imports.js @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 Shivam Tripathi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Layout from '../containers/layout'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import RecentImports from '../components/pages/recent-imports'; +import {extractLayoutProps} from '../helpers/props'; + + +const propsTarget = document.getElementById('props'); +const props = propsTarget ? JSON.parse(propsTarget.innerHTML) : {}; +const markup = ( + + + +); + +ReactDOM.hydrate(markup, document.getElementById('target')); From d1e255948ebf02c536f53f36d6d32e1052621cf1 Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:31:48 +0530 Subject: [PATCH 05/10] feat(client/components/pages/parts/recentImports): Add part to hold recentImports results --- .../pages/parts/recent-import-results.js | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/client/components/pages/parts/recent-import-results.js diff --git a/src/client/components/pages/parts/recent-import-results.js b/src/client/components/pages/parts/recent-import-results.js new file mode 100644 index 0000000000..a6a05d82d1 --- /dev/null +++ b/src/client/components/pages/parts/recent-import-results.js @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 Shivam Tripathi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import * as bootstrap from 'react-bootstrap'; +import * as utilsHelper from '../../../helpers/utils'; +import PropTypes from 'prop-types'; +import React from 'react'; + + +const {formatDate} = utilsHelper; +const {Table} = bootstrap; + +/** + * Renders the document and displays the recentImports table. + * @returns {ReactElement} a HTML document which displays the recentImports + */ + +function RecentImportsTable(props) { + const {offset, recentImports} = props; + return ( +
+

Click to review them!

+ + + + + + + + + + + + { + recentImports.map((imports, i) => { + const type = imports.type.toLowerCase(); + const {import_id: id, importedAt} = imports; + return ( + + + + + + + + ); + }) + } + +
#NameTypeDate AddedSource
{i + 1 + offset} + + {imports.defaultAlias.name} + + {imports.type} + {formatDate(new Date(importedAt))} + + {imports.source} +
+
+ ); +} + +RecentImportsTable.propTypes = { + offset: PropTypes.number.isRequired, + recentImports: PropTypes.array.isRequired +}; + +export default RecentImportsTable; From 2563ce0c1e3ceba6fbd6d04531f1c8bbbd47052d Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:32:49 +0530 Subject: [PATCH 06/10] feat(client/components/pages/recentImports): Add base component to display SPA recentImports --- src/client/components/pages/recent-imports.js | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/client/components/pages/recent-imports.js diff --git a/src/client/components/pages/recent-imports.js b/src/client/components/pages/recent-imports.js new file mode 100644 index 0000000000..7468219405 --- /dev/null +++ b/src/client/components/pages/recent-imports.js @@ -0,0 +1,106 @@ +import * as bootstrap from 'react-bootstrap'; +import PaginationProps from '../../helpers/pagination-props'; +import PropTypes from 'prop-types'; +import React from 'react'; +import RecentImportsTable from './parts/recent-import-results'; +import request from 'superagent-bluebird-promise'; + + +const {PageHeader, Pagination} = bootstrap; + +class RecentImports extends React.Component { + constructor(props) { + super(props); + + this.paginationPropsGenerator = PaginationProps({ + displayedPagesRange: 10, + itemsPerPage: props.limit + }); + + this.state = { + currentPage: props.currentPage, + offset: 0, + paginationProps: { + hasBeginningPage: false, + hasEndPage: false, + hasNextPage: false, + hasPreviousPage: false, + totalPages: 0 + }, + recentImports: [] + }; + + this.handleClick = this.handleClick.bind(this); + this.handleCb = this.handleCb.bind(this); + } + + componentDidMount() { + this.handleClick(this.state.currentPage); + } + + componentDidUpdate() { + window.history.replaceState( + null, null, `?page=${this.state.currentPage}` + ); + } + + async handleClick(pageNumber) { + const {currentPage, limit, offset, totalResults, recentImports} = + await request.get(`/imports/recent/raw?page=${pageNumber}`) + .then((res) => JSON.parse(res.text)); + + const paginationProps = this.paginationPropsGenerator( + totalResults, currentPage + ); + + this.setState({ + currentPage, limit, offset, paginationProps, recentImports, + totalResults + }); + } + + handleCb() { + this.handleClick(this.state.currentPage + 1); + } + + render() { + const {currentPage, limit, totalResults, paginationProps} = this.state; + return ( +
+ Recent Imports +

The following data has been imported recently.

+ +

{`Displaying ${limit} of ${totalResults} results`}

+ +
+ ); + } +} + +RecentImports.displayName = 'RecentImports'; +RecentImports.propTypes = { + currentPage: PropTypes.number, + limit: PropTypes.number +}; +RecentImports.defaultProps = { + currentPage: 1, + limit: 10 +}; + +export default RecentImports; + From cb7db9dd1c5cddd6cff6c117221ffa508e31ce18 Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:34:04 +0530 Subject: [PATCH 07/10] feat(server/routes): Add imports route --- src/server/routes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/routes.js b/src/server/routes.js index 20e4fec9b3..58ad28a689 100644 --- a/src/server/routes.js +++ b/src/server/routes.js @@ -21,6 +21,7 @@ import authRouter from './routes/auth'; import creatorRouter from './routes/entity/creator'; import editionRouter from './routes/entity/edition'; import editorRouter from './routes/editor'; +import importRouter from './routes/imports'; import indexRouter from './routes/index'; import publicationRouter from './routes/entity/publication'; import publisherRouter from './routes/entity/publisher'; @@ -37,6 +38,7 @@ function initRootRoutes(app) { app.use('/search', searchRouter); app.use('/register', registerRouter); app.use('/statistics', statisticsRouter); + app.use('/imports', importRouter); } function initPublicationRoutes(app) { From 7725523bc1d849c023608db449c16b8a1b1f4a63 Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:44:30 +0530 Subject: [PATCH 08/10] feat(client/helpers/utils): Add function to extract imports url --- src/client/components/pages/parts/recent-import-results.js | 4 ++-- src/client/helpers/utils.js | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/components/pages/parts/recent-import-results.js b/src/client/components/pages/parts/recent-import-results.js index a6a05d82d1..63abefe159 100644 --- a/src/client/components/pages/parts/recent-import-results.js +++ b/src/client/components/pages/parts/recent-import-results.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import React from 'react'; -const {formatDate} = utilsHelper; +const {formatDate, getImportUrl} = utilsHelper; const {Table} = bootstrap; /** @@ -58,7 +58,7 @@ function RecentImportsTable(props) { {i + 1 + offset} - + {imports.defaultAlias.name} diff --git a/src/client/helpers/utils.js b/src/client/helpers/utils.js index 26cd784796..7c5c8e61c5 100644 --- a/src/client/helpers/utils.js +++ b/src/client/helpers/utils.js @@ -53,3 +53,8 @@ const MILLISECONDS_PER_DAY = 86400000; export function isWithinDayFromNow(date) { return Boolean(Date.now() - date.getTime() < MILLISECONDS_PER_DAY); } + +export function getImportUrl(importType, importId) { + return `/imports/${importType.toLowerCase()}/${importId}`; +} + From cf8bfc8c891ec1875225e8f8fe74cb495e7fc9af Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:45:30 +0530 Subject: [PATCH 09/10] feat(client/helpers/pagination-props): Add function to determine pagination props --- src/client/helpers/pagination-props.js | 154 +++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/client/helpers/pagination-props.js diff --git a/src/client/helpers/pagination-props.js b/src/client/helpers/pagination-props.js new file mode 100644 index 0000000000..be5252a88e --- /dev/null +++ b/src/client/helpers/pagination-props.js @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018 Shivam Tripathi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// @flow + +/* + ______ _ ______ _ +| ___ \ | | | ___ \ (_) +| |_/ / ___ ___ | | __| |_/ / _ __ __ _ _ _ __ ____ +| ___ \ / _ \ / _ \ | |/ /| ___ \| '__|/ _` || || '_ \ |_ / +| |_/ /| (_) || (_) || < | |_/ /| | | (_| || || | | | / / +\____/ \___/ \___/ |_|\_\\____/ |_| \__,_||_||_| |_|/___| + +*/ + +import _ from 'lodash'; + + +type getPaginationPropsGeneratorType = { + displayedPagesRange: number, + itemsPerPage: number +}; + +/** Pagination::validateDefault - Validates, if invalid passed default + * @param {number} currentPage - The current active page + * @param {number} totalResults - Total number of results + * @param {number} itemsPerPage - number of items per page + * @returns {Object} - Returns an object encapsulating validated + * currentPage, totalResults, totalPages + */ +function validatePaginationArgs( + currentPage: number, + totalResults: number, + itemsPerPage: number +) { + const totalPages: number = Math.ceil(totalResults / itemsPerPage); + const args = {currentPage, totalPages, totalResults}; + if (!args.currentPage || + !_.isNumber(args.currentPage) || + args.currentPage < 1 + ) { + args.currentPage = 1; + } + + if (!args.totalPages || + !_.isNumber(args.totalPages) || + args.totalPages < 1 + ) { + args.totalPages = 1; + args.totalResults = itemsPerPage; + } + + if (args.currentPage > args.totalPages) { + args.currentPage = args.totalPages; + } + return args; +} + +/** getPaginationPropsGenerator - Takes in number of pages and items per page, + * defaults to 10, 25 and returns propsGenerator function + * @param {object} args - encapsulates args + * @param {number} args.displayedPagesRange - Range of pages surrounding the + * current page to be displayed + * @param {number} args.itemsPerPage - Number of items per page + * @returns {function} - Returns a function which give pagination props given + * currentPage and totalResults + */ +export default function getPaginationPropsGenerator( + args: getPaginationPropsGeneratorType +) { + const {displayedPagesRange = 10, itemsPerPage = 25} = args; + + /** + * @param {number} totalRes - Total number of results + * @param {number} curPage - Current page + * @returns {object} Returns result encapsulating pagination details + */ + return function getDetails(totalRes: number, curPage: number) { + // Extract results, clean them up + const {currentPage, totalPages, totalResults} = + validatePaginationArgs(curPage, totalRes, itemsPerPage); + + // The first and last page links to be displayed, exactly halfway left + // and right from the currentpage + const halfwayDistance: number = + Math.floor(displayedPagesRange / 2); + let firstPage: number = Math.max(1, currentPage - halfwayDistance); + let lastPage: number = + Math.min(totalPages, currentPage + halfwayDistance); + + // Incase number of pages lying in [firstPage, lastPage] are not + // covering the range due to being at extremes, we adjust the range + const defaultRange: number = lastPage - firstPage + 1; + const defaultDiffInRange = displayedPagesRange - defaultRange; + if (defaultDiffInRange) { + if (lastPage < totalPages) { + lastPage = Math.min(lastPage + defaultDiffInRange, totalPages); + } + if (firstPage > 1) { + firstPage = Math.max(firstPage - defaultDiffInRange, 1); + } + } + + // First result on current page + const firstResultOnCurrentPage: number = _.clamp( + displayedPagesRange * (currentPage - 1), 1, totalResults + ); + const lastResultOnCurrentPage: number = _.clamp( + displayedPagesRange * currentPage, 1, totalResults + ); + + return { + beginningPage: 1, + currentPage, + endPage: totalPages, + firstPage, + firstResultOnCurrentPage, + hasBeginningPage: totalPages > 1, + hasEndPage: totalPages > 1, + hasNextPage: currentPage < totalPages, + hasPreviousPage: (currentPage - 1) > 0, + itemsPerPage, + lastPage, + lastResultOnCurrentPage, + nextPage: currentPage + 1, + pagesRange: _.clamp( + lastPage - firstPage + 1, + 1, totalPages + ), + previousPage: currentPage - 1, + resultsRange: _.clamp( + lastResultOnCurrentPage - firstResultOnCurrentPage + 1, + 1, totalResults + ), + totalPages, + totalResults + }; + }; +} From 6ecc39d89603bf12a1d94a1565c0e2532eb94a81 Mon Sep 17 00:00:00 2001 From: Shivam Tripathi Date: Wed, 1 Aug 2018 02:54:10 +0530 Subject: [PATCH 10/10] feat(client/components/pages/parts/paginationComp): Add pagination component Using React Boostrap v 32.1, we construct pagination component --- .../pages/parts/pagination-component.js | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/client/components/pages/parts/pagination-component.js diff --git a/src/client/components/pages/parts/pagination-component.js b/src/client/components/pages/parts/pagination-component.js new file mode 100644 index 0000000000..af94c14a1c --- /dev/null +++ b/src/client/components/pages/parts/pagination-component.js @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 Shivam Tripathi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import * as bootstrap from 'react-bootstrap'; +import PropTypes from 'prop-types'; +import React from 'react'; +import _ from 'lodash'; + + +const {Pagination, Button} = bootstrap; +const {Item} = Pagination; + +function getPaginationItem({ + active, + content, + handleClick, + link, + pageNumber +}) { + function onClick() { + handleClick(pageNumber); + } + return ( + + {content} + + ); +} + +getPaginationItem.propTypes = { + active: PropTypes.bool.isRequired, + content: PropTypes.string.isRequired, + handleClick: PropTypes.func.isRequired, + link: PropTypes.string.isRequired, + pageNumber: PropTypes.number.isRequired +}; + +function PaginationComponent({ + currentPage, + firstPage, + lastPage, + linkGenerator, + hasPreviousPage, + previousPage, + hasNextPage, + nextPage, + hasBeginningPage, + beginningPage, + hasEndPage, + endPage, + handleClick +}) { + const items = []; + if (hasBeginningPage) { + const link = linkGenerator(beginningPage); + items.push(getPaginationItem( + {active: false, content: '<<', handleClick, link, + pageNumber: beginningPage} + )); + } + + if (hasPreviousPage) { + items.push(getPaginationItem( + {active: false, content: '<', handleClick, + link: linkGenerator(previousPage), pageNumber: previousPage} + )); + } + _.range(firstPage, lastPage).forEach(pageNumber => { + const active = pageNumber === currentPage; + items.push(getPaginationItem( + {active, content: pageNumber.toString(), handleClick, + link: linkGenerator(pageNumber), pageNumber} + )); + }); + + if (hasNextPage) { + items.push(getPaginationItem( + {active: false, content: '>', handleClick, + link: linkGenerator(nextPage), pageNumber: nextPage} + )); + } + + if (hasEndPage) { + items.push(getPaginationItem( + {active: false, content: '>>', handleClick, + link: linkGenerator(endPage), pageNumber: endPage} + )); + } + + return {items} ; +} + +PaginationComponent.displayName = 'PaginationComponent'; +PaginationComponent.propTypes = { + beginningPage: PropTypes.number.isRequired, + currentPage: PropTypes.number.isRequired, + endPage: PropTypes.number.isRequired, + firstPage: PropTypes.number.isRequired, + handleClick: PropTypes.func.isRequired, + hasBeginningPage: PropTypes.bool.isRequired, + hasEndPage: PropTypes.bool.isRequired, + hasNextPage: PropTypes.bool.isRequired, + hasPreviousPage: PropTypes.bool.isRequired, + lastPage: PropTypes.number.isRequired, + linkGenerator: PropTypes.func, + nextPage: PropTypes.number.isRequired, + previousPage: PropTypes.number.isRequired +}; + +PaginationComponent.defaultProps = { + linkGenerator: () => '#' +}; + +export default PaginationComponent;