@fishawack/lab-velocity 2.0.0-beta.5 → 2.0.0-beta.50

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 (111) hide show
  1. package/README.md +467 -36
  2. package/_Build/js/libs/build-id.js +14 -0
  3. package/_Build/js/libs/filters.js +36 -0
  4. package/_Build/js/libs/globals.js +7 -0
  5. package/_Build/js/libs/router.js +22 -0
  6. package/_Build/js/libs/routes.js +29 -0
  7. package/_Build/js/libs/store.js +21 -0
  8. package/_Build/js/libs/utility.js +161 -0
  9. package/_Build/vue/components/basic/Button.vue +1 -1
  10. package/_Build/vue/components/form/Avatar.vue +90 -0
  11. package/_Build/vue/components/form/Checkbox.vue +10 -0
  12. package/_Build/vue/components/form/InputNumber.vue +1 -1
  13. package/_Build/vue/components/form/Select.vue +223 -33
  14. package/_Build/vue/components/form/Spinner.vue +5 -0
  15. package/_Build/vue/components/layout/Alert.vue +5 -5
  16. package/_Build/vue/components/layout/Audit.vue +143 -0
  17. package/_Build/vue/{modules/AuthModule/components/VBreadcrumbs.vue → components/layout/Breadcrumbs.vue} +4 -4
  18. package/_Build/vue/{modules/AuthModule/components → components/layout}/Chips.vue +2 -2
  19. package/_Build/vue/components/layout/Footer.vue +11 -10
  20. package/_Build/vue/{modules/AuthModule/components/VFormFooter.vue → components/layout/FormFooter.vue} +13 -7
  21. package/_Build/vue/{modules/AuthModule/components → components/layout}/FormRole.vue +10 -8
  22. package/_Build/vue/components/layout/Layout.vue +94 -0
  23. package/_Build/vue/components/layout/Navigation.vue +77 -0
  24. package/_Build/vue/{modules/AuthModule/components/VPageHeader.vue → components/layout/PageHeader.vue} +14 -8
  25. package/_Build/vue/components/layout/SideBar.vue +26 -0
  26. package/_Build/vue/{modules/AuthModule/components/VTable.vue → components/layout/Table.vue} +37 -16
  27. package/_Build/vue/{modules/AuthModule/components/VTableSorter.vue → components/layout/TableSorter.vue} +108 -52
  28. package/_Build/vue/components/layout/TokenDisplay.vue +52 -0
  29. package/_Build/vue/components/layout/pageTitle.vue +1 -1
  30. package/_Build/vue/components/navigation/MenuItem.vue +7 -2
  31. package/_Build/vue/components/navigation/MenuItemGroup.vue +7 -2
  32. package/_Build/vue/modules/AuthModule/js/axios.js +21 -1
  33. package/_Build/vue/modules/AuthModule/js/guest-request.js +32 -0
  34. package/_Build/vue/modules/AuthModule/js/impersonation-banner.js +102 -0
  35. package/_Build/vue/modules/AuthModule/js/router.js +91 -114
  36. package/_Build/vue/modules/AuthModule/js/store.js +23 -6
  37. package/_Build/vue/modules/AuthModule/routes/PCompanies/columns.js +268 -0
  38. package/_Build/vue/modules/AuthModule/routes/PCompanies/resource.js +213 -0
  39. package/_Build/vue/modules/AuthModule/routes/PIntegrations/columns.js +58 -0
  40. package/_Build/vue/modules/AuthModule/routes/PIntegrations/resource.js +79 -0
  41. package/_Build/vue/modules/AuthModule/routes/PTeams/columns.js +78 -0
  42. package/_Build/vue/modules/AuthModule/routes/PTeams/resource.js +251 -0
  43. package/_Build/vue/modules/AuthModule/routes/PUsers/SetPasswordAction.vue +51 -0
  44. package/_Build/vue/modules/AuthModule/routes/PUsers/SetPasswordDialog.vue +138 -0
  45. package/_Build/vue/modules/AuthModule/routes/PUsers/columns.js +349 -0
  46. package/_Build/vue/modules/AuthModule/routes/PUsers/resource.js +239 -0
  47. package/_Build/vue/modules/AuthModule/routes/account-exists.vue +2 -2
  48. package/_Build/vue/modules/AuthModule/routes/change-password.vue +28 -32
  49. package/_Build/vue/modules/AuthModule/routes/container.vue +2 -11
  50. package/_Build/vue/modules/AuthModule/routes/expired-reset.vue +4 -4
  51. package/_Build/vue/modules/AuthModule/routes/expired-verification.vue +10 -9
  52. package/_Build/vue/modules/AuthModule/routes/force-reset.vue +44 -58
  53. package/_Build/vue/modules/AuthModule/routes/forgot.vue +10 -5
  54. package/_Build/vue/modules/AuthModule/routes/login.vue +12 -19
  55. package/_Build/vue/modules/AuthModule/routes/logincallback.vue +1 -3
  56. package/_Build/vue/modules/AuthModule/routes/loginsso.vue +14 -10
  57. package/_Build/vue/modules/AuthModule/routes/logout.vue +17 -5
  58. package/_Build/vue/modules/AuthModule/routes/logoutheadless.vue +1 -3
  59. package/_Build/vue/modules/AuthModule/routes/register.vue +24 -28
  60. package/_Build/vue/modules/AuthModule/routes/reset.vue +20 -14
  61. package/_Build/vue/modules/AuthModule/routes/success-forgot.vue +14 -8
  62. package/_Build/vue/modules/AuthModule/routes/success-reset.vue +2 -2
  63. package/_Build/vue/modules/AuthModule/routes/success-verify.vue +1 -3
  64. package/_Build/vue/modules/AuthModule/routes/verify.vue +11 -14
  65. package/_Build/vue/modules/resource/Children/create.vue +81 -0
  66. package/_Build/vue/modules/resource/Children/edit.vue +106 -0
  67. package/_Build/vue/modules/resource/Children/index.vue +42 -0
  68. package/_Build/vue/modules/resource/Children/partials/form.vue +111 -0
  69. package/_Build/vue/modules/resource/Children/show.vue +166 -0
  70. package/_Build/vue/modules/resource/index.js +561 -0
  71. package/_Build/vue/modules/resource/parent.vue +63 -0
  72. package/_Build/vue/modules/resource/trashable.js +104 -0
  73. package/_base.scss +0 -1
  74. package/_defaults.scss +2 -13
  75. package/_variables.scss +9 -4
  76. package/{modules/_AuthModule.scss → components/_auth.scss} +19 -68
  77. package/components/_datepicker.scss +1 -0
  78. package/components/_descriptions.scss +2 -0
  79. package/components/_footer.scss +1 -0
  80. package/components/_form.scss +18 -0
  81. package/components/_header.scss +3 -27
  82. package/components/_layout.scss +56 -0
  83. package/components/_menu.scss +0 -5
  84. package/components/_sidebar.scss +12 -27
  85. package/components/_table.scss +3 -0
  86. package/components/_token-display.scss +41 -0
  87. package/general.scss +1 -0
  88. package/index.js +31 -1
  89. package/package.json +7 -4
  90. package/vendor.scss +0 -1
  91. package/_Build/vue/components/layout/sideBar.vue +0 -25
  92. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/Upload/upload.vue +0 -251
  93. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/create.vue +0 -62
  94. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/edit.vue +0 -98
  95. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/index.vue +0 -90
  96. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/partials/form.vue +0 -173
  97. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/show.vue +0 -262
  98. package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/parent.vue +0 -36
  99. package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/create.vue +0 -112
  100. package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/edit.vue +0 -103
  101. package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/index.vue +0 -112
  102. package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/partials/form.vue +0 -169
  103. package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/show.vue +0 -120
  104. package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/parent.vue +0 -36
  105. package/components/_input.scss +0 -0
  106. package/modules/_AuthVariables.scss +0 -7
  107. /package/_Build/vue/{modules/AuthModule/components → components/layout}/AuthModal.vue +0 -0
  108. /package/_Build/vue/{modules/AuthModule/components → components/layout}/Chip.vue +0 -0
  109. /package/_Build/vue/{modules/AuthModule/components/VPasswordValidation.vue → components/layout/PasswordValidation.vue} +0 -0
  110. /package/_Build/vue/{modules/AuthModule/components/VRoleLegend.vue → components/layout/RoleLegend.vue} +0 -0
  111. /package/{modules → components}/_modal.scss +0 -0
