@kaspernj/api-maker 1.0.230 → 1.0.231

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.
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  ]
17
17
  },
18
18
  "name": "@kaspernj/api-maker",
19
- "version": "1.0.230",
19
+ "version": "1.0.231",
20
20
  "type": "module",
21
21
  "description": "",
22
22
  "main": "index.js",
@@ -0,0 +1,12 @@
1
+ import {digg} from "diggerize"
2
+ import inflection from "inflection"
3
+
4
+ export default class ApiMakerBaseModelScope {
5
+ constructor(scopeData) {
6
+ this.scopeData = scopeData
7
+ }
8
+
9
+ name() {
10
+ return inflection.camelize(digg(this, "scopeData", "name"), true)
11
+ }
12
+ }
@@ -11,6 +11,7 @@ import ModelName from "./model-name.mjs"
11
11
  import NotLoadedError from "./not-loaded-error.mjs"
12
12
  import objectToFormData from "object-to-formdata"
13
13
  import Reflection from "./base-model/reflection.mjs"
14
+ import Scope from "./base-model/scope.mjs"
14
15
  import Services from "./services.mjs"
15
16
  import ValidationError from "./validation-error.mjs"
16
17
  import {ValidationErrors} from "./validation-errors.mjs"
@@ -84,6 +85,41 @@ export default class BaseModel {
84
85
  return new Collection({modelClass: this}, {ransack: query})
85
86
  }
86
87
 
