adminforth 2.4.0-next.26 → 2.4.0-next.261

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 (168) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/cli.js +10 -3
  3. package/commands/createApp/templates/api.ts.hbs +10 -0
  4. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  5. package/commands/createApp/templates/index.ts.hbs +12 -1
  6. package/commands/createApp/utils.js +25 -8
  7. package/commands/createCustomComponent/configLoader.js +17 -4
  8. package/commands/createCustomComponent/main.js +1 -0
  9. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  10. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  11. package/commands/createPlugin/templates/package.json.hbs +1 -1
  12. package/commands/generateModels.js +30 -22
  13. package/dist/auth.d.ts +9 -1
  14. package/dist/auth.d.ts.map +1 -1
  15. package/dist/auth.js +21 -2
  16. package/dist/auth.js.map +1 -1
  17. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  18. package/dist/dataConnectors/baseConnector.js +46 -15
  19. package/dist/dataConnectors/baseConnector.js.map +1 -1
  20. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  21. package/dist/dataConnectors/clickhouse.js +15 -0
  22. package/dist/dataConnectors/clickhouse.js.map +1 -1
  23. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  24. package/dist/dataConnectors/mongo.js +50 -15
  25. package/dist/dataConnectors/mongo.js.map +1 -1
  26. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  27. package/dist/dataConnectors/mysql.js +11 -0
  28. package/dist/dataConnectors/mysql.js.map +1 -1
  29. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  30. package/dist/dataConnectors/postgres.js +43 -14
  31. package/dist/dataConnectors/postgres.js.map +1 -1
  32. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  33. package/dist/dataConnectors/sqlite.js +11 -0
  34. package/dist/dataConnectors/sqlite.js.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +23 -9
  38. package/dist/index.js.map +1 -1
  39. package/dist/modules/codeInjector.d.ts +2 -0
  40. package/dist/modules/codeInjector.d.ts.map +1 -1
  41. package/dist/modules/codeInjector.js +50 -6
  42. package/dist/modules/codeInjector.js.map +1 -1
  43. package/dist/modules/configValidator.d.ts +6 -0
  44. package/dist/modules/configValidator.d.ts.map +1 -1
  45. package/dist/modules/configValidator.js +184 -19
  46. package/dist/modules/configValidator.js.map +1 -1
  47. package/dist/modules/restApi.d.ts.map +1 -1
  48. package/dist/modules/restApi.js +164 -26
  49. package/dist/modules/restApi.js.map +1 -1
  50. package/dist/modules/styles.d.ts +499 -13
  51. package/dist/modules/styles.d.ts.map +1 -1
  52. package/dist/modules/styles.js +555 -31
  53. package/dist/modules/styles.js.map +1 -1
  54. package/dist/modules/utils.d.ts +7 -15
  55. package/dist/modules/utils.d.ts.map +1 -1
  56. package/dist/modules/utils.js +45 -68
  57. package/dist/modules/utils.js.map +1 -1
  58. package/dist/servers/express.d.ts +5 -0
  59. package/dist/servers/express.d.ts.map +1 -1
  60. package/dist/servers/express.js +40 -1
  61. package/dist/servers/express.js.map +1 -1
  62. package/dist/spa/index.html +1 -1
  63. package/dist/spa/package-lock.json +5 -4
  64. package/dist/spa/package.json +1 -1
  65. package/dist/spa/src/App.vue +58 -173
  66. package/dist/spa/src/adminforth.ts +42 -18
  67. package/dist/spa/src/afcl/BarChart.vue +2 -2
  68. package/dist/spa/src/afcl/Button.vue +6 -6
  69. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  70. package/dist/spa/src/afcl/Card.vue +25 -0
  71. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  72. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  73. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  74. package/dist/spa/src/afcl/Dialog.vue +47 -27
  75. package/dist/spa/src/afcl/Dropzone.vue +12 -12
  76. package/dist/spa/src/afcl/Input.vue +5 -5
  77. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  78. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  79. package/dist/spa/src/afcl/PieChart.vue +5 -5
  80. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  81. package/dist/spa/src/afcl/Select.vue +68 -34
  82. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  83. package/dist/spa/src/afcl/Table.vue +213 -74
  84. package/dist/spa/src/afcl/Textarea.vue +31 -0
  85. package/dist/spa/src/afcl/Toggle.vue +32 -0
  86. package/dist/spa/src/afcl/Tooltip.vue +26 -18
  87. package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
  88. package/dist/spa/src/afcl/index.ts +6 -3
  89. package/dist/spa/src/components/AcceptModal.vue +48 -14
  90. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  91. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  92. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  93. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  94. package/dist/spa/src/components/CustomRangePicker.vue +37 -8
  95. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  96. package/dist/spa/src/components/Filters.vue +85 -39
  97. package/dist/spa/src/components/GroupsTable.vue +9 -8
  98. package/dist/spa/src/components/MenuLink.vue +90 -23
  99. package/dist/spa/src/components/ResourceForm.vue +94 -51
  100. package/dist/spa/src/components/ResourceListTable.vue +90 -80
  101. package/dist/spa/src/components/ResourceListTableVirtual.vue +86 -76
  102. package/dist/spa/src/components/ShowTable.vue +21 -15
  103. package/dist/spa/src/components/Sidebar.vue +470 -0
  104. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  105. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  106. package/dist/spa/src/components/ThreeDotsMenu.vue +73 -14
  107. package/dist/spa/src/components/Toast.vue +27 -9
  108. package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
  109. package/dist/spa/src/components/ValueRenderer.vue +43 -16
  110. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  111. package/dist/spa/src/i18n.ts +1 -1
  112. package/dist/spa/src/renderers/CompactField.vue +1 -1
  113. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  114. package/dist/spa/src/router/index.ts +8 -0
  115. package/dist/spa/src/shims-vue.d.ts +5 -0
  116. package/dist/spa/src/spa_types/core.ts +13 -1
  117. package/dist/spa/src/stores/core.ts +13 -1
  118. package/dist/spa/src/stores/filters.ts +29 -2
  119. package/dist/spa/src/stores/modal.ts +6 -1
  120. package/dist/spa/src/stores/toast.ts +22 -3
  121. package/dist/spa/src/types/Back.ts +158 -22
  122. package/dist/spa/src/types/Common.ts +81 -32
  123. package/dist/spa/src/types/FrontendAPI.ts +31 -5
  124. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  125. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -2
  126. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  127. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  128. package/dist/spa/src/types/adapters/index.ts +8 -0
  129. package/dist/spa/src/utils.ts +279 -9
  130. package/dist/spa/src/views/CreateView.vue +18 -19
  131. package/dist/spa/src/views/EditView.vue +25 -19
  132. package/dist/spa/src/views/ListView.vue +144 -87
  133. package/dist/spa/src/views/LoginView.vue +26 -35
  134. package/dist/spa/src/views/ResourceParent.vue +2 -2
  135. package/dist/spa/src/views/SettingsView.vue +121 -0
  136. package/dist/spa/src/views/ShowView.vue +59 -39
  137. package/dist/spa/src/websocket.ts +6 -1
  138. package/dist/spa/tsconfig.app.json +1 -1
  139. package/dist/spa/vite.config.ts +45 -2
  140. package/dist/types/Back.d.ts +134 -14
  141. package/dist/types/Back.d.ts.map +1 -1
  142. package/dist/types/Back.js +15 -0
  143. package/dist/types/Back.js.map +1 -1
  144. package/dist/types/Common.d.ts +96 -29
  145. package/dist/types/Common.d.ts.map +1 -1
  146. package/dist/types/Common.js.map +1 -1
  147. package/dist/types/FrontendAPI.d.ts +31 -3
  148. package/dist/types/FrontendAPI.d.ts.map +1 -1
  149. package/dist/types/FrontendAPI.js.map +1 -1
  150. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  151. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  152. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  153. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  154. package/dist/types/adapters/EmailAdapter.d.ts +1 -1
  155. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  156. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  157. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  158. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  159. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  160. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  161. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  162. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  163. package/dist/types/adapters/index.d.ts +9 -0
  164. package/dist/types/adapters/index.d.ts.map +1 -0
  165. package/dist/types/adapters/index.js +2 -0
  166. package/dist/types/adapters/index.js.map +1 -0
  167. package/package.json +4 -2
  168. package/dist/spa/src/types/adapters/index.js +0 -5
