@kaspernj/api-maker 1.0.216 → 1.0.217

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 (48) hide show
  1. package/package.json +2 -1
  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 +1 -1
  30. package/src/super-admin/index.jsx +11 -0
  31. package/src/super-admin/layout/header/index.jsx +60 -0
  32. package/src/super-admin/layout/header/style.scss +124 -0
  33. package/src/super-admin/layout/index.jsx +156 -0
  34. package/src/super-admin/layout/menu/index.jsx +116 -0
  35. package/src/super-admin/layout/menu/menu-content.jsx +106 -0
  36. package/src/super-admin/layout/menu/menu-item/index.jsx +27 -0
  37. package/src/super-admin/layout/menu/menu-item/style.scss +30 -0
  38. package/src/super-admin/layout/menu/style.scss +103 -0
  39. package/src/super-admin/layout/style.scss +25 -0
  40. package/src/table/column-identifier.mjs +23 -0
  41. package/src/table/column-visible.mjs +7 -0
  42. package/src/table/model-row.jsx +182 -0
  43. package/src/table/select-calculator.mjs +48 -0
  44. package/src/table/style.scss +72 -0
  45. package/src/table/table-settings.js +175 -0
  46. package/src/table/table.jsx +498 -0
  47. package/src/table/variables.scss +11 -0
  48. package/src/table/with-breakpoint.jsx +48 -0