@@ -4,15 +4,15 @@
4
4
  <h1 class="h2 h2--small">Reset password</h1>
5
5
 
6
6
  <form class="form" @submit.prevent="submit">
7
- <p class="AM-mt-2 AM-mb-0 AM-color-highlight">
7
+ <p class="mt-2 mb-0 color-highlight">
8
8
  <strong class="">Email: {{ form?.email }}</strong>
9
9
  </p>
10
- <p class="AM-mt">
10
+ <p class="mt">
11
11
  If this account exists we have sent an email containing
12
12
  instructions for resetting the password.
13
13
  <strong>Please check your inbox.</strong>
14
14
  </p>
15
- <p class="AM-mt-3 AM-mb">
15
+ <p class="mt-3 mb">
16
16
  Haven’t received the email after 10 minutes?
17
17
  </p>
18
18
 
@@ -37,7 +37,7 @@
37
37
  Re-send instructions
38
38
  </elButton>
39
39
 
40
- <p class="disclaimer AM-mt-3">
40
+ <p class="disclaimer mt-3">
41
41
  <router-link
42
42
  class="color-1 underline"
43
43
  :to="{ name: `auth.login` }"
@@ -52,6 +52,8 @@
52
52
 
53
53
  <script>
54
54
  import Form from "form-backend-validation";