@@ -6,27 +6,26 @@
6
6
  <!-- skelet loader -->
7
7
  <div role="status" v-if="!resource || !resource.columns"
8
8
  class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
9
-
10
9
  <div role="status" class="max-w-sm animate-pulse">
11
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
10
+ <div class="h-2 bg-lightListSkeletLoader rounded-full dark:bg-darkListSkeletLoader max-w-[360px]"></div>
12
11
  </div>
13
12
  </div>
14
- <table v-else class=" w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
13
+ <table v-else class=" w-full text-sm text-left rtl:text-right text-lightListTableText dark:text-darkListTableText rounded-default">
15
14
 
16
15
  <tbody>
17
16
  <!-- table header -->
18
- <tr class="t-header sticky z-10 top-0 text-xs bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-gray-400">
19
- <td scope="col" class="p-4">
20
- <div class="flex items-center">
21
- <input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
22
- :disabled="!rows || !rows.length"
23
- class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded
24
- focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
25
- <label for="checkbox-all-search" class="sr-only">{{ $t('checkbox') }}</label>
26
- </div>
17
+ <tr class="t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
18
+ <td scope="col" class="p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
19
+ <Checkbox
20
+ :modelValue="allFromThisPageChecked"
21
+ :disabled="!rows || !rows.length"
22
+ @update:modelValue="selectAll"
23
+ >
24
+ <span class="sr-only">{{ $t('checkbox') }}</span>
25
+ </Checkbox>
27
26
  </td>
