adminforth 1.3.54-next.9 → 1.3.55-next.2

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 (224) hide show
  1. package/dist/auth.d.ts +31 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +42 -56
  4. package/dist/auth.js.map +1 -0
  5. package/dist/basePlugin.d.ts +18 -0
  6. package/dist/basePlugin.d.ts.map +1 -0
  7. package/dist/basePlugin.js +1 -0
  8. package/dist/basePlugin.js.map +1 -0
  9. package/dist/dataConnectors/baseConnector.d.ts +94 -0
  10. package/dist/dataConnectors/baseConnector.d.ts.map +1 -0
  11. package/dist/dataConnectors/baseConnector.js +108 -122
  12. package/dist/dataConnectors/baseConnector.js.map +1 -0
  13. package/dist/dataConnectors/clickhouse.d.ts +92 -0
  14. package/dist/dataConnectors/clickhouse.d.ts.map +1 -0
  15. package/dist/dataConnectors/clickhouse.js +132 -149
  16. package/dist/dataConnectors/clickhouse.js.map +1 -0
  17. package/dist/dataConnectors/mongo.d.ts +93 -0
  18. package/dist/dataConnectors/mongo.d.ts.map +1 -0
  19. package/dist/dataConnectors/mongo.js +75 -101
  20. package/dist/dataConnectors/mongo.js.map +1 -0
  21. package/dist/dataConnectors/postgres.d.ts +71 -0
  22. package/dist/dataConnectors/postgres.d.ts.map +1 -0
  23. package/dist/dataConnectors/postgres.js +124 -143
  24. package/dist/dataConnectors/postgres.js.map +1 -0
  25. package/dist/dataConnectors/sqlite.d.ts +67 -0
  26. package/dist/dataConnectors/sqlite.d.ts.map +1 -0
  27. package/dist/dataConnectors/sqlite.js +113 -130
  28. package/dist/dataConnectors/sqlite.js.map +1 -0
  29. package/dist/index.d.ts +92 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +197 -217
  32. package/dist/index.js.map +1 -0
  33. package/dist/modules/codeInjector.d.ts +35 -0
  34. package/dist/modules/codeInjector.d.ts.map +1 -0
  35. package/dist/modules/codeInjector.js +480 -486
  36. package/dist/modules/codeInjector.js.map +1 -0
  37. package/dist/modules/configValidator.d.ts +12 -0
  38. package/dist/modules/configValidator.d.ts.map +1 -0
  39. package/dist/modules/configValidator.js +31 -22
  40. package/dist/modules/configValidator.js.map +1 -0
  41. package/dist/modules/operationalResource.d.ts +17 -0
  42. package/dist/modules/operationalResource.d.ts.map +1 -0
  43. package/dist/modules/operationalResource.js +50 -70
  44. package/dist/modules/operationalResource.js.map +1 -0
  45. package/dist/modules/restApi.d.ts +10 -0
  46. package/dist/modules/restApi.d.ts.map +1 -0
  47. package/dist/modules/restApi.js +104 -116
  48. package/dist/modules/restApi.js.map +1 -0
  49. package/dist/modules/styleGenerator.d.ts +9 -0
  50. package/dist/modules/styleGenerator.d.ts.map +1 -0
  51. package/dist/modules/styleGenerator.js +1 -0
  52. package/dist/modules/styleGenerator.js.map +1 -0
  53. package/dist/modules/styles.d.ts +96 -0
  54. package/dist/modules/styles.d.ts.map +1 -0
  55. package/dist/modules/styles.js +1 -0
  56. package/dist/modules/styles.js.map +1 -0
  57. package/dist/modules/utils.d.ts +39 -0
  58. package/dist/modules/utils.d.ts.map +1 -0
  59. package/dist/modules/utils.js +1 -0
  60. package/dist/modules/utils.js.map +1 -0
  61. package/dist/plugins/audit-log/types.d.ts +35 -0
  62. package/dist/plugins/audit-log/types.d.ts.map +1 -0
  63. package/dist/plugins/audit-log/types.js +2 -0
  64. package/dist/plugins/audit-log/types.js.map +1 -0
  65. package/dist/plugins/chat-gpt/types.d.ts +82 -0
  66. package/dist/plugins/chat-gpt/types.d.ts.map +1 -0
  67. package/dist/plugins/chat-gpt/types.js +2 -0
  68. package/dist/plugins/chat-gpt/types.js.map +1 -0
  69. package/dist/plugins/email-password-reset/types.d.ts +28 -0
  70. package/dist/plugins/email-password-reset/types.d.ts.map +1 -0
  71. package/dist/plugins/email-password-reset/types.js +2 -0
  72. package/dist/plugins/email-password-reset/types.js.map +1 -0
  73. package/dist/plugins/foreign-inline-list/types.d.ts +19 -0
  74. package/dist/plugins/foreign-inline-list/types.d.ts.map +1 -0
  75. package/dist/plugins/foreign-inline-list/types.js +2 -0
  76. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  77. package/dist/plugins/import-export/types.d.ts +3 -0
  78. package/dist/plugins/import-export/types.d.ts.map +1 -0
  79. package/dist/plugins/import-export/types.js +2 -0
  80. package/dist/plugins/import-export/types.js.map +1 -0
  81. package/dist/plugins/rich-editor/custom/async-queue.d.ts +8 -0
  82. package/dist/plugins/rich-editor/custom/async-queue.d.ts.map +1 -0
  83. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  84. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  85. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts +8 -0
  86. package/dist/plugins/rich-editor/dist/custom/async-queue.d.ts.map +1 -0
  87. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  88. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  89. package/dist/plugins/rich-editor/types.d.ts +153 -0
  90. package/dist/plugins/rich-editor/types.d.ts.map +1 -0
  91. package/dist/plugins/rich-editor/types.js +16 -0
  92. package/dist/plugins/rich-editor/types.js.map +1 -0
  93. package/dist/plugins/two-factors-auth/types.d.ts +18 -0
  94. package/dist/plugins/two-factors-auth/types.d.ts.map +1 -0
  95. package/dist/plugins/two-factors-auth/types.js +2 -0
  96. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  97. package/dist/plugins/upload/types.d.ts +132 -0
  98. package/dist/plugins/upload/types.d.ts.map +1 -0
  99. package/dist/plugins/upload/types.js +2 -0
  100. package/dist/plugins/upload/types.js.map +1 -0
  101. package/dist/servers/express.d.ts +18 -0
  102. package/dist/servers/express.d.ts.map +1 -0
  103. package/dist/servers/express.js +30 -42
  104. package/dist/servers/express.js.map +1 -0
  105. package/dist/spa/index.html +2 -2
  106. package/dist/spa/package-lock.json +87 -1
  107. package/dist/spa/package.json +4 -1
  108. package/dist/spa/src/App.vue +154 -50
  109. package/dist/spa/src/components/AcceptModal.vue +1 -1
  110. package/dist/spa/src/components/Breadcrumbs.vue +1 -1
  111. package/dist/spa/src/components/CustomDatePicker.vue +1 -1
  112. package/dist/spa/src/components/CustomDateRangePicker.vue +1 -1
  113. package/dist/spa/src/components/CustomRangePicker.vue +9 -5
  114. package/dist/spa/src/components/Dropdown.vue +4 -4
  115. package/dist/spa/src/components/Filters.vue +2 -2
  116. package/dist/spa/src/components/MenuLink.vue +3 -0
  117. package/dist/spa/src/components/ResourceForm.vue +67 -36
  118. package/dist/spa/src/components/ResourceListTable.vue +216 -144
  119. package/dist/spa/src/components/SkeleteLoader.vue +4 -4
  120. package/dist/spa/src/components/Toast.vue +3 -2
  121. package/dist/spa/src/components/ValueRenderer.vue +81 -6
  122. package/dist/spa/src/composables/useFrontendApi.ts +1 -1
  123. package/dist/spa/src/composables/useStores.ts +18 -14
  124. package/dist/spa/src/index.scss +4 -0
  125. package/{spa → dist/spa}/src/renderers/CompactUUID.vue +4 -4
  126. package/{spa → dist/spa}/src/renderers/CountryFlag.vue +2 -2
  127. package/dist/spa/src/router/index.ts +4 -8
  128. package/dist/spa/src/spa_types/core.ts +2 -0
  129. package/dist/spa/src/stores/core.ts +6 -2
  130. package/dist/spa/src/stores/filters.ts +15 -10
  131. package/dist/spa/src/stores/toast.ts +22 -6
  132. package/dist/spa/src/types/AdminForthConfig.ts +340 -55
  133. package/dist/spa/src/types/FrontendAPI.ts +52 -30
  134. package/dist/spa/src/utils.ts +59 -2
  135. package/dist/spa/src/views/CreateView.vue +15 -4
  136. package/dist/spa/src/views/EditView.vue +20 -7
  137. package/dist/spa/src/views/ListView.vue +132 -38
  138. package/dist/spa/src/views/LoginView.vue +50 -18
  139. package/dist/spa/src/views/ShowView.vue +25 -15
  140. package/dist/types/AdminForthConfig.d.ts +1619 -0
  141. package/dist/types/AdminForthConfig.d.ts.map +1 -0
  142. package/dist/types/AdminForthConfig.js +1 -0
  143. package/dist/types/AdminForthConfig.js.map +1 -0
  144. package/{types/FrontendAPI.ts → dist/types/FrontendAPI.d.ts} +27 -52
  145. package/dist/types/FrontendAPI.d.ts.map +1 -0
  146. package/dist/types/FrontendAPI.js +1 -0
  147. package/dist/types/FrontendAPI.js.map +1 -0
  148. package/package.json +16 -6
  149. package/auth.ts +0 -140
  150. package/basePlugin.ts +0 -70
  151. package/dataConnectors/baseConnector.ts +0 -216
  152. package/dataConnectors/clickhouse.ts +0 -338
  153. package/dataConnectors/mongo.ts +0 -202
  154. package/dataConnectors/postgres.ts +0 -306
  155. package/dataConnectors/sqlite.ts +0 -254
  156. package/index.ts +0 -428
  157. package/modules/codeInjector.ts +0 -736
  158. package/modules/configValidator.ts +0 -571
  159. package/modules/operationalResource.ts +0 -98
  160. package/modules/restApi.ts +0 -718
  161. package/modules/styleGenerator.ts +0 -55
  162. package/modules/styles.ts +0 -126
  163. package/modules/utils.ts +0 -472
  164. package/servers/express.ts +0 -259
  165. package/spa/.eslintrc.cjs +0 -14
  166. package/spa/README.md +0 -39
  167. package/spa/env.d.ts +0 -1
  168. package/spa/index.html +0 -23
  169. package/spa/package-lock.json +0 -4602
  170. package/spa/package.json +0 -51
  171. package/spa/postcss.config.js +0 -6
  172. package/spa/public/assets/favicon.png +0 -0
  173. package/spa/src/App.vue +0 -418
  174. package/spa/src/assets/base.css +0 -2
  175. package/spa/src/assets/logo.svg +0 -19
  176. package/spa/src/components/AcceptModal.vue +0 -45
  177. package/spa/src/components/Breadcrumbs.vue +0 -41
  178. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  179. package/spa/src/components/CustomDatePicker.vue +0 -176
  180. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  181. package/spa/src/components/CustomRangePicker.vue +0 -156
  182. package/spa/src/components/Dropdown.vue +0 -168
  183. package/spa/src/components/Filters.vue +0 -222
  184. package/spa/src/components/HelloWorld.vue +0 -17
  185. package/spa/src/components/MenuLink.vue +0 -27
  186. package/spa/src/components/ResourceForm.vue +0 -290
  187. package/spa/src/components/ResourceListTable.vue +0 -466
  188. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  189. package/spa/src/components/SkeleteLoader.vue +0 -23
  190. package/spa/src/components/Toast.vue +0 -78
  191. package/spa/src/components/ValueRenderer.vue +0 -114
  192. package/spa/src/components/icons/IconCalendar.vue +0 -5
  193. package/spa/src/components/icons/IconCommunity.vue +0 -7
  194. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  195. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  196. package/spa/src/components/icons/IconSupport.vue +0 -7
  197. package/spa/src/components/icons/IconTime.vue +0 -5
  198. package/spa/src/components/icons/IconTooling.vue +0 -19
  199. package/spa/src/composables/useFrontendApi.ts +0 -26
  200. package/spa/src/composables/useStores.ts +0 -131
  201. package/spa/src/index.scss +0 -31
  202. package/spa/src/main.ts +0 -18
  203. package/spa/src/router/index.ts +0 -59
  204. package/spa/src/spa_types/core.ts +0 -53
  205. package/spa/src/stores/core.ts +0 -148
  206. package/spa/src/stores/filters.ts +0 -27
  207. package/spa/src/stores/modal.ts +0 -48
  208. package/spa/src/stores/toast.ts +0 -31
  209. package/spa/src/stores/user.ts +0 -72
  210. package/spa/src/utils.ts +0 -160
  211. package/spa/src/views/CreateView.vue +0 -167
  212. package/spa/src/views/EditView.vue +0 -170
  213. package/spa/src/views/ListView.vue +0 -352
  214. package/spa/src/views/LoginView.vue +0 -192
  215. package/spa/src/views/ResourceParent.vue +0 -17
  216. package/spa/src/views/ShowView.vue +0 -186
  217. package/spa/tailwind.config.js +0 -17
  218. package/spa/tsconfig.app.json +0 -14
  219. package/spa/tsconfig.json +0 -11
  220. package/spa/tsconfig.node.json +0 -19
  221. package/spa/vite.config.ts +0 -56
  222. package/tsconfig.json +0 -112
  223. package/types/AdminForthConfig.ts +0 -1762
  224. /package/{spa → dist/spa}/src/components/ThreeDotsMenu.vue +0 -0
