@kaspernj/api-maker 1.0.215 → 1.0.218

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.
Files changed (52) hide show
  1. package/package.json +5 -4
  2. package/src/base-model.mjs +1 -1
  3. package/src/bootstrap/attribute-row/basic-style.scss +9 -0
  4. package/src/bootstrap/attribute-row/index.jsx +84 -0
  5. package/src/bootstrap/attribute-rows.jsx +27 -0
  6. package/src/bootstrap/card.jsx +135 -0
  7. package/src/bootstrap/checkbox.jsx +79 -0
  8. package/src/bootstrap/checkboxes.jsx +122 -0
  9. package/src/bootstrap/index.js +0 -0
  10. package/src/bootstrap/input.jsx +160 -0
  11. package/src/bootstrap/invalid-feedback.jsx +31 -0
  12. package/src/bootstrap/live-table/model-row.jsx +150 -0
  13. package/src/bootstrap/live-table.jsx +399 -0
  14. package/src/bootstrap/paginate.jsx +153 -0
  15. package/src/bootstrap/radio-buttons.jsx +87 -0
  16. package/src/bootstrap/select.jsx +110 -0
  17. package/src/bootstrap/sort-link.jsx +102 -0
  18. package/src/collection-loader.jsx +7 -8
  19. package/src/inputs/auto-submit.mjs +37 -0
  20. package/src/inputs/checkbox.jsx +97 -0
  21. package/src/inputs/checkboxes.jsx +113 -0
  22. package/src/inputs/id-for-component.mjs +15 -0
  23. package/src/inputs/input-wrapper.jsx +170 -0
  24. package/src/inputs/input.jsx +235 -0
  25. package/src/inputs/money.jsx +177 -0
  26. package/src/inputs/name-for-component.mjs +15 -0
  27. package/src/inputs/select.jsx +87 -0
  28. package/src/model-class-require.mjs +1 -1
  29. package/src/model-name.mjs +6 -2
  30. package/src/params.mjs +7 -0
  31. package/src/super-admin/index-page/index.jsx +44 -0
  32. package/src/super-admin/index.jsx +46 -0
  33. package/src/super-admin/layout/header/index.jsx +60 -0
  34. package/src/super-admin/layout/header/style.scss +124 -0
  35. package/src/super-admin/layout/index.jsx +156 -0
  36. package/src/super-admin/layout/menu/index.jsx +116 -0
  37. package/src/super-admin/layout/menu/menu-content.jsx +55 -0
  38. package/src/super-admin/layout/menu/menu-item/index.jsx +27 -0
  39. package/src/super-admin/layout/menu/menu-item/style.scss +30 -0
  40. package/src/super-admin/layout/menu/style.scss +103 -0
  41. package/src/super-admin/layout/no-access.jsx +16 -0
  42. package/src/super-admin/layout/style.scss +25 -0
  43. package/src/super-admin/show-page.jsx +9 -0
  44. package/src/table/column-identifier.mjs +23 -0
  45. package/src/table/column-visible.mjs +7 -0
  46. package/src/table/model-row.jsx +182 -0
  47. package/src/table/select-calculator.mjs +48 -0
  48. package/src/table/style.scss +72 -0
  49. package/src/table/table-settings.js +175 -0
  50. package/src/table/table.jsx +498 -0
  51. package/src/table/variables.scss +11 -0
  52. package/src/table/with-breakpoint.jsx +48 -0