28
27
 
29
- <td v-for="c in columnsListed" ref="headerRefs" scope="col" class="px-2 md:px-3 lg:px-6 py-3">
28
+ <td v-for="c in columnsListed" ref="headerRefs" scope="col" class="px-2 md:px-3 lg:px-6 py-3" :class="{'sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading': c.listSticky}">
30
29
 
31
30
  <div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
32
31
  class="flex items-center " :class="{'cursor-pointer':c.sortable}">
@@ -48,8 +47,8 @@
48
47
  </div>
49
48
  <span
50
49
  class="bg-red-100 text-red-800 text-xs font-medium me-1 px-1 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
51
- v-if="sort.findIndex((s) => s.field === c.name) !== -1 && sort?.length > 1">
52
- {{ sort.findIndex((s) => s.field === c.name) + 1 }}
50
+ v-if="sort.findIndex((s: any) => s.field === c.name) !== -1 && sort?.length > 1">
51
+ {{ sort.findIndex((s: any) => s.field === c.name) + 1 }}
53
52
  </span>
54
53
 
55
54
  </div>
@@ -65,7 +64,7 @@
65
64
  <!-- table header end -->
66
65
  <SkeleteLoader
67
66
  v-if="!rows"
68
- :columns="resource?.columns.filter(c => c.showIn.list).length + 2"
67
+ :columns="resource?.columns.filter((c: AdminForthResourceColumnInputCommon) => c.showIn?.list).length + 2"
69
68
  :rows="rowHeights.length || 3"
70
69
  :row-heights="rowHeights"
71
70
  :column-widths="columnWidths"
@@ -91,23 +90,21 @@
91
90
 
92
91
  :class="{'border-b': rowI !== rows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
93
92
  >
94
- <td class="w-4 p-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
95
- <div class="flex items center ">
96
- <input
97
- @click="(e)=>{e.stopPropagation()}"
98
- id="checkbox-table-search-1"
99
- type="checkbox"
100
- :checked="checkboxesInternal.includes(row._primaryKeyValue)"
101
- @change="(e)=>{addToCheckedValues(row._primaryKeyValue)}"
102
- class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer">
103
- <label for="checkbox-table-search-1" class="sr-only">{{ $t('checkbox') }}</label>
104
- </div>
105
- </td>
106
- <td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4">
93
+ <td class="w-4 p-4 cursor-default sticky-column bg-lightListTable dark:bg-darkListTable" @click="(e)=>e.stopPropagation()">
94
+ <Checkbox
95
+ :model-value="checkboxesInternal.includes(row._primaryKeyValue)"
96
+ @change="(e: any)=>{addToCheckedValues(row._primaryKeyValue)}"
97
+ @click="(e: any)=>e.stopPropagation()"
98
+ >
99
+ <span class="sr-only">{{ $t('checkbox') }}</span>
100
+ </Checkbox>
101
+ </td>
102
+
103
+ <td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4" :class="{'sticky-column bg-lightListTable dark:bg-darkListTable': c.listSticky}">
107
104
  <!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