@@ -0,0 +1,87 @@
1
+ import {digs} from "diggerize"
2
+ import inputWrapper from "../inputs/input-wrapper"
3
+ import InvalidFeedback from "./invalid-feedback"
4
+ import PropTypes from "prop-types"
5
+ import propTypesExact from "prop-types-exact"
6
+ import React from "react"
7
+
8
+ class ApiMakerBootstrapRadioButtons extends React.PureComponent {
9
+ static propTypes = propTypesExact({
10
+ attribute: PropTypes.string,
11
+ collection: PropTypes.array.isRequired,
12
+ defaultValue: PropTypes.oneOfType([
13
+ PropTypes.number,
14
+ PropTypes.string
15
+ ]),
16
+ id: PropTypes.string,
17
+ inputProps: PropTypes.object.isRequired,
18
+ name: PropTypes.string,
19
+ model: PropTypes.object,
20
+ onChange: PropTypes.func,
21
+ onMatchValidationError: PropTypes.func,
22
+ wrapperClassName: PropTypes.string,
23
+ wrapperOpts: PropTypes.object.isRequired
24
+ })
25
+
26
+ render () {
27
+ return (
28
+ <div className={this.wrapperClassName()}>
29
+ <input {...this.props.inputProps} type="hidden" value="" />
30
+ {this.props.collection.map((option, index) => this.optionElement(option, index))}
31
+ </div>
32
+ )
33
+ }
34
+
35
+ inputRadioClassName () {
36
+ const classNames = ["form-check-input"]
37
+
38
+ if (this.props.wrapperOpts.errors.length > 0)
39
+ classNames.push("is-invalid")
40
+
41
+ return classNames.join(" ")
42
+ }
43
+
44
+ generatedId () {
45
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
46
+ }
47
+
48
+ optionElement (option, index) {
49
+ const {collection} = digs(this.props, "collection")
50
+ const {onChange} = this.props
51
+ const id = this.generatedId()
52
+
53
+ return (
54
+ <div className="form-check" key={`option-${option[1]}`}>
55
+ <input
56
+ className={this.inputRadioClassName()}
57
+ data-option-value={option[1]}
58
+ defaultChecked={option[1] == this.props.inputProps.defaultValue}
59
+ id={id}
60
+ name={this.props.inputProps.name}
61
+ onChange={onChange}
62
+ type="radio"
63
+ value={option[1]}
64
+ />
65
+
66
+ <label className="form-check-label" htmlFor={id}>
67
+ {option[0]}
68
+ </label>
69
+
70
+ {(index + 1) == collection.length && this.props.wrapperOpts.errors.length > 0 &&
71
+ <InvalidFeedback errors={this.props.wrapperOpts.errors} />
72
+ }
73
+ </div>
74
+ )
75
+ }
76
+
77
+ wrapperClassName () {
78
+ const classNames = ["component-bootstrap-radio-buttons"]
79
+
80
+ if (this.props.wrapperClassName)
81
+ classNames.push(this.props.wrapperClassName)
82
+
83
+ return classNames.join(" ")
84
+ }
85
+ }
86
+
87
+ export default inputWrapper(ApiMakerBootstrapRadioButtons)
@@ -0,0 +1,110 @@
1
+ import {digs} from "diggerize"
2
+ import inputWrapper from "../inputs/input-wrapper"
3
+ import {Select} from "../inputs/select"
4
+ import InvalidFeedback from "./invalid-feedback"
5
+ import PropTypes from "prop-types"
6
+ import React from "react"
7
+
8
+ class ApiMakerBootstrapSelect extends React.PureComponent {
9
+ static propTypes = {
10
+ attribute: PropTypes.string,
11
+ className: PropTypes.string,
12
+ description: PropTypes.node,
13
+ id: PropTypes.string,
14
+ hint: PropTypes.node,
15
+ hintBottom: PropTypes.node,
16
+ inputProps: PropTypes.object.isRequired,
17
+ label: PropTypes.node,
18
+ labelContainerClassName: PropTypes.string,
19
+ model: PropTypes.object,
20
+ wrapperClassName: PropTypes.string,
21
+ wrapperOpts: PropTypes.object.isRequired
22
+ }
23
+
24
+ render () {
25
+ const {
26
+ children,
27
+ className,
28
+ description,
29
+ id,
30
+ inputProps,
31
+ hint,
32
+ hintBottom,
33
+ label,
34
+ labelContainerClassName,
35
+ name,
36
+ wrapperClassName,
37
+ wrapperOpts,
38
+ ...restProps
39
+ } = this.props
40
+ const {ref, ...forwardedInputProps} = inputProps
41
+ const {errors} = digs(wrapperOpts, "errors")
42
+
43
+ return (
44
+ <div className={this.wrapperClassName()}>
45
+ {wrapperOpts.label &&
46
+ <div className={labelContainerClassName ? labelContainerClassName : null}>
47
+ <label className={this.labelClassName()} htmlFor={inputProps.id}>
48
+ {wrapperOpts.label}
49
+ </label>
50
+ </div>
51
+ }
52
+ {description &&
53
+ <div className="mb-4">
54
+ {description}
55
+ </div>
56
+ }
57
+ {hint &&
58
+ <span className="font-smoothing font-xs form-text text-muted">
59
+ {hint}
60
+ </span>
61
+ }
62
+ {children}
63
+ {!children &&
64
+ <Select
65
+ {...forwardedInputProps}
66
+ className={this.selectClassName()}
67
+ inputProps={inputProps}
68
+ inputRef={ref}
69
+ wrapperOpts={wrapperOpts}
70
+ {...restProps}
71
+ />
72
+ }
73
+ {hintBottom &&
74
+ <span className="form-text text-muted font-smoothing font-xs">
75
+ {hintBottom}
76
+ </span>
77
+ }
78
+ {errors.length > 0 && <InvalidFeedback errors={errors} />}
79
+ </div>
80
+ )
81
+ }
82
+
83
+ labelClassName () {
84
+ const classNames = ["form-group-label"]
85
+
86
+ if (this.props.labelClassName)
87
+ classNames.push(this.props.labelClassName)
88
+
89
+ return classNames.join(" ")
90
+ }
91
+
92
+ selectClassName () {
93
+ const classNames = ["form-control"]
94
+
95
+ if (this.props.className) classNames.push(this.props.className)
96
+ if (this.props.wrapperOpts.errors.length > 0) classNames.push("is-invalid")
97
+
98
+ return classNames.join(" ")
99
+ }
100
+
101
+ wrapperClassName () {
102
+ const classNames = ["form-group", "component-bootstrap-select"]
103
+
104
+ if (this.props.wrapperClassName) classNames.push(this.props.wrapperClassName)
105
+
106
+ return classNames.join(" ")
107
+ }
108
+ }
109
+
110
+ export default inputWrapper(ApiMakerBootstrapSelect, {type: "select"})
@@ -0,0 +1,102 @@
1
+ import {digg, digs} from "diggerize"
2
+ import inflection from "inflection"
3
+ import PropTypes from "prop-types"
4
+ import qs from "qs"
5
+ import React from "react"
6
+
7
+ import Link from "../link"
8
+ import PureComponent from "set-state-compare/src/pure-component"
9
+ import withQueryParams from "on-location-changed/src/with-query-params"
10
+
11
+ class ApiMakerBootstrapSortLink extends PureComponent {
12
+ static propTypes = {
13
+ attribute: PropTypes.string.isRequired,
14
+ className: PropTypes.string,
15
+ linkComponent: PropTypes.object,
16
+ onChanged: PropTypes.func,
17
+ query: PropTypes.object.isRequired,
18
+ queryParams: PropTypes.object.isRequired,
19
+ title: PropTypes.node
20
+ }
21
+
22
+ searchKey = digg(this, "props", "query", "queryArgs").searchKey || "q"
23
+
24
+ attribute () {
25
+ return inflection.underscore(this.props.attribute)
26
+ }
27
+
28
+ href () {
29
+ const {queryParams} = digs(this.props, "queryParams")
30
+ const {searchKey} = digs(this, "searchKey")
31
+
32
+ if (!queryParams[searchKey]) queryParams[searchKey] = {}
33
+
34
+ queryParams[searchKey].s = `${this.attribute()} ${this.sortMode()}` // eslint-disable-line id-length
35
+
36
+ const newParams = qs.stringify(queryParams)
37
+ const newPath = `${location.pathname}?${newParams}`
38
+
39
+ return newPath
40
+ }
41
+
42
+ isSortedByAttribute () {
43
+ const {queryParams} = digs(this.props, "queryParams")
44
+ const {searchKey} = digs(this, "searchKey")
45
+ const params = queryParams[searchKey] || {}
46
+
47
+ if (params.s == this.attribute()) return true
48
+ if (params.s == `${this.attribute()} asc`) return true
49
+
50
+ return false
51
+ }
52
+
53
+ render () {
54
+ const LinkComponent = this.linkComponent()
55
+ const {attribute, className, linkComponent, onChanged, query, queryParams, title, ...restProps} = this.props
56
+
57
+ return (
58
+ <>
59
+ <LinkComponent
60
+ className={this.className()}
61
+ data-attribute={attribute}
62
+ data-sort-mode={this.sortMode()}
63
+ to={this.href()}
64
+ {...restProps}
65
+ >
66
+ {this.title()}
67
+ </LinkComponent>
68
+ </>
69
+ )
70
+ }
71
+
72
+ className () {
73
+ const classNames = ["component-api-maker-bootstrap-sort-link"]
74
+
75
+ if (this.props.className) classNames.push(this.props.className)
76
+
77
+ return classNames.join(" ")
78
+ }
79
+
80
+ linkComponent () {
81
+ if (this.props.linkComponent) return this.props.linkComponent
82
+
83
+ return Link
84
+ }
85
+
86
+ sortMode () {
87
+ if (this.isSortedByAttribute()) return "desc"
88
+
89
+ return "asc"
90
+ }
91
+
92
+ title () {
93
+ const {attribute, query} = digs(this.props, "attribute", "query")
94
+ const {title} = this.props
95
+
96
+ if (title) return title
97
+
98
+ return query.modelClass().humanAttributeName(attribute)
99
+ }
100
+ }
101
+
102
+ export default withQueryParams(ApiMakerBootstrapSortLink)
@@ -1,16 +1,15 @@
1
- import Collection from "@kaspernj/api-maker/src/collection"
1
+ import Collection from "./collection"
2
2
  import {debounce} from "debounce"