55
+ import { ElNotification } from "element-plus";
56
+ import { guestRequest } from "../js/guest-request";
55
57
 
56
58
  export default {
57
59
  components: {
@@ -76,9 +78,13 @@ export default {
76
78
  this.notification = null;
77
79
  }
78
80
 
79
- await this.form.post("/forgot-password");
81
+ await guestRequest({
82
+ form: this.form,
83
+ url: "/forgot-password",
84
+ store: this.$store,
85
+ });
80
86
 
81
- this.$notify({
87
+ ElNotification({
82
88
  type: "success",
83
89
  message: "Email has been re-sent",
84
90
  duration: 10000,
@@ -86,14 +92,14 @@ export default {
86
92
  });
87
93
  } catch (e) {
88
94
  if (e.response && e.response.status === 422) {
89
- this.notification = this.$notify({
95
+ this.notification = ElNotification({
90
96
  type: "warning",
91
97
  message: "Please allow 10 minutes before re-requesting",
92
98
  duration: 0,
93
99
  class: "el-notification--warning el-notification--right-override",
94
100
  });
95
101
  } else {
96
- this.$root.errors(e);
102
+ console.log(e);
97
103
  }
98
104
  }
99
105
  },
@@ -3,10 +3,10 @@
3
3
  <section id="resetPasswordForm">
4
4
  <h1 class="h2 h2--small">Success</h1>
5
5
 
6
- <p class="AM-mt-2 AM-mb-0 AM-color-highlight">
6
+ <p class="mt-2 mb-0 color-highlight">
7
7
  <strong class="">Email: {{ $route.query.email }}</strong>
8
8
  </p>
9
- <p class="AM-mt">
9
+ <p class="mt">
10
10
  Your password has been reset. Please sign in to continue.
11
11
  </p>
12
12
 
@@ -20,9 +20,7 @@
20
20
  <script>
21
21
  export default {
22
22
  mounted() {
23
- this.$store.dispatch("getUser", {
24
- errors: this.$root.errors,
25
- });
23
+ this.$store.dispatch("getUser");
26
24
  },
27
25
  components: {
28
26
  elButton: require("../../../components/basic/Button.vue").default,
@@ -2,17 +2,15 @@
2
2
  <h1 class="h2 h2--small">Verify your email address</h1>
3
3
 
4
4
  <form class="form" @submit.prevent="submit">
5
- <p class="AM-mt-2 AM-mb-0 AM-color-highlight">
5
+ <p class="mt-2 mb-0 color-highlight">
6
6
  <strong class="">Email: {{ user?.email }}</strong>
7
7
  </p>
8
- <p class="AM-mt-0.5">
8
+ <p class="mt-0.5">
9
9
  <strong>Please check your inbox</strong> for a Verification email
10
10
  from {{ $root.appName }}. Click the link in that email to verify
11
11
  your email address and complete your registration.
12
12
  </p>
13
- <p class="AM-mt-0.5 AM-mb-0">
14
- Haven’t received the email after 10 minutes?
15
- </p>
13
+ <p class="mt-0.5 mb-0">Haven’t received the email after 10 minutes?</p>
16
14
 
17
15
  <el-input
18
16
  class="hidden"
@@ -26,7 +24,7 @@
26
24
  />
27
25
 
28
26
  <elButton
29
- class="AM-mt AM-mb-3"
27
+ class="mt mb-3"
30
28
  native-type="submit"
31
29
  type="primary"
32
30
  :disabled="form.processing"
@@ -36,7 +34,7 @@
36
34
  Re-send verification link
37
35
  </elButton>
38
36
 
39
- <p class="disclaimer AM-mt-1.5">
37
+ <p class="disclaimer mt-1.5">
40
38
  Having trouble singing in?
41
39
  <a :href="$store.state.auth.contact" class="underline"
42
40
  >Contact us</a
@@ -47,6 +45,7 @@
47
45
 
48
46
  <script>
49
47
  import Form from "form-backend-validation";
48
+ import { ElNotification } from "element-plus";
50
49
 
51
50
  export default {
52
51
  components: {
@@ -74,7 +73,7 @@ export default {
74
73
 
75
74
  await this.form.post("/email/verification-notification");
76
75
 
77
- this.$notify({
76
+ ElNotification({
78
77
  type: "success",
79
78
  message: "Email has been re-sent",
80
79
  duration: 10000,
@@ -82,30 +81,28 @@ export default {
82
81
  });
83
82
  } catch (e) {
84
83
  if (e.response && e.response.status === 429) {
85
- this.notification = this.$notify({
84
+ this.notification = ElNotification({
86
85
  type: "warning",
87
86
  message: "Please allow 10 minutes before re-requesting",
88
87
  duration: 0,
89
88
  class: "el-notification--warning el-notification--right-override",
90
89
  });
91
90
  } else if (e.response && e.response.status === 422) {
92
- this.notification = this.$notify({
91
+ this.notification = ElNotification({
93
92
  type: "error",
94
93
  message: e.response.data.message,
95
94
  duration: 0,
96
95
  class: "el-notification--error el-notification--right-override",
97
96
  });
98
97
  } else {
99
- this.$root.errors(e);
98
+ console.log(e);
100
99
  }
101
100
  }
102
101
  },
103
102
  },
104
103
 
105
104
  async mounted() {
106
- this.user = await this.$store.dispatch("getUser", {
107
- errors: this.$root.errors,
108
- });
105
+ this.user = await this.$store.dispatch("getUser");
109
106
 
110
107
  this.form.email = this.user.email;
111
108
  },
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <div class="container px-6 tablet:px-4 mobile:px-2 mb-8 ml-0 mr-0">
3
+ <div class="grid__1/1">
4
+ <div class="grid__1/1 mb-4">
5
+ <h2 class="h1">Create {{ resource.singular }}</h2>
6
+ </div>
7
+ <div :class="['mt', resource.form.class]">
8
+ <component
9
+ :is="resource.form.component ?? 'XForm'"
10
+ ref="form"
11
+ :form="form"
12
+ :submit="submit"
13
+ :method="method"
14
+ :resource="resource"
15
+ />
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script>
22
+ import Form from "form-backend-validation";
23
+
24
+ export default {
25
+ components: {
26
+ XForm: require("./partials/form.vue").default,
27
+ },
28
+
29
+ props: {
30
+ resource: {
31
+ type: Object,
32
+ required: true,
33
+ },
34
+ },
35
+
36
+ data() {
37
+ return {
38
+ form: null,
39
+ method: "post",
40
+ };
41
+ },
42
+
43
+ beforeMount() {
44
+ this.form = new Form(this.resource.form.fields(this), {
45
+ resetOnSuccess: false,
46
+ });
47
+ },
48
+
49
+ methods: {
50
+ async submit() {
51
+ if (this.resource.form.submit) {
52
+ await this.resource.form.submit(this);
53
+ } else {
54
+ const hold = { ...this.form.data() };
55
+
56
+ try {
57
+ this.form.populate(this.resource.form.preparation(this));
58
+
59
+ let res = await this.form.post(
60
+ `${this.resource.api.endpoint(this)}`,
61
+ );
62
+
63
+ this.$router.replace({
64
+ name: `${this.resource.routeName}.show`,
65
+ params: { [this.resource.id]: res.data.id },
66
+ });
67
+ } catch (e) {
68
+ console.log(e);
69
+ } finally {
70
+ if (
71
+ !this.form.successful ||
72
+ !this.form.__options.resetOnSuccess
73
+ ) {
74
+ this.form.populate(hold);
75
+ }
76
+ }
77
+ }
78
+ },
79
+ },
80
+ };
81
+ </script>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <div class="container px-6 tablet:px-4 mobile:px-2 mb-8 ml-0 mr-0">
3
+ <div class="grid__1/1">
4
+ <div class="grid__1/1 mb-4">
5
+ <h2 class="h1">Edit {{ resource.singular }}</h2>
6
+ </div>
7
+ <div :class="resource.form.class">
8
+ <component
9
+ :is="resource.form.component ?? 'XForm'"
10
+ ref="form"
11
+ :form="form"
12
+ :submit="submit"
13
+ :method="method"
14
+ :resource="resource"
15
+ :model="model"
16
+ />
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ import Form from "form-backend-validation";
24
+
25
+ export default {
26
+ components: {
27
+ XForm: require("./partials/form.vue").default,
28
+ },
29
+
30
+ props: {
31
+ resource: {
32
+ type: Object,
33
+ required: true,
34
+ },
35
+ },
36
+
37
+ data() {
38
+ return {
39
+ form: null,
40
+ model: null,
41
+ method: "patch",
42
+ };
43
+ },
44
+
45
+ beforeMount() {
46
+ this.form = new Form(
47
+ { _method: "PATCH", ...this.resource.form.fields(this) },
48
+ {
49
+ resetOnSuccess: false,
50
+ },
51
+ );
52
+ },
53
+
54
+ async mounted() {
55
+ window.axios
56
+ .get(
57
+ `${this.resource.api.endpoint(this)}/${this.$route.params[this.resource.id]}`,
58
+ {
59
+ params: this.resource.api.params.show(this),
60
+ },
61
+ )
62
+ .then((res) => {
63
+ this.model = res.data.data;
64
+
65
+ // Set initial form data
66
+ Object.entries(this.resource.form.fields(this)).forEach(
67
+ ([key, value]) => {
68
+ this.form[key] = value;
69
+ },
70
+ );
71
+ });
72
+ },
73
+
74
+ methods: {
75
+ async submit() {
76
+ if (this.resource.form.submit) {
77
+ await this.resource.form.submit(this);
78
+ } else {
79
+ const hold = { ...this.form.data() };
80
+
81
+ try {
82
+ this.form.populate(this.resource.form.preparation(this));
83
+
84
+ let res = await this.form.post(
85
+ `${this.resource.api.endpoint(this)}/${this.model.id}`,
86
+ );
87
+
88
+ this.$router.replace({
89
+ name: `${this.resource.routeName}.show`,
90
+ params: { [this.resource.id]: res.data.id },
91
+ });
92
+ } catch (e) {
93
+ console.log(e);
94
+ } finally {
95
+ if (
96
+ !this.form.successful ||
97
+ !this.form.__options.resetOnSuccess
98
+ ) {
99
+ this.form.populate(hold);
100
+ }
101
+ }
102
+ }
103
+ },
104
+ },
105
+ };
106
+ </script>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="container px-6 tablet:px-4 mobile:px-2 mb-8 ml-0 mr-0">
3
+ <div class="grid__1/1">
4
+ <div class="flex gap items-center justify-end">
5
+ <template
6
+ v-for="(render, index) in resource.index.actions"
7
+ :key="index"
8
+ >
9
+ <component :is="render(this)" />
10
+ </template>
11
+ </div>
12
+
13
+ <template
14
+ v-for="(render, index) in resource.index.layout"
15
+ :key="index"
16
+ >
17
+ <component :is="render(this)" />
18
+ </template>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script>
24
+ export default {
25
+ provide() {
26
+ return {
27
+ additionalInfo: { resource: this.resource },
28
+ };
29
+ },
30
+
31
+ props: {
32
+ resource: {
33
+ type: Object,
34
+ required: true,
35
+ },
36
+ depth: {
37
+ type: Number,
38
+ required: true,
39
+ },
40
+ },
41
+ };
42
+ </script>
@@ -0,0 +1,111 @@
1
+ <!-- eslint-disable vue/no-mutating-props -->
2
+ <template>
3
+ <form @submit.prevent="submit" class="vel-resource-form">
4
+ <component
5
+ :is="segment.group ? 'fieldset' : 'div'"
6
+ v-for="(segment, sIndex) in segments"
7
+ :key="sIndex"
8
+ :class="{ 'vel-form-group': segment.group }"
9
+ >
10
+ <legend v-if="segment.group">
11
+ {{ segment.group.title || formatGroupKey(segment.groupKey) }}
12
+ </legend>
13
+ <div :class="segment.group?.class" :style="segment.group?.style">
14
+ <component
15
+ v-for="(item, iIndex) in segment.items"
16
+ :key="`${sIndex}-${iIndex}`"
17
+ :is="item.render ? item.render(_self) : 'VelBasic'"
18
+ v-model="form[item.key]"
19
+ :type="item.type || 'text'"
20
+ :error="form.errors"
21
+ :name="item.key"
22
+ :placeholder="
23
+ item.placeholder ||
24
+ item.label ||
25
+ item.key[0].toUpperCase() + item.key.slice(1)
26
+ "
27
+ :label="
28
+ item.label ||
29
+ item.key[0].toUpperCase() + item.key.slice(1)
30
+ "
31
+ v-bind="item"
32
+ />
33
+ </div>
34
+ </component>
35
+
36
+ <VelFormFooter :loading="form.processing" />
37
+ </form>
38
+ </template>
39
+
40
+ <script>
41
+ export default {
42
+ components: {
43
+ VelFormFooter: require("../../../../components/layout/FormFooter.vue")
44
+ .default,
45
+ VelBasic: require("../../../../components/form/basic.vue").default,
46
+ },
47
+
48
+ props: {
49
+ form: {
50
+ required: true,
51
+ type: Object,
52
+ },
53
+ submit: {
54
+ required: true,
55
+ type: Function,
56
+ },
57
+ method: {
58
+ type: String,
59
+ default: "post",
60
+ },
61
+ resource: {
62
+ required: true,
63
+ type: Object,
64
+ },
65
+ model: {
66
+ type: Object,
67
+ default: null,
68
+ },
69
+ },
70
+
71
+ computed: {
72
+ _self() {
73
+ return this;
74
+ },
75
+ segments() {
76
+ const items = this.resource.form.structure(this);
77
+ const groups = this.resource.form.groups || {};
78
+ const segments = [];
79
+ const groupSegments = {};
80
+
81
+ for (const item of items) {
82
+ if (item.groupKey && groups[item.groupKey]) {
83
+ if (groupSegments[item.groupKey]) {
84
+ groupSegments[item.groupKey].items.push(item);
85
+ } else {
86
+ const segment = {
87
+ groupKey: item.groupKey,
88
+ group: groups[item.groupKey],
89
+ items: [item],
90
+ };
91
+ groupSegments[item.groupKey] = segment;
92
+ segments.push(segment);
93
+ }
94
+ } else {
95
+ segments.push({ group: null, items: [item] });
96
+ }
97
+ }
98
+
99
+ return segments;
100
+ },
101
+ },
102
+
103
+ methods: {
104
+ formatGroupKey(key) {
105
+ return key
106
+ .replace(/[-_]/g, " ")
107
+ .replace(/\b\w/g, (c) => c.toUpperCase());
108
+ },
109
+ },
110
+ };
111
+ </script>
@@ -0,0 +1,166 @@
1
+ <template>
2
+ <!-- Render nested router view if this isn't the deepest route -->
3
+ <router-view v-if="!deepestRoute" v-slot="{ Component }">
4
+ <component :is="Component" :depth="depth + 1" />
5
+ </router-view>
6
+
7
+ <template v-else>
8
+ <div class="container px-6 tablet:px-4 mobile:px-2 mb-8 ml-0 mr-0">
9
+ <div class="grid__1/1">
10
+ <template v-if="model">
11
+ <div class="bg-0 p-3 box-shadow-1 border-r-4 mb-6">
12
+ <VelPageHeader
13
+ :icon="resource.icon"
14
+ :title="
15
+ resource.modelTitle
16
+ ? resource.modelTitle(this)
17
+ : `${model.name ?? model.id} ${model.last_name ?? ''}`
18
+ "
19
+ >
20
+ <template
21
+ v-for="(rendered, index) in renderedActions"
22
+ :key="index"
23
+ >
24
+ <component :is="rendered" />
25
+ </template>
26
+ </VelPageHeader>
27
+
28
+ <hr class="my-3 hr-muted" />
29
+
30
+ <el-tabs v-model="active" type="card">
31
+ <template
32
+ v-for="(rendered, index) in renderedTabs"
33
+ :key="index"
34
+ >
35
+ <el-tab-pane :name="index">
36
+ <template #label>
37
+ <span class="align-middle-dive">
38
+ <GIcon
39
+ v-if="rendered.icon"
40
+ class="icon icon--text mr"
41
+ :name="rendered.icon"
42
+ />
43
+ <span>{{
44
+ rendered.label ||
45
+ `Tab ${index + 1}`
46
+ }}</span>
47
+ </span>
48
+ </template>
49
+ <component
50
+ :is="rendered.component || rendered"
51
+ />
52
+ </el-tab-pane>
53
+ </template>
54
+ </el-tabs>
55
+ </div>
56
+ </template>
57
+ <div v-else class="absolute transform-center text-center">
58
+ <VelSpinner />
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </template>
63
+ </template>
64
+
65
+ <script>
66
+ import { h } from "vue";
67
+ import axios from "axios";
68
+ import VelSpinner from "../../../components/form/Spinner.vue";
69
+ import VelButton from "../../../components/basic/Button.vue";
70
+ import VelAudit from "../../../components/layout/Audit.vue";
71
+ import { ElTabs, ElTabPane } from "element-plus";
72
+
73
+ export default {
74
+ components: {
75
+ VelPageHeader: require("../../../components/layout/PageHeader.vue")
76
+ .default,
77
+ VelSpinner,
78
+ VelButton,
79
+ ElTabs,
80
+ ElTabPane,
81
+ },
82
+
83
+ props: {
84
+ breadcrumbs: {
85
+ type: Array,
86
+ required: true,
87
+ },
88
+ resource: {
89
+ type: Object,
90
+ required: true,
91
+ },
92
+ depth: {
93
+ type: Number,
94
+ required: true,
95
+ },
96
+ },
97
+
98
+ data() {
99
+ return {
100
+ model: null,
101
+ active: 0,
102
+ };
103
+ },
104
+
105
+ computed: {
106
+ // This boolean helps determine if we are the final depth of route being rendered
107
+ deepestRoute() {
108
+ return (
109
+ this.depth ===
110
+ this.$route.matched.filter((d) => d.components).length
111
+ );
112
+ },
113
+
114
+ // Compute rendered layout once
115
+ renderedTabs() {
116
+ const tabs = this.resource.show.tabs
117
+ .map((render) => render(this))
118
+ .filter((d) => d);
119
+
120
+ if (this.resource.auditable) {
121
+ const auditableType =
122
+ typeof this.resource.auditable === "string"
123
+ ? this.resource.auditable
124
+ : this.resource.singular.replace(/ /g, "_");
125
+
126
+ tabs.push({
127
+ label: "History",
128
+ icon: "icon-time",
129
+ component: h(VelAudit, {
130
+ auditableType,
131
+ auditableId: this.model.id,
132
+ }),
133
+ });
134
+ }
135
+
136
+ return tabs;
137
+ },
138
+
139
+ // Compute rendered actions once
140
+ renderedActions() {
141
+ return this.resource.show.actions
142
+ .map((render) => render(this))
143
+ .filter((d) => d);
144
+ },
145
+ },
146
+
147
+ mounted() {
148
+ if (!this.deepestRoute) {
149
+ return;
150
+ }
151
+
152
+ const showParams = this.resource.api.params.show(this);
153
+
154
+ axios
155
+ .get(
156
+ `${this.resource.api.endpoint(this)}/${this.$route.params[`${this.resource.id}`]}`,
157
+ {
158
+ params: showParams,
159
+ },
160
+ )
161
+ .then((res) => {
162
+ this.model = res.data.data;
163
+ });
164
+ },
165
+ };
166
+ </script>