@@ -0,0 +1,150 @@
1
+ import {digg, digs} from "diggerize"
2
+ import inflection from "inflection"
3
+ import MoneyFormatter from "../../money-formatter"
4
+ import PropTypes from "prop-types"
5
+
6
+ import Link from "../../link"
7
+
8
+ export default class ApiMakerBootStrapLiveTableModelRow extends React.PureComponent {
9
+ static propTypes = {
10
+ model: PropTypes.object.isRequired,
11
+ liveTable: PropTypes.object.isRequired
12
+ }
13
+
14
+ modelCallbackArgs = this._modelCallbackArgs()
15
+
16
+ render() {
17
+ const {model} = digs(this.props, "model")
18
+ const {modelClass} = digs(this.props.liveTable.props, "modelClass")
19
+ const {actionsContent, columnsContent, destroyEnabled, editModelPath, viewModelPath} = digg(this, "props", "liveTable", "props")
20
+ const {columns} = digg(this, "props", "liveTable", "shape")
21
+
22
+ let editPath, viewPath
23
+
24
+ if (editModelPath && model.can("edit")) editPath = editModelPath(this.modelCallbackArgs)
25
+ if (viewModelPath && model.can("show")) viewPath = viewModelPath(this.modelCallbackArgs)
26
+
27
+ return (
28
+ <tr className={`${inflection.dasherize(modelClass.modelClassData().paramKey)}-row`} data-model-id={model.id()}>
29
+ {columns && this.columnsContentFromColumns(model)}
30
+ {!columns && columnsContent && columnsContent(this.modelCallbackArgs)}
31
+ <td className="actions-column text-end text-nowrap text-right">
32
+ {actionsContent && actionsContent(this.modelCallbackArgs)}
33
+ {viewPath &&
34
+ <Link className="view-button" to={viewPath}>
35
+ <i className="fa fa-search la la-search" />
36
+ </Link>
37
+ }
38
+ {editPath &&
39
+ <Link className="edit-button" to={editPath}>
40
+ <i className="fa fa-edit la la-edit" />
41
+ </Link>
42
+ }
43
+ {destroyEnabled && model.can("destroy") &&
44
+ <a className="destroy-button" href="#" onClick={this.onDestroyClicked}>
45
+ <i className="fa fa-trash la la-trash" />
46
+ </a>
47
+ }
48
+ </td>
49
+ </tr>
50
+ )
51
+ }
52
+
53
+ columnClassNamesForColumn (column) {
54
+ const classNames = ["live-table-column"]
55
+
56
+ if (column.commonProps && column.commonProps.className) classNames.push(column.commonProps.className)
57
+ if (column.columnProps && column.columnProps.className) classNames.push(column.columnProps.className)
58
+ if (column.textCenter) classNames.push("text-center")
59
+ if (column.textRight) classNames.push("text-end text-right")
60
+
61
+ return classNames
62
+ }
63
+
64
+ columnsContentFromColumns (model) {
65
+ const {columns} = digs(this.props.liveTable.shape, "columns")
66
+
67
+ return columns.map((column) =>
68
+ <td
69
+ className={classNames(this.columnClassNamesForColumn(column))}
70
+ data-identifier={this.props.liveTable.identifierForColumn(column)}
71
+ key={this.props.liveTable.identifierForColumn(column)}
72
+ >
73
+ {column.content && this.columnContentFromContentArg(column, model)}
74
+ {!column.content && column.attribute && this.columnsContentFromAttributeAndPath(column, model)}
75
+ </td>
76
+ )
77
+ }
78
+
79
+ columnContentFromContentArg (column, model) {
80
+ const value = column.content(this.modelCallbackArgs)
81
+
82
+ return this.presentColumnValue(value)
83
+ }
84
+
85
+ columnsContentFromAttributeAndPath (column, model) {
86
+ const {attribute} = digs(column, "attribute")
87
+ const currentModelClass = this.props.modelClass
88
+ const path = column.path || []
89
+
90
+ if (path.length > 0) throw new Error("'path' support not implemented")
91
+
92
+ if (!(attribute in model)) throw new Error(`${currentModelClass.modelName().name} doesn't respond to ${attribute}`)
93
+
94
+ const value = model[attribute]()
95
+
96
+ return this.presentColumnValue(value)
97
+ }
98
+
99
+ _modelCallbackArgs () {
100
+ const {model} = digs(this.props, "model")
101
+ const modelArgName = inflection.camelize(this.props.liveTable.props.modelClass.modelClassData().name, true)
102
+ const modelCallbackArgs = {}
103
+
104
+ modelCallbackArgs[modelArgName] = model
105
+
106
+ return modelCallbackArgs
107
+ }
108
+
109
+ onDestroyClicked = async (e) => {
110
+ e.preventDefault()
111
+
112
+ const {destroyMessage} = digg(this, "props", "liveTable", "props")
113
+ const {model} = digs(this.props, "model")
114
+
115
+ if (!confirm(I18n.t("js.shared.are_you_sure"))) {
116
+ return
117
+ }
118
+
119
+ try {
120
+ await model.destroy()
121
+
122
+ if (destroyMessage) {
123
+ FlashMessage.success(destroyMessage)
124
+ }
125
+ } catch (error) {
126
+ FlashMessage.errorResponse(error)
127
+ }
128
+ }
129
+
130
+ presentColumnValue (value) {
131
+ if (value instanceof Date) {
132
+ return I18n.l("time.formats.default", value)
133
+ } else if (MoneyFormatter.isMoney(value)) {
134
+ return MoneyFormatter.format(value)
135
+ } else if (typeof value == "boolean") {
136
+ if (value) {
137
+ return I18n.t("js.shared.yes")
138
+ }
139
+
140
+ return I18n.t("js.shared.no")
141
+ } else if (Array.isArray(value)) {
142
+ return value
143
+ .map((valuePart) => this.presentColumnValue(valuePart))
144
+ .filter((valuePart) => Boolean(valuePart))
145
+ .join(", ")
146
+ }
147
+
148
+ return value
149
+ }
150
+ }
@@ -0,0 +1,399 @@
1
+ import Card from "./card"
2
+ import classNames from "classnames"
3
+ import Collection from "../collection"
4
+ import CollectionLoader from "../collection-loader"
5
+ import {debounce} from "debounce"
6
+ import {digg, digs} from "diggerize"
7
+ import instanceOfClassName from "../instance-of-class-name"
8
+ import ModelRow from "./live-table/model-row"
9
+ import Paginate from "./paginate"
10
+ import Params from "../params"
11
+ import PropTypes from "prop-types"
12
+ import React from "react"
13
+ import Shape from "set-state-compare/src/shape"
14
+ import SortLink from "./sort-link"
15
+
16
+ export default class ApiMakerBootstrapLiveTable extends React.PureComponent {
17
+ static defaultProps = {
18
+ card: true,
19
+ destroyEnabled: true,
20
+ filterCard: true,
21
+ filterSubmitButton: true,
22
+ noRecordsAvailableContent: undefined,
23
+ noRecordsFoundContent: undefined,
24
+ preloads: [],
25
+ select: {}
26
+ }
27
+
28
+ static propTypes = {
29
+ abilities: PropTypes.object,
30
+ actionsContent: PropTypes.func,
31
+ appHistory: PropTypes.object,
32
+ card: PropTypes.bool.isRequired,
33
+ className: PropTypes.string,
34
+ collection: PropTypes.oneOfType([
35
+ instanceOfClassName("ApiMakerCollection"),
36
+ PropTypes.instanceOf(Collection)
37
+ ]),
38
+ columns: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
39
+ columnsContent: PropTypes.func,
40
+ controls: PropTypes.func,
41
+ defaultParams: PropTypes.object,
42
+ destroyEnabled: PropTypes.bool.isRequired,
43
+ destroyMessage: PropTypes.string,
44
+ editModelPath: PropTypes.func,
45
+ filterCard: PropTypes.bool.isRequired,
46
+ filterContent: PropTypes.func,
47
+ filterSubmitButton: PropTypes.bool.isRequired,
48
+ filterSubmitLabel: PropTypes.node,
49
+ headersContent: PropTypes.func,
50
+ header: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
51
+ groupBy: PropTypes.array,
52
+ modelClass: PropTypes.func.isRequired,
53
+ noRecordsAvailableContent: PropTypes.func,
54
+ noRecordsFoundContent: PropTypes.func,
55
+ onModelsLoaded: PropTypes.func,
56
+ paginateContent: PropTypes.func,
57
+ paginationComponent: PropTypes.func,
58
+ preloads: PropTypes.array.isRequired,
59
+ queryName: PropTypes.string,
60
+ select: PropTypes.object,
61
+ selectColumns: PropTypes.object,
62
+ viewModelPath: PropTypes.func
63
+ }
64
+
65
+ constructor (props) {
66
+ super(props)
67
+
68
+ let queryName = props.queryName
69
+
70
+ if (!queryName) {
71
+ queryName = digg(props.modelClass.modelClassData(), "collectionKey")
72
+ }
73
+
74
+ this.shape = new Shape(this, {
75
+ columns: this.columnsAsArray(),
76
+ models: undefined,
77
+ overallCount: undefined,
78
+ query: undefined,
79
+ queryName,
80
+ queryQName: `${queryName}_q`,
81
+ queryPageName: `${queryName}_page`,
82
+ qParams: undefined,
83
+ result: undefined,
84
+ showNoRecordsAvailableContent: false,
85
+ showNoRecordsFoundContent: false
86
+ })
87
+ }
88
+
89
+ columnsAsArray () {
90
+ if (typeof this.props.columns == "function") {
91
+ return this.props.columns()
92
+ }
93
+
94
+ return this.props.columns
95
+ }
96
+
97
+ render () {
98
+ const {modelClass, noRecordsAvailableContent, noRecordsFoundContent} = digs(this.props, "modelClass", "noRecordsAvailableContent", "noRecordsFoundContent")
99
+ const {collection, defaultParams, onModelsLoaded, preloads, select, selectColumns} = this.props
100
+ const {
101
+ overallCount,
102
+ qParams,
103
+ query,
104
+ result,
105
+ models,
106
+ showNoRecordsAvailableContent,
107
+ showNoRecordsFoundContent
108
+ } = digs(
109
+ this.shape,
110
+ "overallCount",
111
+ "qParams",
112
+ "query",
113
+ "result",
114
+ "models",
115
+ "showNoRecordsAvailableContent",
116
+ "showNoRecordsFoundContent"
117
+ )
118
+
119
+ return (
120
+ <div className={this.className()}>
121
+ <CollectionLoader
122
+ abilities={this.abilitiesToLoad()}
123
+ defaultParams={defaultParams}
124
+ collection={collection}
125
+ component={this}
126
+ modelClass={modelClass}
127
+ noRecordsAvailableContent={noRecordsAvailableContent}
128
+ noRecordsFoundContent={noRecordsFoundContent}
129
+ onModelsLoaded={onModelsLoaded}
130
+ preloads={preloads}
131
+ select={select}
132
+ selectColumns={selectColumns}
133
+ />
134
+ {showNoRecordsAvailableContent &&
135
+ <div className="live-table--no-records-available-content">
136
+ {noRecordsAvailableContent({models, qParams, overallCount})}
137
+ </div>
138
+ }
139
+ {showNoRecordsFoundContent &&
140
+ <div className="live-table--no-records-found-content">
141
+ {noRecordsFoundContent({models, qParams, overallCount})}
142
+ </div>
143
+ }
144
+ {qParams && query && result && models && !showNoRecordsAvailableContent && !showNoRecordsFoundContent &&
145
+ this.cardOrTable()
146
+ }
147
+ </div>
148
+ )
149
+ }
150
+
151
+ abilitiesToLoad () {
152
+ const abilitiesToLoad = {}
153
+ const {abilities, modelClass} = this.props
154
+ const ownAbilities = []
155
+
156
+ if (this.props.destroyEnabled) {
157
+ ownAbilities.push("destroy")
158
+ }
159
+
160
+ if (this.props.editModelPath) {
161
+ ownAbilities.push("edit")
162
+ }
163
+
164
+ if (this.props.viewModelPath) {
165
+ ownAbilities.push("show")
166
+ }
167
+
168
+ if (ownAbilities.length > 0) {
169
+ const modelClassName = digg(modelClass.modelClassData(), "name")
170
+
171
+ abilitiesToLoad[modelClassName] = ownAbilities
172
+ }
173
+
174
+ if (abilities) {
175
+ for (const modelName in abilities) {
176
+ if (!(modelName in abilitiesToLoad)) {
177
+ abilitiesToLoad[modelName] = []
178
+ }
179
+
180
+ for (const ability of abilities[modelName]) {
181
+ abilitiesToLoad[modelName].push(ability)
182
+ }
183
+ }
184
+ }
185
+
186
+ return abilitiesToLoad
187
+ }
188
+
189
+ cardOrTable () {
190
+ const {
191
+ abilities,
192
+ actionsContent,
193
+ appHistory,
194
+ card,
195
+ className,
196
+ collection,
197
+ columns,
198
+ columnsContent,
199
+ controls,
200
+ defaultParams,
201
+ destroyEnabled,
202
+ destroyMessage,
203
+ editModelPath,
204
+ filterCard,
205
+ filterContent,
206
+ filterSubmitButton,
207
+ filterSubmitLabel,
208
+ headersContent,
209
+ header,
210
+ groupBy,
211
+ modelClass,
212
+ noRecordsAvailableContent,
213
+ noRecordsFoundContent,
214
+ onModelsLoaded,
215
+ paginateContent,
216
+ paginationComponent,
217
+ preloads,
218
+ queryName,
219
+ select,
220
+ selectColumns,
221
+ viewModelPath,
222
+ ...restProps
223
+ } = this.props
224
+ const {models, qParams, query, result} = digs(this.shape, "models", "qParams", "query", "result")
225
+
226
+ let controlsContent, headerContent, PaginationComponent
227
+
228
+ if (controls) {
229
+ controlsContent = controls({models, qParams, query, result})
230
+ }
231
+
232
+ if (typeof header == "function") {
233
+ headerContent = header({models, qParams, query, result})
234
+ } else if (header) {
235
+ headerContent = header
236
+ }
237
+
238
+ if (!paginateContent) {
239
+ if (paginationComponent) {
240
+ PaginationComponent = paginationComponent
241
+ } else {
242
+ PaginationComponent = Paginate
243
+ }
244
+ }
245
+
246
+ return (
247
+ <>
248
+ {filterContent && filterCard &&
249
+ <Card className="live-table--filter-card mb-4">
250
+ {this.filterForm()}
251
+ </Card>
252
+ }
253
+ {filterContent && !filterCard &&
254
+ this.filterForm()
255
+ }
256
+ {card &&
257
+ <Card className={classNames("mb-4", className)} controls={controlsContent} header={headerContent} table {...restProps}>
258
+ {this.tableContent()}
259
+ </Card>
260
+ }
261
+ {!card &&
262
+ <table className={className} {...restProps}>
263
+ {this.tableContent()}
264
+ </table>
265
+ }
266
+ {result && PaginationComponent &&
267
+ <PaginationComponent result={result} />
268
+ }
269
+ {result && paginateContent &&
270
+ paginateContent({result})
271
+ }
272
+ </>
273
+ )
274
+ }
275
+
276
+ filterForm = () => {
277
+ const {submitFilterDebounce} = digs(this, "submitFilterDebounce")
278
+ const {filterContent, filterSubmitButton} = digs(this.props, "filterContent", "filterSubmitButton")
279
+ const {filterSubmitLabel} = this.props
280
+ const {qParams} = digs(this.shape, "qParams")
281
+
282
+ return (
283
+ <form className="live-table--filter-form" onSubmit={this.onFilterFormSubmit} ref="filterForm">
284
+ {filterContent({
285
+ onFilterChanged: this.submitFilter,
286
+ onFilterChangedWithDelay: submitFilterDebounce,
287
+ qParams
288
+ })}
289
+ {filterSubmitButton &&
290
+ <input
291
+ className="btn btn-primary live-table--submit-filter-button"
292
+ type="submit"
293
+ value={filterSubmitLabel || I18n.t("js.api_maker_bootstrap.live_table.filter")}
294
+ />
295
+ }
296
+ </form>
297
+ )
298
+ }
299
+
300
+ tableContent () {
301
+ const {query, models} = this.shape
302
+
303
+ return (
304
+ <>
305
+ <thead>
306
+ <tr>
307
+ {this.shape.columns && this.headersContentFromColumns()}
308
+ {this.props.headersContent && this.props.headersContent({query})}
309
+ <th />
310
+ </tr>
311
+ </thead>
312
+ <tbody>
313
+ {models.map((model) =>
314
+ <ModelRow key={model.id()} liveTable={this} model={model} />
315
+ )}
316
+ </tbody>
317
+ </>
318
+ )
319
+ }
320
+
321
+ className () {
322
+ const classNames = ["component-api-maker-live-table"]
323
+
324
+ if (this.props.className)
325
+ classNames.push(this.props.className)
326
+
327
+ return classNames.join(" ")
328
+ }
329
+
330
+ headersContentFromColumns () {
331
+ const {columns} = digs(this.shape, "columns")
332
+
333
+ return columns.map((column) =>
334
+ <th
335
+ className={classNames(...this.headerClassNameForColumn(column))}
336
+ data-identifier={this.identifierForColumn(column)}
337
+ key={this.identifierForColumn(column)}
338
+ >
339
+ {column.sortKey && this.shape.query &&
340
+ <SortLink attribute={column.sortKey} query={this.shape.query} title={this.headerLabelForColumn(column)} />
341
+ }
342
+ {(!column.sortKey || !this.shape.query) &&
343
+ this.headerLabelForColumn(column)
344
+ }
345
+ </th>
346
+ )
347
+ }
348
+
349
+ headerClassNameForColumn (column) {
350
+ const classNames = ["live-table-header"]
351
+
352
+ if (column.commonProps && column.commonProps.className) classNames.push(column.commonProps.className)
353
+ if (column.headerProps && column.headerProps.className) classNames.push(column.headerProps.className)
354
+ if (column.textCenter) classNames.push("text-center")
355
+ if (column.textRight) classNames.push("text-end text-right")
356
+
357
+ return classNames
358
+ }
359
+
360
+ headerLabelForColumn (column) {
361
+ if ("label" in column) {
362
+ if (typeof column.label == "function") {
363
+ return column.label()
364
+ } else {
365
+ return column.label
366
+ }
367
+ }
368
+
369
+ if (column.attribute) return this.props.modelClass.humanAttributeName(column.attribute)
370
+
371
+ throw new Error("No 'label' or 'attribute' was given")
372
+ }
373
+
374
+ identifierForColumn (column) {
375
+ if (column.identifier) return column.identifier
376
+ if (column.attribute) return `attribute-${column.attribute}`
377
+ if (column.sortKey) return `sort-key-${column.sortKey}`
378
+
379
+ throw new Error("No 'attribute', 'identifier' or 'sortKey' was given")
380
+ }
381
+
382
+ onFilterFormSubmit = (e) => {
383
+ e.preventDefault()
384
+ this.submitFilter()
385
+ }
386
+
387
+ submitFilter = () => {
388
+ const {appHistory} = this.props
389
+ const qParams = Params.serializeForm(this.refs.filterForm)
390
+ const {queryQName} = this.shape
391
+
392
+ const changeParamsParams = {}
393
+ changeParamsParams[queryQName] = qParams
394
+
395
+ Params.changeParams(changeParamsParams, {appHistory})
396
+ }
397
+
398
+ submitFilterDebounce = debounce(digg(this, "submitFilter"))
399
+ }
@@ -0,0 +1,153 @@
1
+ import instanceOfClassName from "../instance-of-class-name"
2
+ import Link from "../link"
3
+ import PropTypes from "prop-types"
4
+ import propTypesExact from "prop-types-exact"
5
+ import qs from "qs"
6
+ import React from "react"
7
+ import Result from "../result"
8
+
9
+ export default class ApiMakerBootstrapPaginate extends React.PureComponent {
10
+ static propTypes = propTypesExact({
11
+ result: PropTypes.oneOfType([
12
+ instanceOfClassName("ApiMakerResult"),
13
+ PropTypes.instanceOf(Result)
14
+ ]).isRequired
15
+ })
16
+
17
+ state = {
18
+ pages: this.pages()
19
+ }
20
+
21
+ componentDidUpdate (prevProps) {
22
+ if (prevProps.result != this.props.result) {
23
+ this.setState({pages: this.pages()})
24
+ }
25
+ }
26
+
27
+ isPageActiveClass (pageNumber) {
28
+ if (this.props.result.currentPage() == pageNumber)
29
+ return "active"
30
+ }
31
+
32
+ pages () {
33
+ const currentPage = this.props.result.currentPage()
34
+ const pages = []
35
+ const totalPages = this.props.result.totalPages()
36
+ let pagesFrom = currentPage - 5
37
+ let pagesTo = currentPage + 5
38
+
39
+ if (pagesFrom < 1)
40
+ pagesFrom = 1
41
+
42
+ if (pagesTo > totalPages)
43
+ pagesTo = totalPages
44
+
45
+ for (let i = pagesFrom; i <= pagesTo; i++) {
46
+ pages.push(i)
47
+ }
48
+
49
+ return pages
50
+ }
51
+
52
+ pagePath (pageNumber) {
53
+ let pageKey = this.props.result.data.collection.queryArgs.pageKey
54
+
55
+ if (!pageKey)
56
+ pageKey = "page"
57
+
58
+ const currentParams = qs.parse(globalThis.location.search.substr(1))
59
+ currentParams[pageKey] = pageNumber
60
+ const newParams = qs.stringify(currentParams)
61
+ const newPath = `${location.pathname}?${newParams}`
62
+
63
+ return newPath
64
+ }
65
+
66
+ previousPagePath () {
67
+ let previousPage
68
+
69
+ if (this.props.result.currentPage() > 1) {
70
+ previousPage = this.props.result.currentPage() - 1
71
+ } else {
72
+ previousPage = this.props.result.currentPage()
73
+ }
74
+
75
+ return this.pagePath(previousPage)
76
+ }
77
+
78
+ nextPagePath () {
79
+ let nextPage
80
+
81
+ if (this.props.result.currentPage() < this.props.result.totalPages()) {
82
+ nextPage = this.props.result.currentPage() + 1
83
+ } else {
84
+ nextPage = this.props.result.currentPage()
85
+ }
86
+
87
+ return this.pagePath(nextPage)
88
+ }
89
+
90
+ showBackwardsDots () {
91
+ const currentPage = this.props.result.currentPage()
92
+ return (currentPage - 5 > 1)
93
+ }
94
+
95
+ showForwardsDots () {
96
+ const currentPage = this.props.result.currentPage()
97
+ const totalPages = this.props.result.totalPages()
98
+ return (currentPage + 5 < totalPages)
99
+ }
100
+
101
+ render () {
102
+ const {result} = this.props
103
+ const {pages} = this.state
104
+
105
+ return (
106
+ <>
107
+ <ul className="pagination" data-pages-length={pages.length}>
108
+ <li className={`page-item ${result.currentPage() <= 1 ? "disabled" : ""}`} key="page-first">
109
+ <Link className="page-link" to={this.pagePath(1)}>
110
+
111
+ </Link>
112
+ </li>
113
+ <li className={`page-item ${result.currentPage() <= 1 ? "disabled" : ""}`} key="page-previous">
114
+ <Link className="page-link" to={this.previousPagePath()}>
115
+
116
+ </Link>
117
+ </li>
118
+ {this.showBackwardsDots() &&
119
+ <li className="page-item">
120
+ <a className="page-link disabled" href="#">
121
+ &hellip;
122
+ </a>
123
+ </li>
124
+ }
125
+ {pages.map((page) =>
126
+ <li className={`page-item ${this.isPageActiveClass(page)}`} key={`page-${page}`}>
127
+ <Link className="page-link" to={this.pagePath(page)}>
128
+ {page}
129
+ </Link>
130
+ </li>
131
+ )}
132
+ {this.showForwardsDots() &&
133
+ <li className="page-item">
134
+ <a className="page-link disabled" href="#">
135
+ &hellip;
136
+ </a>
137
+ </li>
138
+ }
139
+ <li className={`page-item ${result.currentPage() >= result.totalPages() ? "disabled" : ""}`} key="page-next">
140
+ <Link className="page-link" to={this.nextPagePath()}>
141
+
142
+ </Link>
143
+ </li>
144
+ <li className={`page-item ${result.currentPage() >= result.totalPages() ? "disabled" : ""}`} key="page-last">
145
+ <Link className="page-link" to={this.pagePath(result.totalPages())}>
146
+
147
+ </Link>
148
+ </li>
149
+ </ul>
150
+ </>
151
+ )
152
+ }
153
+ }