@@ -19,6 +19,7 @@ export interface FrontendAPIInterface {
19
19
  * @returns A promise that resolves when the user confirms the dialog
20
20
  */
21
21
  confirm(params:ConfirmParams ): Promise<void>;
22
+
22
23
  /**
23
24
  * Show an alert
24
25
  *
@@ -33,37 +34,58 @@ export interface FrontendAPIInterface {
33
34
  * @param params - The parameters of the alert
34
35
  */
35
36
  alert(params:AlertParams): void;
36
- /**
37
- * Add a filter to the list of filters.
38
- * Works only when user located on the list page.
39
- * Can be used to set filter from charts or other components in pageInjections.
40
- *
41
- * Example:
42
- *
43
- * ```ts
44
- * window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
45
- * ```
46
- *
47
- * @param filter - The filter to add
48
- */
49
- setListFilter(filter: any): void;
50
- /**
51
- * Update a filter in the list of filters
52
- *
53
- * Example:
54
- *
55
- * ```ts
56
- * window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
57
- * ```
58
- *
59
- * @param filter - The filter to update
60
- */
61
- updateListFilter(filter: any): void;
62
- /**
63
- * Clear all filters from the list
64
- */
65
- clearListFilters(): void;
66
37
 
38
+
39
+ list: {
40
+
41
+ /**
42
+ * Refresh the list
43
+ */
44
+ refresh(): void;
45
+
46
+ /**
47
+ * Close the three dots dropdown
48
+ */
49
+ closeThreeDotsDropdown(): void;
50
+
51
+ /**
52
+ * Close the user menu dropdown
53
+ */
54
+ closeUserMenuDropdown(): void;
55
+
56
+ /**
57
+ * Set a filter in the list
58
+ * Works only when user located on the list page.
59
+ * Can be used to set filter from charts or other components in pageInjections.
60
+ *
61
+ * Example:
62
+ *
63
+ * ```ts
64
+ * window.adminforth.list.setFilter({field: 'name', operator: 'ilike', value: 'john'})
65
+ * ```
66
+ *
67
+ * @param filter - The filter to set
68
+ */
69
+ setFilter(filter: any): void;
70
+
71
+ /**
72
+ * Update a filter in the list
73
+ *
74
+ * Example:
75
+ *
76
+ * ```ts
77
+ * window.adminforth.list.updateFilter({field: 'name', operator: 'ilike', value: 'john'})
78
+ * ```
79
+ *
80
+ * @param filter - The filter to update
81
+ */
82
+ updateFilter(filter: any): void;
83
+
84
+ /**
85
+ * Clear all filters from the list
86
+ */
87
+ clearFilters(): void;
88
+ }
67
89
  }