108
105
  <component
109
- :is="c?.components?.list ? getCustomComponent(c.components.list) : ValueRenderer"
110
- :meta="c?.components?.list?.meta"
106
+ :is="c?.components?.list ? getCustomComponent(typeof c.components.list === 'string' ? { file: c.components.list } : c.components.list) : ValueRenderer"
107
+ :meta="typeof c?.components?.list === 'object' ? c.components.list.meta : undefined"
111
108
  :column="c"
112
109
  :record="row"
113
110
  :adminUser="coreStore.adminUser"
@@ -118,7 +115,7 @@
118
115
  <div class="flex text-lightPrimary dark:text-darkPrimary items-center">
119
116
  <Tooltip>
120
117
  <RouterLink
121
- v-if="resource.options?.allowedActions.show"
118
+ v-if="resource.options?.allowedActions?.show"
122
119
  :to="{
123
120
  name: 'resource-show',
124
121
  params: {
@@ -138,7 +135,7 @@
138
135
 
139
136
  <Tooltip>
140
137
  <RouterLink
141
- v-if="resource.options?.allowedActions.edit"
138
+ v-if="resource.options?.allowedActions?.edit"
142
139
  :to="{
143
140
  name: 'resource-edit',
144
141
  params: {
@@ -156,7 +153,7 @@
156
153
 
157
154
  <Tooltip>
158
155
  <button
159
- v-if="resource.options?.allowedActions.delete"
156
+ v-if="resource.options?.allowedActions?.delete"
160
157
  @click="deleteRecord(row)"
161
158
  >
162
159
  <IconTrashBinSolid class="af-delete-icon w-5 h-5 me-2"/>
@@ -175,6 +172,7 @@
175
172
  :resource="coreStore.resource"
176
173
  :adminUser="coreStore.adminUser"
177
174
  :record="row"
175
+ :updateRecords="()=>emits('update:records', true)"
178
176
  />
179
177
  </template>
180
178
 
@@ -199,14 +197,14 @@
199
197
  <!-- pagination
200
198
  totalRows in v-if is used to not hide page input during loading when user puts cursor into it and edit directly (rows gets null there during edit)
201
199
  -->
202
- <div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3"
203
- v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
204
- >
200
+ <div class="af-pagination-container flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
205
201
 
206
- <div class="inline-flex ">
202
+ <div class="af-pagination-buttons-container inline-flex "
203
+ v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
204
+ >
207
205
  <!-- Buttons -->
208
206
  <button
209
- class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s 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 disabled:opacity-50"
207
+ class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 rounded-s border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
210
208
  @click="page--; pageInput = page.toString();" :disabled="page <= 1">
211
209
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
212
210
  viewBox="0 0 14 10">
@@ -218,14 +216,14 @@
218
216
  </span>
219
217
  </button>
220
218
  <button
221
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 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 disabled:opacity-50"
219
+ class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
222
220
  @click="page = 1; pageInput = page.toString();" :disabled="page <= 1">
223
221
  <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
224
222
  1
225
223
  </button>
226
224
  <div
227
225
  contenteditable="true"
228
- class="min-w-10 outline-none inline-block w-auto min-w-10 py-1.5 px-3 text-sm text-center text-gray-700 border border-gray-300 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800 z-10"
226
+ class="af-pagination-input min-w-10 outline-none inline-block w-auto py-1.5 px-3 text-sm text-center text-lightListTablePaginationCurrentPageText border border-lightListTablePaginationBorder dark:border-darkListTablePaginationBorder dark:text-darkListTablePaginationCurrentPageText dark:bg-darkListTablePaginationBackgoround z-10"
229
227
  @keydown="onPageKeydown($event)"
230
228
  @input="onPageInput($event)"
231
229
  @blur="validatePageInput()"
@@ -234,14 +232,14 @@
234
232
  </div>
235
233
 
236
234
  <button
237
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 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 disabled:opacity-50"
235
+ class="af-pagination-last-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-white dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
238
236
  @click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
239
237
  {{ totalPages }}
240
238
 
241
239
  <!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
242
240
  </button>
243
241
  <button
244
- class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e 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 disabled:opacity-50"
242
+ class="af-pagination-next-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 rounded-e border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-white dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
245
243
  @click="page++; pageInput = page.toString();" :disabled="page >= totalPages">
246
244
  <span class="hidden sm:inline">{{ $t('Next') }}</span>
247
245
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
@@ -253,11 +251,11 @@
253
251
  </div>
254
252
 
255
253
  <!-- Help text -->
256
- <span class="text-sm text-gray-700 dark:text-gray-400">
257
- <span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">{{ $t('Wrong Page') }} </span>
258
- <template v-else>
254
+ <span class="text-sm text-lightListTablePaginationHelpText dark:text-darkListTablePaginationHelpText">
255
+ <span v-if="((((page || 1) - 1) * pageSize + 1 > totalRows) && totalRows > 0)">{{ $t('Wrong Page') }} </span>
256
+ <template v-else-if="resource && totalRows > 0">
259
257
 
260
- <span class="hidden sm:inline">
258
+ <span class="af-pagination-info hidden sm:inline">
261
259
  <i18n-t keypath="Showing {from} to {to} of {total} Entries" tag="p" >
262
260
  <template v-slot:from>
263
261
  <strong>{{ from }}</strong>
@@ -307,12 +305,13 @@ import {
307
305
  import {
308
306
  IconEyeSolid,
309
307
  IconPenSolid,
310
- IconTrashBinSolid
308
+ IconTrashBinSolid,
311
309
  } from '@iconify-prerendered/vue-flowbite';
312
310
  import router from '@/router';
313
311
  import { Tooltip } from '@/afcl';
314
- import type { AdminForthResourceCommon } from '@/types/Common';
312
+ import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon } from '@/types/Common';
315
313
  import adminforth from '@/adminforth';
314
+ import Checkbox from '@/afcl/Checkbox.vue';
316
315
 
317
316
  const coreStore = useCoreStore();
318
317
  const { t } = useI18n();
@@ -341,7 +340,7 @@ const emits = defineEmits([
341
340
  const checkboxesInternal: Ref<any[]> = ref([]);
342
341
  const pageInput = ref('1');
343
342
  const page = ref(1);
344
- const sort = ref([]);
343
+ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
345
344
 
346
345
 
347
346
  const from = computed(() => ((page.value || 1) - 1) * props.pageSize + 1);
@@ -350,11 +349,11 @@ const to = computed(() => Math.min((page.value || 1) * props.pageSize, props.tot
350
349
  watch(() => page.value, (newPage) => {
351
350
  emits('update:page', newPage);
352
351
  });
353
- async function onPageKeydown(event) {
352
+ async function onPageKeydown(event: any) {
354
353
  // page input should accept only numbers, arrow keys and backspace
355
354
  if (['Enter', 'Space'].includes(event.code) ||
356
355
  (!['Backspace', 'ArrowRight', 'ArrowLeft'].includes(event.code)
357
- && isNaN(String.fromCharCode(event.keyCode)))) {
356
+ && isNaN(Number(String.fromCharCode(event.keyCode || 0))))) {
358
357
  event.preventDefault();
359
358
  if (event.code === 'Enter') {
360
359
  validatePageInput();
@@ -375,7 +374,7 @@ watch(() => props.checkboxes, (newCheckboxes) => {
375
374
  checkboxesInternal.value = newCheckboxes;
376
375
  });
377
376
 
378
- watch(() => props.sort, (newSort) => {
377
+ watch(() => props.sort, (newSort: any) => {
379
378
  sort.value = newSort;
380
379
  });
381
380
 
@@ -386,17 +385,17 @@ watch(() => props.page, (newPage) => {
386
385
  page.value = newPage;
387
386
  });
388
387
 
389
- const rowRefs = useTemplateRef('rowRefs');
390
- const headerRefs = useTemplateRef('headerRefs');
391
- const rowHeights = ref([]);
392
- const columnWidths = ref([]);
388
+ const rowRefs = useTemplateRef<HTMLElement[]>('rowRefs');
389
+ const headerRefs = useTemplateRef<HTMLElement[]>('headerRefs');
390
+ const rowHeights = ref<number[]>([]);
391
+ const columnWidths = ref<number[]>([]);
393
392
  watch(() => props.rows, (newRows) => {
394
393
  // rows are set to null when new records are loading
395
- rowHeights.value = newRows || !rowRefs.value ? [] : rowRefs.value.map((el) => el.offsetHeight);
396
- columnWidths.value = newRows || !headerRefs.value ? [] : [48, ...headerRefs.value.map((el) => el.offsetWidth)];
394
+ rowHeights.value = newRows || !rowRefs.value ? [] : rowRefs.value.map((el: HTMLElement) => el.offsetHeight);
395
+ columnWidths.value = newRows || !headerRefs.value ? [] : [48, ...headerRefs.value.map((el: HTMLElement) => el.offsetWidth)];
397
396
  });
398
397
 
399
- function addToCheckedValues(id) {
398
+ function addToCheckedValues(id: string) {
400
399
  if (checkboxesInternal.value.includes(id)) {
401
400
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
402
401
  } else {
@@ -405,17 +404,17 @@ function addToCheckedValues(id) {
405
404
  checkboxesInternal.value = [ ...checkboxesInternal.value ]
406
405
  }
407
406
 
408
- const columnsListed = computed(() => props.resource?.columns?.filter(c => c.showIn.list));
407
+ const columnsListed = computed(() => props.resource?.columns?.filter((c: AdminForthResourceColumnCommon) => c.showIn?.list));
409
408
 
410
- async function selectAll(value) {
409
+ async function selectAll() {
411
410
  if (!allFromThisPageChecked.value) {
412
- props.rows.forEach((r) => {
411
+ props.rows?.forEach((r) => {
413
412
  if (!checkboxesInternal.value.includes(r._primaryKeyValue)) {
414
413
  checkboxesInternal.value.push(r._primaryKeyValue)
415
414
  }
416
415
  });
417
416
  } else {
418
- props.rows.forEach((r) => {
417
+ props.rows?.forEach((r) => {
419
418
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r._primaryKeyValue);
420
419
  });
421
420
  }
@@ -428,15 +427,15 @@ const allFromThisPageChecked = computed(() => {
428
427
  if (!props.rows || !props.rows.length) return false;
429
428
  return props.rows.every((r) => checkboxesInternal.value.includes(r._primaryKeyValue));
430
429
  });
431
- const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
432
- const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
430
+ const ascArr = computed(() => sort.value.filter((s:any) => s.direction === 'asc').map((s: any) => s.field));
431
+ const descArr = computed(() => sort.value.filter((s: any) => s.direction === 'desc').map((s: any) => s.field));
433
432
 
434
433
 
435
- function onSortButtonClick(event, field) {
434
+ function onSortButtonClick(event: any, field: string) {
436
435
  // if ctrl key is pressed, add to sort otherwise sort by this field
437
436
  // in any case if field is already in sort, toggle direction
438
437
 
439
- const sortIndex = sort.value.findIndex((s) => s.field === field);
438
+ const sortIndex = sort.value.findIndex((s: any) => s.field === field);
440
439
  if (sortIndex === -1) {
441
440
  // field is not in sort, add it
442
441
  if (event.ctrlKey) {
@@ -447,9 +446,9 @@ function onSortButtonClick(event, field) {
447
446
  } else {
448
447
  const sortField = sort.value[sortIndex];
449
448
  if (sortField.direction === 'asc') {
450
- sort.value = sort.value.map((s) => s.field === field ? {field, direction: 'desc'} : s);
449
+ sort.value = sort.value.map((s: any) => s.field === field ? {field, direction: 'desc'} : s);
451
450
  } else {
452
- sort.value = sort.value.filter((s) => s.field !== field);
451
+ sort.value = sort.value.filter((s: any) => s.field !== field);
453
452
  }
454
453
  }
455
454
  }
@@ -457,11 +456,11 @@ function onSortButtonClick(event, field) {
457
456
 
458
457
  const clickTarget = ref(null);
459
458
 
460
- async function onClick(e,row) {
459
+ async function onClick(e: any, row: any) {
461
460
  if(clickTarget.value === e.target) return;
462
461
  clickTarget.value = e.target;
463
462
  await new Promise((resolve) => setTimeout(resolve, 100));
464
- if (window.getSelection().toString()) return;
463
+ if (window.getSelection()?.toString()) return;
465
464
  else {
466
465
  if (row._clickUrl === null) {
467
466
  // user asked to nothing on click
@@ -476,7 +475,7 @@ async function onClick(e,row) {
476
475
  router.resolve({
477
476
  name: 'resource-show',
478
477
  params: {
479
- resourceId: props.resource.resourceId,
478
+ resourceId: props.resource?.resourceId,
480
479
  primaryKey: row._primaryKeyValue,
481
480
  },
482
481
  }).href,
@@ -494,7 +493,7 @@ async function onClick(e,row) {
494
493
  router.push({
495
494
  name: 'resource-show',
496
495
  params: {
497
- resourceId: props.resource.resourceId,
496
+ resourceId: props.resource?.resourceId,
498
497
  primaryKey: row._primaryKeyValue,
499
498
  },
500
499
  });
@@ -503,7 +502,7 @@ async function onClick(e,row) {
503
502
  }
504
503
  }
505
504
 
506
- async function deleteRecord(row) {
505
+ async function deleteRecord(row: any) {
507
506
  const data = await adminforth.confirm({
508
507
  message: t('Are you sure you want to delete this item?'),
509
508
  yes: t('Delete'),
@@ -515,7 +514,7 @@ async function deleteRecord(row) {
515
514
  path: '/delete_record',
516
515
  method: 'POST',
517
516
  body: {
518
- resourceId: props.resource.resourceId,
517
+ resourceId: props.resource?.resourceId,
519
518
  primaryKey: row._primaryKeyValue,
520
519
  }
521
520
  });
@@ -533,16 +532,16 @@ async function deleteRecord(row) {
533
532
  }
534
533
  }
535
534
 
536
- const actionLoadingStates = ref({});
535
+ const actionLoadingStates = ref<Record<string | number, boolean>>({});
537
536
 
538
- async function startCustomAction(actionId, row) {
537
+ async function startCustomAction(actionId: string, row: any) {
539
538
  actionLoadingStates.value[actionId] = true;
540
539
 
541
540
  const data = await callAdminForthApi({
542
541
  path: '/start_custom_action',
543
542
  method: 'POST',
544
543
  body: {
545
- resourceId: props.resource.resourceId,
544
+ resourceId: props.resource?.resourceId,
546
545
  actionId: actionId,
547
546
  recordId: row._primaryKeyValue
548
547
  }
@@ -580,7 +579,7 @@ async function startCustomAction(actionId, row) {
580
579
  }
581
580
  }
582
581
 
583
- function onPageInput(event) {
582
+ function onPageInput(event: any) {
584
583
  pageInput.value = event.target.innerText;
585
584
  }
586
585
 
@@ -600,4 +599,15 @@ input[type="checkbox"][disabled] {
600
599
  input[type="checkbox"]:not([disabled]) {
601
600
  @apply cursor-pointer;
602
601
  }
602
+ td.sticky-column {
603
+ @apply sticky left-0 z-10;
604
+ &:not(:first-child) {
605
+ @apply left-[56px];
606
+ }
607
+ }
608
+ tr:not(:first-child):hover {
609
+ td.sticky-column {
610
+ @apply bg-lightListTableRowHover dark:bg-darkListTableRowHover;
611
+ }
612
+ }
603
613
  </style>