3
3
  import {digg, digs} from "diggerize"
4
- import EventCreated from "@kaspernj/api-maker/src/event-created"
5
- import EventDestroyed from "@kaspernj/api-maker/src/event-destroyed"
6
- import EventUpdated from "@kaspernj/api-maker/src/event-updated"
7
- import instanceOfClassName from "@kaspernj/api-maker/src/instance-of-class-name"
8
- import Params from "@kaspernj/api-maker/src/params"
4
+ import EventCreated from "./event-created"
5
+ import EventDestroyed from "./event-destroyed"
6
+ import EventUpdated from "./event-updated"
7
+ import instanceOfClassName from "./instance-of-class-name"
8
+ import {LocationChanged} from "on-location-changed/src/location-changed-component"
9
+ import Params from "./params"
9
10
  import PropTypes from "prop-types"
10
11
  import React from "react"
11
12
 
12
- import {LocationChanged} from "on-location-changed/src/location-changed-component"
13
-
14
13
  export default class CollectionLoader extends React.PureComponent {
15
14
  static defaultProps = {
16
15
  destroyEnabled: true,
@@ -0,0 +1,37 @@
1
+ import {digg} from "diggerize"
2
+ import inflection from "inflection"
3
+
4
+ export default class ApiMakerInputsAutoSubmit {
5
+ constructor ({component}) {
6
+ this.component = component
7
+ }
8
+
9
+ autoSubmit () {
10
+ const {attribute, model} = this.component.props
11
+ const updateAttributeName = inflection.underscore(attribute)
12
+ const updateParams = {}
13
+
14
+ updateParams[updateAttributeName] = this.value()
15
+
16
+ model.update(updateParams)
17
+ }
18
+
19
+ value () {
20
+ const inputRef = this.component.props.inputRef || this.component.props.inputProps.ref || this.component.inputRef
21
+ const input = digg(inputRef, "current")
22
+
23
+ if (input.type == "checkbox") {
24
+ if (input.checked) {
25
+ if (input.value !== undefined) {
26
+ return input.value
27
+ } else {
28
+ return 1
29
+ }
30
+ } else {
31
+ return 0
32
+ }
33
+ }
34
+
35
+ return digg(input, "value")
36
+ }
37
+ }
@@ -0,0 +1,97 @@
1
+ import AutoSubmit from "./auto-submit.mjs"
2
+ import {digg, digs} from "diggerize"
3
+ import EventUpdated from "../event-updated"
4
+ import inputWrapper from "./input-wrapper"
5
+ import PropTypes from "prop-types"
6
+ import React from "react"
7
+
8
+ class ApiMakerInputsCheckbox extends React.PureComponent {
9
+ static defaultProps = {
10
+ autoRefresh: false,
11
+ autoSubmit: false,
12
+ defaultValue: 1,
13
+ zeroInput: true
14
+ }
15
+
16
+ static propTypes = {
17
+ attribute: PropTypes.string,
18
+ autoRefresh: PropTypes.bool.isRequired,
19
+ autoSubmit: PropTypes.bool.isRequired,
20
+ defaultChecked: PropTypes.bool,
21
+ defaultValue: PropTypes.node,
22
+ id: PropTypes.string,
23
+ inputRef: PropTypes.object,
24
+ model: PropTypes.object,
25
+ name: PropTypes.string,
26
+ onErrors: PropTypes.func,
27
+ onMatchValidationError: PropTypes.func,
28
+ zeroInput: PropTypes.bool
29
+ }
30
+
31
+ render () {
32
+ const {
33
+ attribute,
34
+ autoRefresh,
35
+ autoSubmit,
36
+ defaultChecked,
37
+ defaultValue,
38
+ id,
39
+ inputProps,
40
+ inputRef,
41
+ model,
42
+ name,
43
+ onChange,
44
+ zeroInput,
45
+ wrapperOpts,
46
+ ...restProps
47
+ } = this.props
48
+
49
+ return (
50
+ <>
51
+ {autoRefresh && model &&
52
+ <EventUpdated model={model} onUpdated={digg(this, "onModelUpdated")} />
53
+ }
54
+ {zeroInput && inputProps.name &&
55
+ <input defaultValue="0" name={inputProps.name} type="hidden" />
56
+ }
57
+ <input
58
+ {...inputProps}
59
+ data-auto-refresh={autoRefresh}
60
+ data-auto-submit={autoSubmit}
61
+ defaultValue={defaultValue}
62
+ onChange={digg(this, "onChanged")}
63
+ type="checkbox"
64
+ {...restProps}
65
+ />
66
+ </>
67
+ )
68
+ }
69
+
70
+ onChanged = (...args) => {
71
+ const {attribute, autoSubmit, model, onChange} = this.props
72
+
73
+ if (attribute && autoSubmit && model) new AutoSubmit({component: this}).autoSubmit()
74
+ if (onChange) onChange(...args)
75
+ }
76
+
77
+ onModelUpdated = (args) => {
78
+ const inputRef = digg(this, "props", "inputProps", "ref")
79
+
80
+ if (!inputRef.current) {
81
+ // This can happen if the component is being unmounted
82
+ return
83
+ }
84
+
85
+ const {attribute} = digs(this.props, "attribute")
86
+ const newModel = digg(args, "model")
87
+ const currentChecked = digg(inputRef, "current", "checked")
88
+ const newValue = newModel.readAttribute(attribute)
89
+
90
+ if (currentChecked != newValue) {
91
+ inputRef.current.checked = newValue
92
+ }
93
+ }
94
+ }
95
+
96
+ export {ApiMakerInputsCheckbox as Checkbox}
97
+ export default inputWrapper(ApiMakerInputsCheckbox, {type: "checkbox"})
@@ -0,0 +1,113 @@
1
+ import classNames from "classnames"
2
+ import {digs} from "diggerize"
3
+ import inputWrapper from "./input-wrapper"
4
+ import inflection from "inflection"
5
+ import InvalidFeedback from "../bootstrap/invalid-feedback"
6
+ import PropTypes from "prop-types"
7
+ import propTypesExact from "prop-types-exact"
8
+ import React from "react"
9
+
10
+ class ApiMakerInputsCheckboxes extends React.PureComponent {
11
+ static propTypes = propTypesExact({
12
+ attribute: PropTypes.string,
13
+ defaultValue: PropTypes.array,
14
+ id: PropTypes.string,
15
+ inputProps: PropTypes.object.isRequired,
16
+ label: PropTypes.node,
17
+ model: PropTypes.object,
18
+ name: PropTypes.string,
19
+ onChange: PropTypes.func,
20
+ options: PropTypes.array.isRequired,
21
+ wrapperOpts: PropTypes.object
22
+ })
23
+
24
+ render () {
25
+ const {className, inputProps, label, wrapperOpts, ...restProps} = this.props
26
+
27
+ return (
28
+ <div className={classNames("component-bootstrap-check-boxes", className)} {...restProps}>
29
+ <input name={this.inputName()} ref={this.props.inputProps.ref} type="hidden" value="" />
30
+ {this.props.options.map((option, index) => this.optionElement(option, index))}
31
+ </div>
32
+ )
33
+ }
34
+
35
+ inputDefaultValue () {
36
+ const {attribute, defaultValue, model} = this.props
37
+
38
+ if (defaultValue) {
39
+ return defaultValue
40
+ } else if (attribute && model) {
41
+ if (!model[attribute])
42
+ throw `No such attribute: ${attribute}`
43
+
44
+ return this.props.model[attribute]()
45
+ }
46
+ }
47
+
48
+ inputCheckboxClassName () {
49
+ const classNames = []
50
+
51
+ if (this.props.wrapperOpts.errors.length > 0) classNames.push("is-invalid")
52
+
53
+ return classNames.join(" ")
54
+ }
55
+
56
+ inputName () {
57
+ if (this.props.name) {
58
+ return `${this.props.name}[]`
59
+ } else if (this.props.model) {
60
+ return `${this.props.model.modelClassData().paramKey}[${inflection.underscore(this.props.attribute)}]`
61
+ }
62
+ }
63
+
64
+ isDefaultSelected (option) {
65
+ let defaultValue = this.inputDefaultValue()
66
+
67
+ if (!defaultValue) return false
68
+
69
+ if (defaultValue.constructor === Array) {
70
+ return defaultValue.includes(option)
71
+ } else {
72
+ return defaultValue == option
73
+ }
74
+ }
75
+
76
+ generatedId () {
77
+ if (!this.generatedIdValue)
78
+ this.generatedIdValue = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
79
+
80
+ return this.generatedIdValue
81
+ }
82
+
83
+ optionElement (option, index) {
84
+ const {onChange, options, wrapperOpts} = this.props
85
+ const {errors} = digs(wrapperOpts, "errors")
86
+ const id = `${this.generatedId()}-${index}`
87
+
88
+ return (
89
+ <div className="checkboxes-option" key={`option-${option[1]}`}>
90
+ <input
91
+ className={this.inputCheckboxClassName()}
92
+ data-option-value={option[1]}
93
+ defaultChecked={this.isDefaultSelected(option[1])}
94
+ id={id}
95
+ name={this.inputName()}
96
+ onChange={onChange}
97
+ type="checkbox"
98
+ value={option[1]}
99
+ />
100
+
101
+ <label className="ml-1" htmlFor={id}>
102
+ {option[0]}
103
+ </label>
104
+
105
+ {(index + 1) == options.length && errors.length > 0 &&
106
+ <InvalidFeedback errors={errors} />
107
+ }
108
+ </div>
109
+ )
110
+ }
111
+ }
112
+
113
+ export default inputWrapper(ApiMakerInputsCheckboxes)
@@ -0,0 +1,15 @@
1
+ import inflection from "inflection"
2
+
3
+ export default function apiMakerIdForComponent (component) {
4
+ if ("id" in component.props) {
5
+ return component.props.id
6
+ } else if (component.props.attribute && component.props.model) {
7
+ return `${component.props.model.modelClassData().paramKey}_${inflection.underscore(component.props.attribute)}`
8
+ } else if (component.generatedInputId) {
9
+ return component.generatedInputId
10
+ } else {
11
+ const generatedInputId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
12
+ component.generatedInputId = generatedInputId
13
+ return generatedInputId
14
+ }
15
+ }