88
+ static ransackableAssociations() {
89
+ const relationships = digg(this.modelClassData(), "ransackable_associations")
90
+ const reflections = []
91
+
92
+ for (const relationshipData of relationships) {
93
+ reflections.push(new Reflection(relationshipData))
94
+ }
95
+
96
+ return reflections
97
+ }
98
+
99
+ static ransackableAttributes() {
100
+ const attributes = digg(this.modelClassData(), "ransackable_attributes")
101
+ const result = []
102
+
103
+ for (const attributeData of attributes) {
104
+ result.push(new Attribute(attributeData))
105
+ }
106
+
107
+ return result
108
+ }
109
+
110
+ static ransackableScopes() {
111
+ const ransackableScopes = digg(this.modelClassData(), "ransackable_scopes")
112
+ const result = []
113
+
114
+ for (const scopeData of ransackableScopes) {
115
+ const scope = new Scope(scopeData)
116
+
117
+ result.push(scope)
118
+ }
119
+
120
+ return result
121
+ }
122
+
87
123
  static reflections() {
88
124
  const relationships = digg(this.modelClassData(), "relationships")
89
125
  const reflections = []
@@ -1,4 +1,5 @@
1
- import {digs} from "diggerize"
1
+ import classNames from "classnames"
2
+ import {digg, digs} from "diggerize"
2
3
  import PropTypes from "prop-types"
3
4
  import React from "react"
4
5
 
@@ -48,11 +49,14 @@ export default class ApiMakerBootstrapCard extends React.PureComponent {
48
49
  ...restProps
49
50
  } = this.props
50
51
  const {expanded} = digs(this.state, "expanded")
52
+ const cardHeaderStyle = {display: "flex"}
53
+
54
+ if (!expanded) cardHeaderStyle["borderBottom"] = "0"
51
55
 
52
56
  return (
53
- <div className={this.classNames()} data-has-footer={Boolean(footer)} ref="card" {...restProps}>
57
+ <div className={classNames("component-bootstrap-card", "card", "card-default", className)} data-has-footer={Boolean(footer)} ref="card" {...restProps}>
54
58
  {(controls || expandable || header) &&
55
- <div className={`card-header d-flex ${!expanded && "border-bottom-0"}`}>
59
+ <div className="card-header" style={cardHeaderStyle}>
56
60
  <div style={{alignSelf: "center", marginRight: "auto"}}>
57
61
  {header}
58
62
  </div>
@@ -60,13 +64,13 @@ export default class ApiMakerBootstrapCard extends React.PureComponent {
60
64
  <div style={{alignSelf: "center"}}>
61
65
  {controls}
62
66
  {expandable && expanded &&
63
- <a className="collapse-card-button text-muted" href="#" onClick={this.onCollapseClicked}>
64
- <i className="la la-angle-up" />
67
+ <a className="collapse-card-button text-muted" href="#" onClick={digg(this, "onCollapseClicked")}>
68
+ <i className="fa fa-angle-up" />
65
69
  </a>
66
70
  }
67
71
  {expandable && !expanded &&
68
- <a className="expand-card-button text-muted" href="#" onClick={this.onExpandClicked}>
69
- <i className="la la-angle-down" />
72
+ <a className="expand-card-button text-muted" href="#" onClick={digg(this, "onExpandClicked")}>
73
+ <i className="fa fa-angle-down" />
70
74
  </a>
71
75
  }
72
76
  </div>
@@ -92,15 +96,6 @@ export default class ApiMakerBootstrapCard extends React.PureComponent {
92
96
  )
93
97
  }
94
98
 
95
- classNames () {
96
- const classNames = ["component-bootstrap-card", "card", "card-default"]
97
-
98
- if (this.props.className)
99
- classNames.push(this.props.className)
100
-
101
- return classNames.join(" ")
102
- }
103
-
104
99
  bodyClassNames () {
105
100
  const {expandableHide, responsiveTable, table} = digs(this.props, "expandableHide", "responsiveTable", "table")
106
101
  const {expanded} = digs(this.state, "expanded")
@@ -4,7 +4,6 @@ import {digg, digs} from "diggerize"
4
4
  import EventCreated from "./event-created"
5
5
  import EventDestroyed from "./event-destroyed"
6
6
  import EventUpdated from "./event-updated"
7
- import instanceOfClassName from "./instance-of-class-name"
8
7
  import {LocationChanged} from "on-location-changed/src/location-changed-component"
9
8
  import Params from "./params"
10
9
  import PropTypes from "prop-types"
@@ -54,9 +53,11 @@ export default class CollectionLoader extends React.PureComponent {
54
53
  query: undefined,
55
54
  queryName,
56
55
  queryQName: `${queryName}_q`,
56
+ querySName: `${queryName}_s`,
57
57
  queryPageName: `${queryName}_page`,
58
58
  qParams: undefined,
59
59
  result: undefined,
60
+ searchParams: undefined,
60
61
  showNoRecordsAvailableContent: false,
61
62
  showNoRecordsFoundContent: false
62
63
  })
@@ -83,17 +84,26 @@ export default class CollectionLoader extends React.PureComponent {
83
84
  }
84
85
 
85
86
  loadQParams () {
86
- const {queryQName} = digs(this.shape, "queryQName")
87
+ const {queryQName, querySName} = digs(this.shape, "queryQName", "querySName")
87
88
  const params = Params.parse()
88
89
  const qParams = Object.assign({}, this.props.defaultParams, params[queryQName])
90
+ const searchParams = []
89
91
 
90
- this.shape.set({qParams})
92
+ if (params[querySName]) {
93
+ for (const rawSearchParam of params[querySName]) {
94
+ const parsedSearchParam = JSON.parse(rawSearchParam)
95
+
96
+ searchParams.push(parsedSearchParam)
97
+ }
98
+ }
99
+
100
+ this.shape.set({qParams, searchParams})
91
101
  }
92
102
 
93
103
  loadModels = async () => {
94
104
  const params = Params.parse()
95
105
  const {abilities, collection, groupBy, modelClass, onModelsLoaded, preloads, select, selectColumns} = this.props
96
- const {qParams, queryPageName, queryQName} = digs(this.shape, "qParams", "queryPageName", "queryQName")
106
+ const {qParams, queryPageName, queryQName, searchParams} = digs(this.shape, "qParams", "queryPageName", "queryQName", "searchParams")
97
107
 
98
108
  let query = collection?.clone() || modelClass.ransack()
99
109
 
@@ -101,6 +111,7 @@ export default class CollectionLoader extends React.PureComponent {
101
111
 
102
112
  query = query
103
113
  .ransack(qParams)
114
+ .search(searchParams)
104
115
  .searchKey(queryQName)
105
116
  .page(params[queryPageName])
106
117
  .pageKey(queryPageName)
@@ -151,11 +162,7 @@ export default class CollectionLoader extends React.PureComponent {
151
162
  }
152
163
 
153
164
  onLocationChanged = () => {
154
- const {queryQName} = digs(this.shape, "queryQName")
155
- const params = Params.parse()
156
- const qParams = Object.assign({}, this.props.defaultParams, params[queryQName])
157
-
158
- this.shape.set({qParams})
165
+ this.loadQParams()
159
166
  this.loadModels()
160
167
  }
161
168
 
@@ -124,6 +124,7 @@ export default class ApiMakerCollection {
124
124
  if (this.queryArgs.preload) params.preload = this.queryArgs.preload
125
125
  if (this.queryArgs.page) params.page = this.queryArgs.page
126
126
  if (this.queryArgs.per) params.per = this.queryArgs.per
127
+ if (this.queryArgs.search) params.search = this.queryArgs.search
127
128
  if (this.queryArgs.select) params.select = this.queryArgs.select
128
129
  if (this.queryArgs.selectColumns) params.select_columns = this.queryArgs.selectColumns
129
130
 
@@ -139,10 +140,7 @@ export default class ApiMakerCollection {
139
140
  }
140
141
 
141
142
  ransack (params) {
142
- if (params) {
143
- this._merge({ransack: params})
144
- }
145
-
143
+ if (params) this._merge({ransack: params})
146
144
  return this
147
145
  }
148
146
 
@@ -157,11 +155,16 @@ export default class ApiMakerCollection {
157
155
  return result
158
156
  }
159
157
 
160
- searchKey (searchKey) {
158
+ search(params) {
159
+ if (params) this._merge({search: params})
160
+ return this
161
+ }
162
+
163
+ searchKey(searchKey) {
161
164
  return this._merge({searchKey})
162
165
  }
163
166
 
164
- select (originalSelect) {
167
+ select(originalSelect) {
165
168
  const newSelect = {}
166
169
 
167
170
  for (const originalModelName in originalSelect) {
@@ -180,7 +183,7 @@ export default class ApiMakerCollection {
180
183
  return this._merge({select: newSelect})
181
184
  }
182
185
 
183
- selectColumns (originalSelect) {
186
+ selectColumns(originalSelect) {
184
187
  const newSelect = {}
185
188
 
186
189
  for (const originalModelName in originalSelect) {
@@ -0,0 +1,277 @@
1
+ import Attribute from "../../base-model/attribute"
2
+ import {digs} from "diggerize"
3
+ import Input from "../../inputs/input"
4
+ import PropTypes from "prop-types"
5
+ import PropTypesExact from "prop-types-exact"
6
+ import React from "react"
7
+ import Reflection from "../../base-model/reflection"
8
+ import Select from "../../inputs/select"
9
+ import Services from "../../services.mjs"
10
+ import Shape from "set-state-compare/src/shape"
11
+
12
+ class AttributeElement extends React.PureComponent {
13
+ static propTypes = {
14
+ active: PropTypes.bool.isRequired,
15
+ attribute: PropTypes.instanceOf(Attribute).isRequired,
16
+ currentModelClass: PropTypes.func.isRequired,
17
+ fikter: PropTypes.object,
18
+ onClick: PropTypes.func.isRequired
19
+ }
20
+
21
+ render() {
22
+ const {active, attribute, currentModelClass} = digs(this.props, "active", "attribute", "currentModelClass")
23
+ const style = {}
24
+
25
+ if (active) style.fontWeight = "bold"
26
+
27
+ return (
28
+ <div onClick={digg(this, "onAttributeClicked")} style={style}>
29
+ {currentModelClass.humanAttributeName(inflection.camelize(attribute.name(), true))}
30
+ </div>
31
+ )
32
+ }
33
+
34
+ onAttributeClicked = (e) => {
35
+ e.preventDefault()
36
+
37
+ this.props.onClick({attribute: digg(this, "props", "attribute")})
38
+ }
39
+ }
40
+
41
+ class ReflectionElement extends React.PureComponent {
42
+ static propTypes = {
43
+ currentModelClass: PropTypes.func.isRequired,
44
+ onClick: PropTypes.func.isRequired,
45
+ reflection: PropTypes.instanceOf(Reflection).isRequired
46
+ }
47
+
48
+ render() {
49
+ const {currentModelClass, reflection} = digs(this.props, "currentModelClass", "reflection")
50
+
51
+ return (
52
+ <div key={reflection.name()} onClick={digg(this, "onReflectionClicked")}>
53
+ {currentModelClass.humanAttributeName(reflection.name())}
54
+ </div>
55
+ )
56
+ }
57
+
58
+ onReflectionClicked = (e) => {
59
+ e.preventDefault()
60
+
61
+ this.props.onClick({reflection: digg(this, "props", "reflection")})
62
+ }
63
+ }
64
+
65
+ export default class ApiMakerTableFiltersRelationshipSelect extends React.PureComponent {
66
+ static propTypes = PropTypesExact({
67
+ filter: PropTypes.object,
68
+ modelClass: PropTypes.func.isRequired,
69
+ querySearchName: PropTypes.string.isRequired
70
+ })
71
+
72
+ shape = new Shape(this, {
73
+ attribute: this.currentModelClassFromPath(this.props.filter.p || []).ransackableAttributes().find((attribute) => attribute.name() == this.props.filter.a),
74
+ path: this.props.filter.p || [],
75
+ predicate: undefined,
76
+ predicates: undefined,
77
+ value: this.props.filter.v
78
+ })
79
+ valueInputRef = React.createRef()
80
+
81
+ componentDidMount() {
82
+ this.loadRansackPredicates()
83
+ }
84
+
85
+ async loadRansackPredicates() {
86
+ const response = await Services.current().sendRequest("Ransack::Predicates")
87
+ const predicates = digg(response, "predicates")
88
+ let currentPredicate
89
+
90
+ if (this.props.filter.pre) {
91
+ currentPredicate = predicates.find((predicate) => predicate.name == this.props.filter.pre)
92
+ }
93
+
94
+ this.shape.set({
95
+ predicate: currentPredicate,
96
+ predicates
97
+ })
98
+ }
99
+
100
+ render() {
101
+ const {valueInputRef} = digs(this, "valueInputRef")
102
+ const currentModelClass = this.currentModelClass()
103
+ const {attribute, predicate, predicates, value} = digs(this.shape, "attribute", "predicate", "predicates", "value")
104
+
105
+ return (
106
+ <div className="api-maker--table--filters--relationship-select">
107
+ <form onSubmit={digg(this, "onSubmit")}>
108
+ <div>
109
+ {this.currentPathParts().map(({translation}, pathPartIndex) =>
110
+ <span key={`${pathPartIndex}-${translation}`}>
111
+ {pathPartIndex > 0 &&
112
+ <span style={{marginRight: "5px", marginLeft: "5px"}}>
113
+ -
114
+ </span>
115
+ }
116
+ {translation}
117
+ </span>
118
+ )}
119
+ </div>
120
+ <div style={{display: "flex"}}>
121
+ <div>
122
+ {this.sortedByName(this.reflectionsWithModelClass(currentModelClass.ransackableAssociations()), currentModelClass).map((reflection) =>
123
+ <ReflectionElement
124
+ currentModelClass={currentModelClass}
125
+ key={reflection.name()}
126
+ onClick={digg(this, "onReflectionClicked")}
127
+ reflection={reflection}
128
+ />
129
+ )}
130
+ </div>
131
+ <div>
132
+ {this.sortedByName(currentModelClass.ransackableAttributes(), currentModelClass).map((attribute) =>
133
+ <AttributeElement
134
+ active={attribute.name() == this.shape.attribute?.name()}
135
+ attribute={attribute}
136
+ currentModelClass={currentModelClass}
137
+ key={attribute.name()}
138
+ onClick={digg(this, "onAttributeClicked")}
139
+ />
140
+ )}
141
+ {currentModelClass.ransackableScopes().map((scope) =>
142
+ <div>
143
+ {scope.name()}
144
+ </div>
145
+ )}
146
+ </div>
147
+ <div>
148
+ {predicates &&
149
+ <Select
150
+ defaultValue={predicate?.name}
151
+ includeBlank
152
+ onChange={digg(this, "onPredicateChanged")}
153
+ options={predicates.map((predicate) => digg(predicate, "name"))}
154
+ />
155
+ }
156
+ </div>
157
+ <div>
158
+ {attribute && predicate &&
159
+ <Input defaultValue={value} inputRef={valueInputRef} />
160
+ }
161
+ </div>
162
+ </div>
163
+ <div>
164
+ <Button disabled={!attribute || !predicate}>
165
+ {I18n.t("js.api_maker.table.filters.relationship_select.apply", {defaultValue: "Apply"})}
166
+ </Button>
167
+ </div>
168
+ </form>
169
+ </div>
170
+ )
171
+ }
172
+
173
+ currentModelClass() {
174
+ const {path} = digs(this.shape, "path")
175
+
176
+ return this.currentModelClassFromPath(path)
177
+ }
178
+
179
+ currentModelClassFromPath(path) {
180
+ const {modelClass} = digs(this.props, "modelClass")
181
+ let currentModelClass = modelClass
182
+
183
+ for (const pathPart of path) {
184
+ currentModelClass = currentModelClass.ransackableAssociations().find((reflection) => reflection.name() == pathPart).modelClass()
185
+ }
186
+
187
+ return currentModelClass
188
+ }
189
+
190
+ currentPathParts() {
191
+ const {modelClass} = digs(this.props, "modelClass")
192
+ const {path} = digs(this.shape, "path")
193
+ const result = []
194
+ let currentModelClass = modelClass
195
+
196
+ result.push({
197
+ modelClass,
198
+ translation: modelClass.modelName().human({count: 2})
199
+ })
200
+
201
+ for (const pathPart of path) {
202
+ const pathPartTranslation = currentModelClass.humanAttributeName(pathPart)
203
+
204
+ currentModelClass = currentModelClass.ransackableAssociations().find((reflection) => reflection.name() == pathPart).modelClass()
205
+
206
+ result.push({
207
+ modelClass: currentModelClass,
208
+ translation: pathPartTranslation
209
+ })
210
+ }
211
+
212
+ return result
213
+ }
214
+
215
+ onAttributeClicked = ({attribute}) => {
216
+ this.shape.set({attribute})
217
+ }
218
+
219
+ onPredicateChanged = (e) => {
220
+ const chosenPredicateName = digg(e, "target", "value")
221
+ const predicate = this.shape.predicates.find((predicate) => predicate.name == chosenPredicateName)
222
+
223
+ this.shape.set({predicate})
224
+ }
225
+
226
+ onReflectionClicked = ({reflection}) => {
227
+ const newPath = this.shape.path.concat([reflection.name()])
228
+
229
+ this.shape.set({
230
+ attribute: undefined,
231
+ path: newPath
232
+ })
233
+
234
+ this.props.onPathChanged
235
+ }
236
+
237
+ onSubmit = (e) => {
238
+ e.preventDefault()
239
+
240
+ const {filter, querySearchName} = digs(this.props, "filter", "querySearchName")
241
+ const {attribute, path, predicate} = digs(this.shape, "attribute", "path", "predicate")
242
+ const {filterIndex} = digs(filter, "filterIndex")
243
+ const searchParams = Params.parse()[querySearchName] || {}
244
+ const value = digg(this, "valueInputRef", "current", "value")
245
+
246
+ searchParams[filterIndex] = JSON.stringify({
247
+ a: attribute.name(),
248
+ p: path,
249
+ pre: digg(predicate, "name"),
250
+ v: value
251
+ })
252
+
253
+ const newParams = {}
254
+
255
+ newParams[querySearchName] = searchParams
256
+
257
+ Params.changeParams(newParams)
258
+ }
259
+
260
+ reflectionsWithModelClass(reflections) {
261
+ return reflections.filter((reflection) => {
262
+ try {
263
+ reflection.modelClass()
264
+
265
+ return true
266
+ } catch (error) {
267
+ return false
268
+ }
269
+ })
270
+ }
271
+
272
+ sortedByName(reflections, currentModelClass) {
273
+ return reflections.sort((a, b) =>
274
+ currentModelClass.humanAttributeName(a.name()).toLowerCase().localeCompare(currentModelClass.humanAttributeName(b.name()).toLowerCase())
275
+ )
276
+ }
277
+ }
@@ -0,0 +1,95 @@
1
+ import PropTypes from "prop-types"
2
+ import React from "react"
3
+ import FilterForm from "./filter-form"
4
+ import Shape from "set-state-compare/src/shape"
5
+ import withQueryParams from "on-location-changed/src/with-query-params"
6
+
7
+ class ApiMakerTableFilter extends React.PureComponent {
8
+ static propTypes = {
9
+ a: PropTypes.string.isRequired,
10
+ filterIndex: PropTypes.number.isRequired,
11
+ onClick: PropTypes.func.isRequired,
12
+ p: PropTypes.array.isRequired,
13
+ pre: PropTypes.string.isRequired,
14
+ v: PropTypes.string.isRequired
15
+ }
16
+
17
+ render() {
18
+ const {a, p, pre, v} = digs(this.props, "a", "p", "pre", "v")
19
+
20
+ return (
21
+ <div onClick={digg(this, "onFilterClicked")} style={{display: "inline-block", backgroundColor: "grey", padding: "10px 6px"}}>
22
+ {p.join(".")}.{a} {pre} {v}
23
+ </div>
24
+ )
25
+ }
26
+
27
+ onFilterClicked = (e) => {
28
+ e.preventDefault()
29
+
30
+ const {a, filterIndex, p, pre, v} = digs(this.props, "a", "filterIndex", "p", "pre", "v")
31
+
32
+ this.props.onClick({a, filterIndex, p, pre, v})
33
+ }
34
+ }
35
+
36
+ class ApiMakerTableFilters extends React.PureComponent {
37
+ static propTypes = {
38
+ modelClass: PropTypes.func.isRequired,
39
+ queryName: PropTypes.string.isRequired,
40
+ queryParams: PropTypes.object.isRequired
41
+ }
42
+
43
+ shape = new Shape(this, {
44
+ filter: undefined
45
+ })
46
+
47
+ render() {
48
+ const {modelClass} = this.props
49
+ const {filter} = digs(this.shape, "filter")
50
+ const currentFilters = this.currentFilters()
51
+
52
+ return (
53
+ <div className="api-maker--table--filters--edit">
54
+ <button onClick={digg(this, "onAddFilterClicked")}>
55
+ {I18n.t("js.api_maker.table.filters.add_new_filter", {defaultValue: "Add new filter"})}
56
+ </button>
57
+ {filter &&
58
+ <FilterForm
59
+ filter={filter}
60
+ key={`filter-${filter.filterIndex}`}
61
+ modelClass={modelClass}
62
+ querySearchName={this.querySearchName()}
63
+ />
64
+ }
65
+ {currentFilters?.map((filterData, filterIndex) =>
66
+ <ApiMakerTableFilter key={filterIndex} filterIndex={filterIndex} onClick={digg(this, "onFilterClicked")} {...JSON.parse(filterData)} />
67
+ )}
68
+ </div>
69
+ )
70
+ }
71
+
72
+ currentFilters() {
73
+ const {queryParams} = this.props
74
+ const currentFilters = queryParams[this.querySearchName()] || []
75
+
76
+ return currentFilters
77
+ }
78
+
79
+ onAddFilterClicked = (e) => {
80
+ e.preventDefault()
81
+
82
+ const newFilterIndex = this.currentFilters().length
83
+
84
+ this.shape.set({
85
+ filter: {
86
+ filterIndex: newFilterIndex
87
+ }
88
+ })
89
+ }
90
+
91
+ onFilterClicked = (args) => this.shape.set({filter: args})
92
+ querySearchName = () => `${this.props.queryName}_s`
93
+ }
94
+
95
+ export default withQueryParams(ApiMakerTableFilters)
@@ -6,6 +6,7 @@ import CollectionLoader from "../collection-loader"
6
6
  import columnVisible from "./column-visible.mjs"
7
7
  import {debounce} from "debounce"
8
8
  import {digg, digs} from "diggerize"
9
+ import Filters from "./filters"
9
10
  import inflection from "inflection"
10
11
  import modelClassRequire from "../model-class-require.mjs"
11
12
  import ModelRow from "./model-row"
@@ -93,6 +94,7 @@ class ApiMakerTable extends React.PureComponent {
93
94
  queryPageName: `${queryName}_page`,
94
95
  qParams: undefined,
95
96
  result: undefined,
97
+ showFilters: false,
96
98
  showNoRecordsAvailableContent: false,
97
99
  showNoRecordsFoundContent: false
98
100
  })
@@ -136,8 +138,10 @@ class ApiMakerTable extends React.PureComponent {
136
138
  preload,
137
139
  qParams,
138
140
  query,
141
+ queryName,
139
142
  result,
140
143
  models,
144
+ showFilters,
141
145
  showNoRecordsAvailableContent,
142
146
  showNoRecordsFoundContent
143
147
  } = digs(
@@ -146,8 +150,10 @@ class ApiMakerTable extends React.PureComponent {
146
150
  "preload",
147
151
  "qParams",
148
152
  "query",
153
+ "queryName",
149
154
  "result",
150
155
  "models",
156
+ "showFilters",
151
157
  "showNoRecordsAvailableContent",
152
158
  "showNoRecordsFoundContent"
153
159
  )
@@ -178,6 +184,9 @@ class ApiMakerTable extends React.PureComponent {
178
184
  {noRecordsFoundContent({models, qParams, overallCount})}
179
185
  </div>
180
186
  }
187
+ {showFilters &&
188
+ <Filters modelClass={modelClass} queryName={queryName} />
189
+ }
181
190
  {qParams && query && result && models && !showNoRecordsAvailableContent && !showNoRecordsFoundContent &&
182
191
  this.cardOrTable()
183
192
  }
@@ -270,6 +279,8 @@ class ApiMakerTable extends React.PureComponent {
270
279
  controlsContent = controls({models, qParams, query, result})
271
280
  }
272
281
 
282
+ controlsContent += this.tableControls()
283
+
273
284
  if (typeof header == "function") {
274
285
  headerContent = header({models, qParams, query, result})
275
286
  } else if (header) {
@@ -299,7 +310,7 @@ class ApiMakerTable extends React.PureComponent {
299
310
  this.filterForm()
300
311
  }
301
312
  {card &&
302
- <Card className={classNames("mb-4", className)} controls={controlsContent} header={headerContent} footer={this.tableFooter()} table={!this.isSmallScreen()} {...restProps}>
313
+ <Card className={classNames("mb-4", className)} controls={this.tableControls()} header={headerContent} footer={this.tableFooter()} table={!this.isSmallScreen()} {...restProps}>
303
314
  {this.tableContent()}
304
315
  </Card>
305
316
  }
@@ -342,6 +353,24 @@ class ApiMakerTable extends React.PureComponent {
342
353
  )
343
354
  }
344
355
 
356
+ tableControls() {
357
+ const {controls} = this.props
358
+
359
+ return (
360
+ <>
361
+ {controls && controls({models, qParams, query, result})}
362
+ <a href="#" onClick={digg(this, "onNewFilterClick")}>
363
+ <i className="fa fa-fw fa-magnifying-glass" />
364
+ </a>
365
+ </>
366
+ )
367
+ }
368
+
369
+ onNewFilterClick = (e) => {
370
+ e.preventDefault()
371
+ this.shape.set({showFilters: !this.shape.showFilters})
372
+ }
373
+
345
374
  tableContent () {
346
375
  const {breakPoint} = digs(this.props, "breakPoint")
347
376
  const {models, preparedColumns} = digs(this.shape, "models", "preparedColumns")