68
90
 
69
91
  export type ConfirmParams = {
@@ -1,10 +1,13 @@
1
1
  import { onMounted, ref, resolveComponent } from 'vue';
2
- import type { CoreConfig } from './spa_types/core';
2
+ import type { CoreConfig } from './spa_types/core';
3
+ import type { ValidationObject } from './types/AdminForthConfig';
4
+
3
5
 
4
6
  import router from "./router";
5
- import { useRouter } from 'vue-router';
6
7
  import { useCoreStore } from './stores/core';
7
8
  import { useUserStore } from './stores/user';
9
+ import { Dropdown } from 'flowbite';
10
+
8
11
 
9
12
  export async function callApi({path, method, body=undefined}: {
10
13
  path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
@@ -100,4 +103,58 @@ export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
100
103
  }
101
104
  }
102
105
 
106
+ export function initThreeDotsDropdown() {
107
+ const threeDotsDropdown: HTMLElement | null = document.querySelector('#listThreeDotsDropdown');
108
+ if (threeDotsDropdown) {
109
+ // this resource has three dots dropdown
110
+ const dd = new Dropdown(
111
+ threeDotsDropdown,
112
+ document.querySelector('[data-dropdown-toggle="listThreeDotsDropdown"]') as HTMLElement,
113
+ );
114
+ window.adminforth.list.closeThreeDotsDropdown = () => {
115
+ dd.hide();
116
+ }
117
+ }
118
+ }
119
+
120
+ export function applyRegexValidation(value: any, validation: ValidationObject[] | undefined) {
121
+
122
+ if ( validation?.length ) {
123
+ const validationArray = validation;
124
+ for (let i = 0; i < validationArray.length; i++) {
125
+ if (validationArray[i].regExp) {
126
+ let flags = '';
127
+ if (validationArray[i].caseSensitive) {
128
+ flags += 'i';
129
+ }
130
+ if (validationArray[i].multiline) {
131
+ flags += 'm';
132
+ }
133
+ if (validationArray[i].global) {
134
+ flags += 'g';
135
+ }
136
+
137
+ const regExp = new RegExp(validationArray[i].regExp, flags);
138
+ if (value === undefined || value === null) {
139
+ value = '';
140
+ }
141
+ let valueS = `${value}`;
142
+
143
+ if (!regExp.test(valueS)) {
144
+ return validationArray[i].message;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ export function currentQuery() {
152
+ return router.currentRoute.value.query;
153
+ }
103
154
 
155
+ export function setQuery(query: any) {
156
+ const currentQuery = { ...router.currentRoute.value.query, ...query };
157
+ router.replace({
158
+ query: currentQuery,
159
+ });
160
+ }
@@ -13,12 +13,13 @@
13
13
  <BreadcrumbsWithButtons>
14
14
  <!-- save and cancle -->
15
15
  <button @click="$router.back()"
16
- class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
16
+ class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
17
17
  >
18
18
  Cancel
19
19
  </button>
20
20
 
21
- <button @click="saveRecord"
21
+ <button
22
+ @click="saveRecord"
22
23
  class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
23
24
  :disabled="saving || (validating && !isValid)"
24
25
  >
@@ -29,6 +30,10 @@
29
30
  Save
30
31
  </button>
31
32
 
33
+ <ThreeDotsMenu
34
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.create?.threeDotsDropdownItems"
35
+ ></ThreeDotsMenu>
36
+
32
37
  </BreadcrumbsWithButtons>
33
38
 
34
39
  <component
@@ -73,18 +78,19 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
73
78
  import ResourceForm from '@/components/ResourceForm.vue';
74
79
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
75
80
  import { useCoreStore } from '@/stores/core';
76
- import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
81
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
77
82
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
78
83
  import { onMounted, ref, watch } from 'vue';
79
84
  import { useRoute, useRouter } from 'vue-router';
80
85
  import { computed } from 'vue';
81
86
  import { showErrorTost } from '@/composables/useFrontendApi';
87
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
82
88
 
83
89
 
84
90
  const isValid = ref(false);
85
91
  const validating = ref(false);
86
92
 
87
- const loading = ref(false);
93
+ const loading = ref(true);
88
94
  const saving = ref(false);
89
95
 
90
96
  const route = useRoute();
@@ -116,6 +122,7 @@ onMounted(async () => {
116
122
  });
117
123
  loading.value = false;
118
124
  checkAcessByAllowedActions(coreStore.resourceOptions.allowedActions,'create');
125
+ initThreeDotsDropdown();
119
126
  });
120
127
 
121
128
  async function saveRecord() {
@@ -149,6 +156,10 @@ async function saveRecord() {
149
156
  primaryKey: response.newRecordId
150
157
  }
151
158
  });
159
+ window.adminforth.alert({
160
+ message: 'Record created successfully',
161
+ variant: 'success'
162
+ });
152
163
  }
153
164
  }
154
165
 
@@ -12,19 +12,24 @@
12
12
  <BreadcrumbsWithButtons>
13
13
  <!-- save and cancle -->
14
14
  <button @click="$router.back()"
15
- class="flex items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 rounded-default focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
15
+ class="flex items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 rounded-default focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
16
16
  >
17
17
  Cancel
18
18
  </button>
19
19
 
20
- <button @click="saveRecord"
21
- class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
20
+ <button
21
+ @click="saveRecord"
22
+ class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
22
23
  :disabled="saving || (validating && !isValid)"
23
24
  >
24
25
  <IconFloppyDiskSolid class="w-4 h-4" />
25
26
  Save
26
27
  </button>
27
28
 
29
+ <ThreeDotsMenu
30
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.edit?.threeDotsDropdownItems"
31
+ ></ThreeDotsMenu>
32
+
28
33
  </BreadcrumbsWithButtons>
29
34
 
30
35
  <component
@@ -69,11 +74,13 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
69
74
  import ResourceForm from '@/components/ResourceForm.vue';
70
75
  import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
71
76
  import { useCoreStore } from '@/stores/core';
72
- import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from '@/utils';
77
+ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
73
78
  import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
74
79
  import { computed, onMounted, ref } from 'vue';
75
80
  import { useRoute, useRouter } from 'vue-router';
76
81
  import { showErrorTost } from '@/composables/useFrontendApi';
82
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
83
+
77
84
 
78
85
  const coreStore = useCoreStore();
79
86
 
@@ -83,7 +90,7 @@ const validating = ref(false);
83
90
  const route = useRoute();
84
91
  const router = useRouter();
85
92
 
86
- const loading = ref(false);
93
+ const loading = ref(true);
87
94
 
88
95
  const saving = ref(false);
89
96
 
@@ -107,12 +114,13 @@ const editableRecord = computed(() => {
107
114
  })
108
115
 
109
116
  onMounted(async () => {
110
-
111
117
  loading.value = true;
112
118
 
113
119
  await coreStore.fetchResourceFull({
114
120
  resourceId: route.params.resourceId
115
121
  });
122
+ initThreeDotsDropdown();
123
+
116
124
  await coreStore.fetchRecord({
117
125
  resourceId: route.params.resourceId,
118
126
  primaryKey: route.params.primaryKey,
@@ -130,7 +138,6 @@ async function saveRecord() {
130
138
  }
131
139
 
132
140
  saving.value = true;
133
-
134
141
  const updates = {};
135
142
  for (const key in record.value) {
136
143
  if (record.value[key] !== coreStore.record[key]) {
@@ -149,6 +156,12 @@ async function saveRecord() {
149
156
  });
150
157
  if (resp.error) {
151
158
  showErrorTost(resp.error);
159
+ } else {
160
+ window.adminforth.alert({
161
+ message: 'Record updated successfully',
162
+ variant: 'success',
163
+ timeout: 400000
164
+ });
152
165
  }
153
166
  saving.value = false;
154
167
  router.push({ name: 'resource-show', params: { resourceId: route.params.resourceId, primaryKey: coreStore.record[coreStore.primaryKey] } });
@@ -21,8 +21,7 @@
21
21
  @click="()=>{checkboxes = []}"
22
22
  v-if="checkboxes.length"
23
23
  data-tooltip-target="tooltip-remove-all"
24
- data-tooltip-placement="bottom"
25
- class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-darkListTable dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
24
+ class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-darkListTable dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
26
25
  >
27
26
  <IconBanOutline class="w-5 h-5 "/>
28
27
 
@@ -38,9 +37,9 @@
38
37
  v-for="(action,i) in coreStore.resource?.options?.bulkActions"
39
38
  :key="action.id"
40
39
  @click="startBulkAction(action.id)"
41
- class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
40
+ class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
42
41
  :class="{'bg-red-100 text-red-800 border-red-400 dark:bg-red-700 dark:text-red-400 dark:border-red-400':action.state==='danger', 'bg-green-100 text-green-800 border-green-400 dark:bg-green-700 dark:text-green-400 dark:border-green-400':action.state==='success',
43
- 'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
42
+ 'bg-lightPrimaryOpacity text-lightPrimary border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
44
43
  }"
45
44
  >
46
45
  <component
@@ -53,14 +52,14 @@
53
52
 
54
53
  <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
55
54
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
56
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
55
+ class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
57
56
  >
58
57
  <IconPlusOutline class="w-4 h-4 me-2"/>
59
58
  Create
60
59
  </RouterLink>
61
60
 
62
61
  <button
63
- class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
62
+ class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
64
63
  @click="()=>{filtersShow = !filtersShow}"
65
64
  v-if="coreStore.resource?.options?.allowedActions?.filter"
66
65
  >
@@ -72,6 +71,10 @@
72
71
  {{ filtersStore.filters.length }}
73
72
  </span>
74
73
  </button>
74
+
75
+ <ThreeDotsMenu
76
+ :threeDotsDropdownItems="coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems"
77
+ ></ThreeDotsMenu>
75
78
  </BreadcrumbsWithButtons>
76
79
 
77
80
  <component
@@ -85,6 +88,7 @@
85
88
  <ResourceListTable
86
89
  :resource="coreStore.resource"
87
90
  :rows="rows"
91
+ :page="page"
88
92
  @update:page="page = $event"
89
93
  @update:sort="sort = $event"
90
94
  @update:checkboxes="checkboxes = $event"
@@ -110,16 +114,14 @@
110
114
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
111
115
  import ResourceListTable from '@/components/ResourceListTable.vue';
112
116
  import { useCoreStore } from '@/stores/core';
113
- import { useModalStore } from '@/stores/modal';
114
117
  import { useFiltersStore } from '@/stores/filters';
115
- import { callAdminForthApi, getIcon } from '@/utils';
116
- import { initFlowbite } from 'flowbite';
118
+ import { callAdminForthApi, currentQuery, getIcon, setQuery } from '@/utils';
117
119
  import { computed, onMounted, ref, watch } from 'vue';
118
120
  import { useRoute } from 'vue-router';
119
121
  import { showErrorTost } from '@/composables/useFrontendApi'
120
-
121
- import ValueRenderer from '@/components/ValueRenderer.vue';
122
- import { getCustomComponent } from '@/utils';
122
+ import { getCustomComponent, initThreeDotsDropdown } from '@/utils';
123
+ import { initFlowbite } from 'flowbite';
124
+ import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
123
125
 
124
126
 
125
127
  import {
@@ -141,6 +143,11 @@ const page = ref(1);
141
143
  const columnsMinMax = ref({});
142
144
  const sort = ref([]);
143
145
 
146
+ watch(() => sort, async (to, from) => {
147
+ // in store sort might be needed for plugins
148
+ filtersStore.setSort(sort.value);
149
+ }, {deep: true});
150
+
144
151
  const rows = ref(null);
145
152
  const totalRows = ref(0);
146
153
  const checkboxes = ref([]);
@@ -151,22 +158,6 @@ const DEFAULT_PAGE_SIZE = 10;
151
158
  const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
152
159
 
153
160
 
154
- watch([page], async () => {
155
- await getList();
156
- });
157
-
158
- watch(()=>filtersStore.filters, async () => {
159
- page.value = 1;
160
- checkboxes.value = [];
161
-
162
- await getList();
163
- }, {deep: true});
164
-
165
- watch([sort], async () => {
166
- await getList();
167
- }, {deep: true});
168
-
169
-
170
161
  async function getList() {
171
162
  rows.value = null;
172
163
  const data = await callAdminForthApi({
@@ -176,7 +167,7 @@ async function getList() {
176
167
  source: 'list',
177
168
  resourceId: route.params.resourceId,
178
169
  limit: pageSize.value,
179
- offset: (page.value - 1) * pageSize.value,
170
+ offset: ((page.value || 1) - 1) * pageSize.value,
180
171
  filters: filtersStore.filters,
181
172
  sort: sort.value,
182
173
  }
@@ -192,9 +183,22 @@ async function getList() {
192
183
  return row;
193
184
  });
194
185
  totalRows.value = data.total;
186
+ await new Promise(resolve => setTimeout(resolve, 0));
187
+ initFlowbite(); // for tooltips in table
188
+
195
189
  }
196
190
 
197
191
  async function startBulkAction(actionId) {
192
+ const action = coreStore.resource.options.bulkActions.find(a => a.id === actionId);
193
+ if (action.confirm) {
194
+ const confirmed = await window.adminforth.confirm({
195
+ message: action.confirm,
196
+ });
197
+ if (!confirmed) {
198
+ return;
199
+ }
200
+ }
201
+
198
202
  const data = await callAdminForthApi({
199
203
  path: '/start_bulk_action',
200
204
  method: 'POST',
@@ -208,6 +212,14 @@ async function startBulkAction(actionId) {
208
212
  if (data?.ok) {
209
213
  checkboxes.value = [];
210
214
  await getList();
215
+
216
+ if (data.successMessage) {
217
+ window.adminforth.alert({
218
+ message: data.successMessage,
219
+ variant: 'success'
220
+ });
221
+ }
222
+
211
223
  }
212
224
  if (data?.error) {
213
225
  showErrorTost(data.error);
@@ -215,6 +227,18 @@ async function startBulkAction(actionId) {
215
227
  }
216
228
 
217
229
 
230
+ class SortQuerySerializer {
231
+ static serialize(sort) {
232
+ return sort.map(s => `${s.field}__${s.direction}`).join(',');
233
+ }
234
+ static deserialize(str) {
235
+ return str.split(',').map(s => {
236
+ const [field, direction] = s.split('__');
237
+ return { field, direction };
238
+ });
239
+ }
240
+ }
241
+
218
242
 
219
243
  async function init() {
220
244
 
@@ -222,7 +246,28 @@ async function init() {
222
246
  resourceId: route.params.resourceId
223
247
  });
224
248
 
225
- if (coreStore.resource.options?.defaultSort) {
249
+ initFlowbite();
250
+
251
+ // !!! clear filters should be in same tick with sort assignment so that watch can catch it as one change
252
+
253
+ // try to init filters from query params
254
+ const filters = Object.keys(route.query).filter(k => k.startsWith('filter__')).map(k => {
255
+ const [_, field, operator] = k.split('__');
256
+ return {
257
+ field,
258
+ operator,
259
+ value: JSON.parse(decodeURIComponent(route.query[k]))
260
+ }
261
+ });
262
+ if (filters.length) {
263
+ filtersStore.setFilters(filters);
264
+ } else {
265
+ filtersStore.clearFilters();
266
+ }
267
+
268
+ if (route.query.sort) {
269
+ sort.value = SortQuerySerializer.deserialize(route.query.sort);
270
+ } else if (coreStore.resource.options?.defaultSort) {
226
271
  sort.value = [{
227
272
  field: coreStore.resource.options.defaultSort.columnName,
228
273
  direction: coreStore.resource.options.defaultSort.direction
@@ -230,8 +275,12 @@ async function init() {
230
275
  } else {
231
276
  sort.value = [];
232
277
  }
278
+ // page init should be also in same tick
279
+ if (route.query.page) {
280
+ page.value = parseInt(route.query.page);
281
+ }
233
282
 
234
- await getList();
283
+ // await getList(); - Not needed here, watch will trigger it
235
284
  columnsMinMax.value = await callAdminForthApi({
236
285
  path: '/get_min_max_for_columns',
237
286
  method: 'POST',
@@ -241,18 +290,63 @@ async function init() {
241
290
  });
242
291
  }
243
292
 
293
+ watch([page, sort, () => filtersStore.filters], async () => {
294
+ console.log('🔄️ page/sort/filter change fired, page:', page.value);
295
+
296
+ await getList();
297
+ }, { deep: true });
298
+
299
+ window.adminforth.list.refresh = async () => {
300
+ await getList();
301
+ }
302
+
303
+ let initInProcess = false;
304
+
305
+ watch(() => filtersStore.filters, async (to, from) => {
306
+ if (initInProcess) {
307
+ return;
308
+ }
309
+ console.log('🔄️ filters changed', JSON.stringify(to))
310
+ page.value = 1;
311
+ checkboxes.value = [];
312
+ // update query param for each filter as filter_<column_name>=value
313
+ const query = {};
314
+ const currentQ = currentQuery();
315
+ filtersStore.filters.forEach(f => {
316
+ if (f.value) {
317
+ query[`filter__${f.field}__${f.operator}`] = encodeURIComponent(JSON.stringify(f.value));
318
+ }
319
+ });
320
+ // set every key in currentQ which starts with filter_ to undefined if it is not in query
321
+ Object.keys(currentQ).forEach(k => {
322
+ if (k.startsWith('filter_') && !query[k]) {
323
+ query[k] = undefined;
324
+ }
325
+ });
326
+ setQuery(query);
327
+ }, {deep: true});
328
+
244
329
  onMounted(async () => {
245
- initFlowbite();
246
-
330
+ initInProcess = true;
247
331
  await init();
332
+ initThreeDotsDropdown();
333
+ initInProcess = false;
334
+ });
248
335
 
336
+ watch([page], async () => {
337
+ setQuery({ page: page.value });
249
338
  });
250
339
 
251
- // on route param change
252
- watch(() => route.params.resourceId, async () => {
253
- filtersStore.setFilters([]);
254
- checkboxes.value = [];
255
- await init();
340
+
341
+
342
+
343
+ watch([sort], async () => {
344
+ if (!sort.value.length) {
345
+ setQuery({ sort: undefined });
346
+ return;
347
+ }
348
+ setQuery({ sort: SortQuerySerializer.serialize(sort.value) });
256
349
  });
257
350
 
351
+
258
352
  </script>