@fishawack/lab-velocity 2.0.0-beta.13 → 2.0.0-beta.14

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/README.md CHANGED
@@ -252,8 +252,8 @@ export function meta(name, properties = {}) {
252
252
  pageLink: name,
253
253
  api: `/api/${name}`,
254
254
  permissions: {
255
- create: true,
256
- edit: true,
255
+ create: () => true,
256
+ edit: () => true,
257
257
  },
258
258
  searchable: {
259
259
  value: "name",
@@ -264,30 +264,73 @@ export function meta(name, properties = {}) {
264
264
  fields: () => ({}),
265
265
  },
266
266
  table: {
267
- structure: [],
267
+ structure: [
268
+ {
269
+ key: "id",
270
+ },
271
+ ],
268
272
  },
269
- index: {
270
- actions: [],
273
+ description: {
271
274
  structure: [
272
275
  {
273
- render: ({ resource }) =>
274
- h(VTableSorter, {
275
- key: "PIndex",
276
- "json-data": {
277
- ...resource,
278
- tableStructure: resource.table.structure,
279
- },
280
- defaults: resource.defaults,
281
- "fixed-height": false,
282
- "display-edit-action":
283
- resource.permissions.create,
284
- }),
276
+ key: "id",
277
+ },
278
+ ],
279
+ },
280
+ index: {
281
+ actions: [],
282
+ layout: [
283
+ (props) => {
284
+ const { resource } = props;
285
+
286
+ return h(VTableSorter, {
287
+ key: "PIndex",
288
+ "json-data": {
289
+ ...resource,
290
+ tableStructure: resource.table.structure,
291
+ },
292
+ defaults: resource.defaults,
293
+ "fixed-height": false,
294
+ "display-edit-action":
295
+ resource.permissions.create(props),
296
+ });
285
297
  },
286
298
  ],
287
299
  },
288
300
  show: {
289
301
  actions: [],
290
- structure: [],
302
+ layout: [
303
+ (props) => {
304
+ const { resource, model } = props;
305
+
306
+ return h(
307
+ ElDescriptions,
308
+ {
309
+ border: true,
310
+ column: 1,
311
+ },
312
+ resource.description.structure.map((item, index) =>
313
+ h(
314
+ ElDescriptionsItem,
315
+ {
316
+ key: index,
317
+ labelWidth: "20%",
318
+ },
319
+ {
320
+ label: () =>
321
+ item.label ||
322
+ item.key[0].toUpperCase() +
323
+ item.key.slice(1),
324
+ default: () =>
325
+ item.render
326
+ ? h(item.render(props))
327
+ : model?.[item.key] || "",
328
+ },
329
+ ),
330
+ ),
331
+ );
332
+ },
333
+ ],
291
334
  },
292
335
  defaults: "",
293
336
  icon: `icon-${singular}`,
@@ -297,7 +340,7 @@ export function meta(name, properties = {}) {
297
340
  }
298
341
  ```
299
342
 
300
- The structure arrays typically take a simple payload for their built in rendering but allow a render function for more complex layouts i.e
343
+ Layout is an array of functions that return render functions
301
344
 
302
345
  ```js
303
346
  import { h, resolveComponent } from "vue";
@@ -305,34 +348,66 @@ import { h, resolveComponent } from "vue";
305
348
  {
306
349
  // ...
307
350
  show: {
351
+ layout: [
352
+ ({ model }) =>
353
+ h(resolveComponent("router-link"), {
354
+ class: "underline",
355
+ to: {
356
+ name: "companies.show",
357
+ params: { id: model.company_id },
358
+ },
359
+ text: model.company.name,
360
+ })
361
+ ],
362
+ },
363
+ }
364
+ ```
365
+
366
+ You can see above that the show & index route have a default render function returned for a table and description block. If you want to keep this you can grab the existing render function from the `defaultResource` export.
367
+
368
+ ```js
369
+ import { defaultResource, meta } from "../../../resource/index.js";
370
+
371
+ {
372
+ // ...
373
+ index: {
374
+ layout: [
375
+ () => h("div", "I appear above"),
376
+ ...defaultResource.index.layout,
377
+ () => h("div", "I appear below"),
378
+ ],
379
+ },
380
+ }
381
+ ```
382
+
383
+ Structure arrays take objects. The objects require a key only but have other optional properties. A render function can also be passed to fully customize the rendering that happens.
384
+
385
+ ```js
386
+ {
387
+ table: {
308
388
  structure: [
309
- [
310
- {
311
- label: "Email",
312
- key: "email",
313
- },
314
- {
315
- label: "Company",
316
- render: ({ model }) =>
317
- h(resolveComponent("router-link"), {
318
- class: "underline",
319
- to: {
320
- name: "companies.show",
321
- params: { id: model.company_id },
322
- },
323
- text: model.company.name,
324
- }),
325
- },
326
- ],
327
389
  {
328
- render: ({ model }) =>
329
- h(VelFormRole, {
330
- overrides: model.overrides_roles_and_permissions,
331
- form: { roles: model.roles.map((d) => d.id) },
332
- readonly: true,
333
- }),
390
+ key: "name",
391
+ sortable: true,
392
+ },
393
+ {
394
+ key: "role",
395
+ render: (row) => h("div", "Custom template"),
334
396
  },
335
397
  ],
336
398
  },
399
+ form: {
400
+ structure: [
401
+ {
402
+ key: "name",
403
+ },
404
+ {
405
+ key: "provider_name",
406
+ label: "Provider",
407
+ render: ({ model }) => h("span", model?.provider_name.label ?? ""),
408
+ initial: ({ model }) => model?.provider_name.value ?? null,
409
+ }
410
+ ]
411
+ }
337
412
  }
338
413
  ```
@@ -1,13 +1,15 @@
1
1
  <template>
2
2
  <div class="vel-app">
3
3
  <VelHeader class="justify-end-dive">
4
- <GSvg
5
- class="logo"
6
- style="width: 180px"
7
- embed
8
- asis
9
- :name="$store.state.auth.logo"
10
- />
4
+ <router-link :to="{ name: 'index' }">
5
+ <GSvg
6
+ class="logo"
7
+ style="width: 180px"
8
+ embed
9
+ asis
10
+ :name="$store.state.auth.logo"
11
+ />
12
+ </router-link>
11
13
  <template #links>
12
14
  <div class="flex items-center pr">
13
15
  <GIcon
@@ -6,7 +6,7 @@ import VelTableSorter from "../../../../components/layout/TableSorter.vue";
6
6
  import VelRoleLegend from "../../../../components/layout/RoleLegend.vue";
7
7
  import component from "./form.vue";
8
8
  import userResource from "../PUsers/resource.js";
9
- import { meta } from "../../../resource/index.js";
9
+ import { defaultResource, meta } from "../../../resource/index.js";
10
10
 
11
11
  import { ElNotification } from "element-plus";
12
12
  import { h } from "vue";
@@ -16,6 +16,10 @@ export default [
16
16
  "companies",
17
17
  {
18
18
  defaults: "include=primary_contact",
19
+ permissions: {
20
+ create: ({ $store }) => $store.getters.can("write companies"),
21
+ edit: ({ $store }) => $store.getters.can("write companies"),
22
+ },
19
23
  singular: "company",
20
24
  icon: "icon-cases",
21
25
  form: {
@@ -34,18 +38,16 @@ export default [
34
38
  table: {
35
39
  structure: [
36
40
  {
37
- label: "Name",
38
41
  key: "name",
39
42
  sortable: true,
40
43
  },
41
44
  {
42
- label: "Total users",
43
45
  key: "user_count",
44
- sortable: false,
46
+ label: "Total users",
45
47
  width: "150",
46
48
  },
47
49
  {
48
- label: "Role",
50
+ key: "role",
49
51
  render: (row) =>
50
52
  h(
51
53
  row.roles.length === 1 ? Chip : Chips,
@@ -59,120 +61,104 @@ export default [
59
61
  },
60
62
  ],
61
63
  },
62
- index: {
64
+ description: {
63
65
  structure: [
64
66
  {
65
- render: ({ resource, $store }) =>
66
- h(VelTableSorter, {
67
- key: "PIndex",
68
- "json-data": {
69
- ...resource,
70
- tableStructure: resource.table.structure,
71
- },
72
- defaults: resource.defaults,
73
- "fixed-height": false,
74
- "display-edit-action":
75
- $store.getters.can("write companies"),
76
- }),
67
+ key: "domains",
68
+ render: ({ model }) => h("span", model.domains.join(", ")),
77
69
  },
78
70
  {
79
- render: () =>
80
- h(VelRoleLegend, {
81
- class: "mt-5",
82
- }),
71
+ key: "sso_enabled",
72
+ label: "SSO Enabled",
83
73
  },
84
- ],
85
- },
86
- show: {
87
- actions: [
88
74
  {
75
+ key: "primary_contact",
76
+ label: "Primary Contact",
89
77
  render: ({ model }) =>
90
- model.primary_contact &&
91
- h(
92
- VelButton,
93
- {
94
- type: "primary",
95
- async onClick() {
96
- try {
97
- const res = await axios.post(
98
- `/api/companies/${model.id}/welcome`,
99
- );
100
- ElNotification({
101
- title: "Success",
102
- message: res.data.message,
103
- type: "success",
104
- });
105
-
106
- model.primary_contact_contacted = true;
107
- } catch (e) {
108
- ElNotification({
109
- title: "Warning",
110
- message:
111
- e.response?.data?.message ||
112
- e.message,
113
- type: "warning",
114
- });
115
- }
116
- },
117
- },
118
- "Send welcome email",
119
- ),
78
+ h("span", model.primary_contact?.name),
120
79
  },
121
- ],
122
- structure: [
123
- [
124
- {
125
- label: "Domains",
126
- render: ({ model }) =>
127
- h("span", model.domains.join(", ")),
128
- },
129
- {
130
- label: "SSO Enabled",
131
- key: "sso_enabled",
132
- },
133
- {
134
- label: "Primary Contact",
135
- render: ({ model }) =>
136
- h("span", model.primary_contact?.name),
137
- },
138
- {
139
- label: "Primary Contact Email",
140
- render: ({ model }) =>
141
- h("span", model.primary_contact?.email),
142
- },
143
- {
144
- label: "Primary Contact Contacted",
145
- render: ({ model }) =>
146
- h("span", !!model.primary_contact_contacted),
147
- },
148
- {
149
- label: "Total users",
150
- key: "user_count",
151
- },
152
- ],
153
80
  {
81
+ key: "primary_contact_email",
82
+ label: "Primary Contact Email",
154
83
  render: ({ model }) =>
155
- h(VelFormRole, {
156
- overrides: model.overrides_roles_and_permissions,
157
- form: { roles: model.roles.map((d) => d.id) },
158
- readonly: true,
159
- }),
84
+ h("span", model.primary_contact?.email),
160
85
  },
161
86
  {
162
- render: ({ model, $store }) => {
163
- const resource = meta(...userResource);
164
- return h(VelTableSorter, {
165
- key: "PIndex",
166
- "json-data": {
167
- ...resource,
168
- tableStructure: resource.table.structure,
87
+ key: "primary_contact_contacted",
88
+ label: "Primary Contact Contacted",
89
+ render: ({ model }) =>
90
+ h("span", !!model.primary_contact_contacted),
91
+ },
92
+ {
93
+ label: "Total users",
94
+ key: "user_count",
95
+ },
96
+ ],
97
+ },
98
+ index: {
99
+ layout: [
100
+ ...defaultResource.index.layout,
101
+ () =>
102
+ h(VelRoleLegend, {
103
+ class: "mt-5",
104
+ }),
105
+ ],
106
+ },
107
+ show: {
108
+ actions: [
109
+ ({ model }) =>
110
+ model.primary_contact &&
111
+ h(
112
+ VelButton,
113
+ {
114
+ type: "primary",
115
+ async onClick() {
116
+ try {
117
+ const res = await axios.post(
118
+ `/api/companies/${model.id}/welcome`,
119
+ );
120
+ ElNotification({
121
+ title: "Success",
122
+ message: res.data.message,
123
+ type: "success",
124
+ });
125
+
126
+ model.primary_contact_contacted = true;
127
+ } catch (e) {
128
+ ElNotification({
129
+ title: "Warning",
130
+ message:
131
+ e.response?.data?.message ||
132
+ e.message,
133
+ type: "warning",
134
+ });
135
+ }
169
136
  },
170
- defaults: `include=company&filter[company_id]=${model.id}`,
171
- "fixed-height": false,
172
- "display-edit-action":
173
- $store.getters.can("write users"),
174
- });
175
- },
137
+ },
138
+ "Send welcome email",
139
+ ),
140
+ ],
141
+ layout: [
142
+ ...defaultResource.show.layout,
143
+ ({ model }) =>
144
+ h(VelFormRole, {
145
+ overrides: model.overrides_roles_and_permissions,
146
+ form: { roles: model.roles.map((d) => d.id) },
147
+ readonly: true,
148
+ }),
149
+ ({ model, $store }) => {
150
+ const resource = meta(...userResource);
151
+ return h(VelTableSorter, {
152
+ key: "PIndex",
153
+ "json-data": {
154
+ ...resource,
155
+ tableStructure: resource.table.structure,
156
+ },
157
+ defaults: `include=company&filter[company_id]=${model.id}`,
158
+ "fixed-height": false,
159
+ "display-edit-action":
160
+ $store.getters.can("write users"),
161
+ });
176
162
  },
177
163
  ],
178
164
  },
@@ -4,6 +4,7 @@ import Chips from "../../../../components/layout/Chips.vue";
4
4
  import VelTableSorter from "../../../../components/layout/TableSorter.vue";
5
5
  import VelRoleLegend from "../../../../components/layout/RoleLegend.vue";
6
6
  import component from "./form.vue";
7
+ import { defaultResource } from "../../../resource/index.js";
7
8
 
8
9
  import { ElMessageBox } from "element-plus";
9
10
  import { ElNotification } from "element-plus";
@@ -27,6 +28,10 @@ export default [
27
28
  searchable: {
28
29
  value: "email",
29
30
  },
31
+ permissions: {
32
+ create: ({ $store }) => $store.getters.can("write users"),
33
+ edit: ({ $store }) => $store.getters.can("write users"),
34
+ },
30
35
  form: {
31
36
  async submit({ model, form, $router, $store, method }) {
32
37
  try {
@@ -111,17 +116,14 @@ export default [
111
116
  table: {
112
117
  structure: [
113
118
  {
114
- label: "Name",
115
119
  key: "name",
116
120
  sortable: true,
117
121
  },
118
122
  {
119
- label: "Email",
120
123
  key: "email",
121
- sortable: true,
122
124
  },
123
125
  {
124
- label: "Company",
126
+ key: "company",
125
127
  sortable: true,
126
128
  render: (model) =>
127
129
  h(resolveComponent("router-link"), {
@@ -134,7 +136,7 @@ export default [
134
136
  }),
135
137
  },
136
138
  {
137
- label: "Role",
139
+ key: "role",
138
140
  render: (row) =>
139
141
  h(
140
142
  !row.overrides_roles_and_permissions ||
@@ -156,58 +158,43 @@ export default [
156
158
  },
157
159
  ],
158
160
  },
159
- index: {
161
+ description: {
160
162
  structure: [
161
163
  {
162
- render: ({ resource, $store }) =>
163
- h(VelTableSorter, {
164
- key: "PIndex",
165
- "json-data": {
166
- ...resource,
167
- tableStructure: resource.table.structure,
168
- },
169
- defaults: resource.defaults,
170
- "fixed-height": false,
171
- "display-edit-action":
172
- $store.getters.can("write users"),
173
- }),
164
+ key: "email",
174
165
  },
175
166
  {
176
- render: () =>
177
- h(VelRoleLegend, {
178
- class: "mt-5",
167
+ key: "company",
168
+ render: ({ model }) =>
169
+ h(resolveComponent("router-link"), {
170
+ class: "underline",
171
+ to: {
172
+ name: "companies.show",
173
+ params: { id: model.company_id },
174
+ },
175
+ text: model.company.name,
179
176
  }),
180
177
  },
181
178
  ],
182
179
  },
180
+ index: {
181
+ layout: [
182
+ ...defaultResource.index.layout,
183
+ () =>
184
+ h(VelRoleLegend, {
185
+ class: "mt-5",
186
+ }),
187
+ ],
188
+ },
183
189
  show: {
184
- structure: [
185
- [
186
- {
187
- label: "Email",
188
- key: "email",
189
- },
190
- {
191
- label: "Company",
192
- render: ({ model }) =>
193
- h(resolveComponent("router-link"), {
194
- class: "underline",
195
- to: {
196
- name: "companies.show",
197
- params: { id: model.company_id },
198
- },
199
- text: model.company.name,
200
- }),
201
- },
202
- ],
203
- {
204
- render: ({ model }) =>
205
- h(VelFormRole, {
206
- overrides: model.overrides_roles_and_permissions,
207
- form: { roles: model.roles.map((d) => d.id) },
208
- readonly: true,
209
- }),
210
- },
190
+ layout: [
191
+ ...defaultResource.show.layout,
192
+ ({ model }) =>
193
+ h(VelFormRole, {
194
+ overrides: model.overrides_roles_and_permissions,
195
+ form: { roles: model.roles.map((d) => d.id) },
196
+ readonly: true,
197
+ }),
211
198
  ],
212
199
  },
213
200
  },
@@ -9,22 +9,18 @@
9
9
 
10
10
  <div class="flex gap items-center justify-end">
11
11
  <template
12
- v-for="(row, index) in resource.index.actions"
12
+ v-for="(render, index) in resource.index.actions"
13
13
  :key="index"
14
14
  >
15
- <template v-if="row.render">
16
- <component :is="row.render(this)" />
17
- </template>
15
+ <component :is="render(this)" />
18
16
  </template>
19
17
  </div>
20
18
 
21
19
  <template
22
- v-for="(row, index) in resource.index.structure"
20
+ v-for="(render, index) in resource.index.layout"
23
21
  :key="index"
24
22
  >
25
- <template v-if="row.render">
26
- <component :is="row.render(this)" />
27
- </template>
23
+ <component :is="render(this)" />
28
24
  </template>
29
25
  </div>
30
26
  </div>
@@ -14,15 +14,13 @@
14
14
  :title="`${model.name} ${model.last_name ?? ''}`"
15
15
  >
16
16
  <template
17
- v-for="(row, index) in resource.show.actions"
17
+ v-for="(render, index) in resource.show.actions"
18
18
  :key="index"
19
19
  >
20
- <template v-if="row.render">
21
- <component :is="row.render(this)" />
22
- </template>
20
+ <component :is="render(this)" />
23
21
  </template>
24
22
  <VelButton
25
- v-if="resource.permissions.edit"
23
+ v-if="resource.permissions.edit(this)"
26
24
  tag="a"
27
25
  type="primary"
28
26
  @click="
@@ -45,37 +43,13 @@
45
43
  <hr class="my-3 hr-muted" />
46
44
 
47
45
  <template
48
- v-for="(row, index) in resource.show.structure"
46
+ v-for="(render, index) in resource.show.layout"
49
47
  :key="index"
50
48
  >
51
- <template v-if="row.render">
52
- <component :is="row.render(this)" />
53
- </template>
54
- <el-descriptions v-else border column="1">
55
- <el-descriptions-item
56
- v-for="(item, i) in row"
57
- :key="i"
58
- :label-width="'20%'"
59
- >
60
- <template #label>
61
- {{
62
- item.label ||
63
- item.key[0].toUpperCase() +
64
- item.key.slice(1)
65
- }}
66
- </template>
67
-
68
- <template v-if="item.render">
69
- <component :is="item.render(this)" />
70
- </template>
71
- <template v-else>
72
- {{ model[item.key] }}
73
- </template>
74
- </el-descriptions-item>
75
- </el-descriptions>
49
+ <component :is="render(this)" />
76
50
 
77
51
  <hr
78
- v-if="index < resource.show.structure.length - 1"
52
+ v-if="index < resource.show.layout.length - 1"
79
53
  class="my-3 hr-muted"
80
54
  />
81
55
  </template>
@@ -93,7 +67,6 @@
93
67
  import axios from "axios";
94
68
  import VelSpinner from "../../../components/form/Spinner.vue";
95
69
  import VelButton from "../../../components/basic/Button.vue";
96
- import { ElDescriptions, ElDescriptionsItem } from "element-plus";
97
70
 
98
71
  export default {
99
72
  components: {
@@ -103,8 +76,6 @@ export default {
103
76
  .default,
104
77
  VelSpinner,
105
78
  VelButton,
106
- ElDescriptions,
107
- ElDescriptionsItem,
108
79
  },
109
80
 
110
81
  props: {
@@ -5,8 +5,11 @@ import { merge } from "lodash";
5
5
  import { h } from "vue";
6
6
 
7
7
  import VTableSorter from "../../components/layout/TableSorter.vue";
8
+ import { ElDescriptions, ElDescriptionsItem } from "element-plus";
8
9
 
9
- export function meta(name, properties = {}) {
10
+ export const defaultResource = meta();
11
+
12
+ export function meta(name = "default", properties = {}) {
10
13
  const singular = properties.singular || name.slice(0, -1);
11
14
 
12
15
  return merge(
@@ -19,8 +22,8 @@ export function meta(name, properties = {}) {
19
22
  pageLink: name,
20
23
  api: `/api/${name}`,
21
24
  permissions: {
22
- create: true,
23
- edit: true,
25
+ create: () => true,
26
+ edit: () => true,
24
27
  },
25
28
  searchable: {
26
29
  value: "name",
@@ -29,32 +32,76 @@ export function meta(name, properties = {}) {
29
32
  form: {
30
33
  component: null,
31
34
  fields: () => ({}),
35
+ structure: [],
32
36
  },
33
37
  table: {
34
- structure: [],
38
+ structure: [
39
+ {
40
+ key: "id",
41
+ },
42
+ ],
35
43
  },
36
- index: {
37
- actions: [],
44
+ description: {
38
45
  structure: [
39
46
  {
40
- render: ({ resource }) =>
41
- h(VTableSorter, {
42
- key: "PIndex",
43
- "json-data": {
44
- ...resource,
45
- tableStructure: resource.table.structure,
46
- },
47
- defaults: resource.defaults,
48
- "fixed-height": false,
49
- "display-edit-action":
50
- resource.permissions.create,
51
- }),
47
+ key: "id",
48
+ },
49
+ ],
50
+ },
51
+ index: {
52
+ actions: [],
53
+ layout: [
54
+ (props) => {
55
+ const { resource } = props;
56
+
57
+ return h(VTableSorter, {
58
+ key: "PIndex",
59
+ "json-data": {
60
+ ...resource,
61
+ tableStructure: resource.table.structure,
62
+ },
63
+ defaults: resource.defaults,
64
+ "fixed-height": false,
65
+ "display-edit-action":
66
+ resource.permissions.create(props),
67
+ });
52
68
  },
53
69
  ],
54
70
  },
55
71
  show: {
56
72
  actions: [],
57
- structure: [],
73
+ layout: [
74
+ (props) => {
75
+ const { resource, model } = props;
76
+
77
+ return h(
78
+ ElDescriptions,
79
+ {
80
+ border: true,
81
+ column: 1,
82
+ },
83
+ resource.description.structure.map((item, index) =>
84
+ h(
85
+ ElDescriptionsItem,
86
+ {
87
+ key: index,
88
+ labelWidth: "20%",
89
+ },
90
+ {
91
+ label: () =>
92
+ item.label ||
93
+ item.key[0].toUpperCase() +
94
+ item.key.slice(1),
95
+ default: () =>
96
+ item.render
97
+ ? h(item.render(props))
98
+ : model?.[item.key] || "",
99
+ },
100
+ ),
101
+ ),
102
+ );
103
+ },
104
+ ],
58
105
  },
59
106
  defaults: "",
60
107
  icon: `icon-${singular}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishawack/lab-velocity",
3
- "version": "2.0.0-beta.13",
3
+ "version": "2.0.0-beta.14",
4
4
  "description": "Avalere Health branded style system",
5
5
  "scripts": {
6
6
  "setup": "npm ci || npm i && npm run content",