adminforth 2.4.0-next.33 → 2.4.0-next.331

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 (177) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/createApp/templates/api.ts.hbs +10 -0
  3. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  4. package/commands/createApp/templates/index.ts.hbs +12 -1
  5. package/commands/createApp/templates/package.json.hbs +1 -1
  6. package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
  7. package/commands/createApp/templates/schema.prisma.hbs +0 -1
  8. package/commands/createApp/utils.js +10 -0
  9. package/commands/createCustomComponent/configLoader.js +17 -4
  10. package/commands/createCustomComponent/main.js +13 -7
  11. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  12. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
  13. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  14. package/commands/createPlugin/templates/package.json.hbs +1 -1
  15. package/commands/generateModels.js +30 -22
  16. package/dist/auth.d.ts +9 -1
  17. package/dist/auth.d.ts.map +1 -1
  18. package/dist/auth.js +21 -2
  19. package/dist/auth.js.map +1 -1
  20. package/dist/dataConnectors/baseConnector.d.ts +1 -1
  21. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  22. package/dist/dataConnectors/baseConnector.js +70 -18
  23. package/dist/dataConnectors/baseConnector.js.map +1 -1
  24. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  25. package/dist/dataConnectors/clickhouse.js +15 -0
  26. package/dist/dataConnectors/clickhouse.js.map +1 -1
  27. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  28. package/dist/dataConnectors/mongo.js +50 -15
  29. package/dist/dataConnectors/mongo.js.map +1 -1
  30. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  31. package/dist/dataConnectors/mysql.js +11 -0
  32. package/dist/dataConnectors/mysql.js.map +1 -1
  33. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  34. package/dist/dataConnectors/postgres.js +43 -14
  35. package/dist/dataConnectors/postgres.js.map +1 -1
  36. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  37. package/dist/dataConnectors/sqlite.js +11 -0
  38. package/dist/dataConnectors/sqlite.js.map +1 -1
  39. package/dist/index.d.ts +11 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +44 -21
  42. package/dist/index.js.map +1 -1
  43. package/dist/modules/codeInjector.d.ts +2 -0
  44. package/dist/modules/codeInjector.d.ts.map +1 -1
  45. package/dist/modules/codeInjector.js +62 -6
  46. package/dist/modules/codeInjector.js.map +1 -1
  47. package/dist/modules/configValidator.d.ts +6 -0
  48. package/dist/modules/configValidator.d.ts.map +1 -1
  49. package/dist/modules/configValidator.js +209 -25
  50. package/dist/modules/configValidator.js.map +1 -1
  51. package/dist/modules/restApi.d.ts +1 -1
  52. package/dist/modules/restApi.d.ts.map +1 -1
  53. package/dist/modules/restApi.js +199 -31
  54. package/dist/modules/restApi.js.map +1 -1
  55. package/dist/modules/styles.d.ts +499 -13
  56. package/dist/modules/styles.d.ts.map +1 -1
  57. package/dist/modules/styles.js +555 -31
  58. package/dist/modules/styles.js.map +1 -1
  59. package/dist/modules/utils.d.ts +7 -15
  60. package/dist/modules/utils.d.ts.map +1 -1
  61. package/dist/modules/utils.js +45 -68
  62. package/dist/modules/utils.js.map +1 -1
  63. package/dist/servers/express.d.ts +5 -0
  64. package/dist/servers/express.d.ts.map +1 -1
  65. package/dist/servers/express.js +40 -1
  66. package/dist/servers/express.js.map +1 -1
  67. package/dist/spa/index.html +1 -1
  68. package/dist/spa/package-lock.json +1208 -708
  69. package/dist/spa/package.json +34 -34
  70. package/dist/spa/src/App.vue +132 -174
  71. package/dist/spa/src/adminforth.ts +41 -17
  72. package/dist/spa/src/afcl/AreaChart.vue +0 -1
  73. package/dist/spa/src/afcl/BarChart.vue +2 -2
  74. package/dist/spa/src/afcl/Button.vue +3 -3
  75. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  76. package/dist/spa/src/afcl/Card.vue +25 -0
  77. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  78. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  79. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  80. package/dist/spa/src/afcl/Dialog.vue +47 -27
  81. package/dist/spa/src/afcl/Dropzone.vue +145 -48
  82. package/dist/spa/src/afcl/Input.vue +14 -6
  83. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  84. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  85. package/dist/spa/src/afcl/PieChart.vue +5 -5
  86. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  87. package/dist/spa/src/afcl/Select.vue +82 -34
  88. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  89. package/dist/spa/src/afcl/Table.vue +313 -75
  90. package/dist/spa/src/afcl/Textarea.vue +31 -0
  91. package/dist/spa/src/afcl/Toggle.vue +32 -0
  92. package/dist/spa/src/afcl/Tooltip.vue +28 -18
  93. package/dist/spa/src/afcl/VerticalTabs.vue +21 -7
  94. package/dist/spa/src/afcl/index.ts +6 -3
  95. package/dist/spa/src/components/AcceptModal.vue +48 -14
  96. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  97. package/dist/spa/src/components/CallActionWrapper.vue +15 -0
  98. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  99. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  100. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  101. package/dist/spa/src/components/CustomRangePicker.vue +37 -21
  102. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  103. package/dist/spa/src/components/Filters.vue +195 -132
  104. package/dist/spa/src/components/GroupsTable.vue +9 -8
  105. package/dist/spa/src/components/MenuLink.vue +95 -23
  106. package/dist/spa/src/components/ResourceForm.vue +99 -51
  107. package/dist/spa/src/components/ResourceListTable.vue +121 -95
  108. package/dist/spa/src/components/ResourceListTableVirtual.vue +119 -88
  109. package/dist/spa/src/components/ShowTable.vue +21 -15
  110. package/dist/spa/src/components/Sidebar.vue +472 -0
  111. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  112. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  113. package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
  114. package/dist/spa/src/components/Toast.vue +40 -29
  115. package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
  116. package/dist/spa/src/components/ValueRenderer.vue +44 -17
  117. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  118. package/dist/spa/src/i18n.ts +5 -3
  119. package/dist/spa/src/main.ts +1 -1
  120. package/dist/spa/src/renderers/CompactField.vue +1 -1
  121. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  122. package/dist/spa/src/router/index.ts +8 -0
  123. package/dist/spa/src/shims-vue.d.ts +5 -0
  124. package/dist/spa/src/spa_types/core.ts +13 -1
  125. package/dist/spa/src/stores/core.ts +15 -1
  126. package/dist/spa/src/stores/filters.ts +33 -2
  127. package/dist/spa/src/stores/modal.ts +6 -1
  128. package/dist/spa/src/stores/toast.ts +22 -3
  129. package/dist/spa/src/types/Back.ts +168 -23
  130. package/dist/spa/src/types/Common.ts +109 -32
  131. package/dist/spa/src/types/FrontendAPI.ts +32 -23
  132. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  133. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
  134. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  135. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  136. package/dist/spa/src/types/adapters/StorageAdapter.ts +4 -2
  137. package/dist/spa/src/types/adapters/index.ts +3 -0
  138. package/dist/spa/src/utils.ts +291 -11
  139. package/dist/spa/src/views/CreateView.vue +88 -22
  140. package/dist/spa/src/views/EditView.vue +55 -22
  141. package/dist/spa/src/views/ListView.vue +144 -87
  142. package/dist/spa/src/views/LoginView.vue +26 -35
  143. package/dist/spa/src/views/ResourceParent.vue +2 -2
  144. package/dist/spa/src/views/SettingsView.vue +121 -0
  145. package/dist/spa/src/views/ShowView.vue +83 -53
  146. package/dist/spa/src/websocket.ts +6 -1
  147. package/dist/spa/tsconfig.app.json +1 -1
  148. package/dist/spa/vite.config.ts +45 -2
  149. package/dist/types/Back.d.ts +151 -14
  150. package/dist/types/Back.d.ts.map +1 -1
  151. package/dist/types/Back.js +15 -0
  152. package/dist/types/Back.js.map +1 -1
  153. package/dist/types/Common.d.ts +123 -29
  154. package/dist/types/Common.d.ts.map +1 -1
  155. package/dist/types/Common.js.map +1 -1
  156. package/dist/types/FrontendAPI.d.ts +32 -18
  157. package/dist/types/FrontendAPI.d.ts.map +1 -1
  158. package/dist/types/FrontendAPI.js.map +1 -1
  159. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  160. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  161. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  162. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  163. package/dist/types/adapters/EmailAdapter.d.ts +2 -3
  164. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
  165. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  166. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  167. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  168. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  169. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  170. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  171. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  172. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  173. package/dist/types/adapters/StorageAdapter.d.ts +2 -0
  174. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -1
  175. package/dist/types/adapters/index.d.ts +3 -0
  176. package/dist/types/adapters/index.d.ts.map +1 -1
  177. package/package.json +4 -2
@@ -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="list-table-header-cell 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="list-table-header-cell 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"
@@ -87,27 +86,25 @@
87
86
  <tr @click="onClick($event,row)"
88
87
  v-else v-for="(row, rowI) in rows" :key="`row_${row._primaryKeyValue}`"
89
88
  ref="rowRefs"
90
- class="bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
89
+ class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
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,22 +172,43 @@
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
 
181
179
  <template v-if="resource.options?.actions">
182
- <Tooltip v-for="action in resource.options.actions.filter(a => a.showIn?.list)" :key="action.id">
183
- <button
184
- @click="startCustomAction(action.id, row)"
185
- >
186
- <component v-if="action.icon" :is="getIcon(action.icon)" class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"></component>
187
- </button>
188
- <template v-slot:tooltip>
180
+ <Tooltip
181
+ v-for="action in resource.options.actions.filter(a => a.showIn?.list)"
182
+ :key="action.id"
183
+ >
184
+ <component
185
+ :is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
186
+ :meta="action.customComponent?.meta"
187
+ :row="row"
188
+ :resource="resource"
189
+ :adminUser="adminUser"
190
+ @callAction="(payload? : Object) => startCustomAction(action.id, payload ?? row)"
191
+ >
192
+ <button
193
+ type="button"
194
+ :disabled="rowActionLoadingStates?.[action.id]"
195
+ @click.stop.prevent
196
+ >
197
+ <component
198
+ v-if="action.icon"
199
+ :is="getIcon(action.icon)"
200
+ class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"
201
+ />
202
+ </button>
203
+ </component>
204
+
205
+ <template #tooltip>
189
206
  {{ action.name }}
190
207
  </template>
191
208
  </Tooltip>
192
209
  </template>
193
210
  </div>
211
+
194
212
  </td>
195
213
  </tr>
196
214
  </tbody>
@@ -199,14 +217,14 @@
199
217
  <!-- pagination
200
218
  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
219
  -->
202
- <div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
220
+ <div class="af-pagination-container flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
203
221
 
204
- <div class="inline-flex "
222
+ <div class="af-pagination-buttons-container inline-flex "
205
223
  v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
206
224
  >
207
225
  <!-- Buttons -->
208
226
  <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"
227
+ 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
228
  @click="page--; pageInput = page.toString();" :disabled="page <= 1">
211
229
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
212
230
  viewBox="0 0 14 10">
@@ -218,30 +236,29 @@
218
236
  </span>
219
237
  </button>
220
238
  <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"
239
+ 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
240
  @click="page = 1; pageInput = page.toString();" :disabled="page <= 1">
223
241
  <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
224
242
  1
225
243
  </button>
226
- <div
227
- 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"
244
+ <input
245
+ type="text"
246
+ v-model="pageInput"
247
+ :style="{ width: `${Math.max(1, pageInput.length+4)}ch` }"
248
+ class="af-pagination-input min-w-10 outline-none inline-block 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
249
  @keydown="onPageKeydown($event)"
230
- @input="onPageInput($event)"
231
250
  @blur="validatePageInput()"
232
- >
233
- {{ pageInput }}
234
- </div>
251
+ />
235
252
 
236
253
  <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"
254
+ 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
255
  @click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
239
256
  {{ totalPages }}
240
257
 
241
258
  <!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
242
259
  </button>
243
260
  <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"
261
+ 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
262
  @click="page++; pageInput = page.toString();" :disabled="page >= totalPages">
246
263
  <span class="hidden sm:inline">{{ $t('Next') }}</span>
247
264
  <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 +270,11 @@
253
270
  </div>
254
271
 
255
272
  <!-- 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>
273
+ <span class="ml-4 text-sm text-lightListTablePaginationHelpText dark:text-darkListTablePaginationHelpText">
274
+ <span v-if="((((page || 1) - 1) * pageSize + 1 > totalRows) && totalRows > 0)">{{ $t('Wrong Page') }} </span>
275
+ <template v-else-if="resource && totalRows > 0">
259
276
 
260
- <span class="hidden sm:inline">
277
+ <span class="af-pagination-info hidden sm:inline">
261
278
  <i18n-t keypath="Showing {from} to {to} of {total} Entries" tag="p" >
262
279
  <template v-slot:from>
263
280
  <strong>{{ from }}</strong>
@@ -307,12 +324,13 @@ import {
307
324
  import {
308
325
  IconEyeSolid,
309
326
  IconPenSolid,
310
- IconTrashBinSolid
327
+ IconTrashBinSolid,
311
328
  } from '@iconify-prerendered/vue-flowbite';
312
329
  import router from '@/router';
313
330
  import { Tooltip } from '@/afcl';
314
- import type { AdminForthResourceCommon } from '@/types/Common';
331
+ import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon } from '@/types/Common';
315
332
  import adminforth from '@/adminforth';
333
+ import Checkbox from '@/afcl/Checkbox.vue';
316
334
 
317
335
  const coreStore = useCoreStore();
318
336
  const { t } = useI18n();
@@ -341,7 +359,7 @@ const emits = defineEmits([
341
359
  const checkboxesInternal: Ref<any[]> = ref([]);
342
360
  const pageInput = ref('1');
343
361
  const page = ref(1);
344
- const sort = ref([]);
362
+ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
345
363
 
346
364
 
347
365
  const from = computed(() => ((page.value || 1) - 1) * props.pageSize + 1);
@@ -350,11 +368,11 @@ const to = computed(() => Math.min((page.value || 1) * props.pageSize, props.tot
350
368
  watch(() => page.value, (newPage) => {
351
369
  emits('update:page', newPage);
352
370
  });
353
- async function onPageKeydown(event) {
371
+ async function onPageKeydown(event: any) {
354
372
  // page input should accept only numbers, arrow keys and backspace
355
373
  if (['Enter', 'Space'].includes(event.code) ||
356
374
  (!['Backspace', 'ArrowRight', 'ArrowLeft'].includes(event.code)
357
- && isNaN(String.fromCharCode(event.keyCode)))) {
375
+ && isNaN(Number(String.fromCharCode(event.keyCode || 0))))) {
358
376
  event.preventDefault();
359
377
  if (event.code === 'Enter') {
360
378
  validatePageInput();
@@ -375,7 +393,7 @@ watch(() => props.checkboxes, (newCheckboxes) => {
375
393
  checkboxesInternal.value = newCheckboxes;
376
394
  });
377
395
 
378
- watch(() => props.sort, (newSort) => {
396
+ watch(() => props.sort, (newSort: any) => {
379
397
  sort.value = newSort;
380
398
  });
381
399
 
@@ -386,17 +404,17 @@ watch(() => props.page, (newPage) => {
386
404
  page.value = newPage;
387
405
  });
388
406
 
389
- const rowRefs = useTemplateRef('rowRefs');
390
- const headerRefs = useTemplateRef('headerRefs');
391
- const rowHeights = ref([]);
392
- const columnWidths = ref([]);
407
+ const rowRefs = useTemplateRef<HTMLElement[]>('rowRefs');
408
+ const headerRefs = useTemplateRef<HTMLElement[]>('headerRefs');
409
+ const rowHeights = ref<number[]>([]);
410
+ const columnWidths = ref<number[]>([]);
393
411
  watch(() => props.rows, (newRows) => {
394
412
  // 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)];
413
+ rowHeights.value = newRows || !rowRefs.value ? [] : rowRefs.value.map((el: HTMLElement) => el.offsetHeight);
414
+ columnWidths.value = newRows || !headerRefs.value ? [] : [48, ...headerRefs.value.map((el: HTMLElement) => el.offsetWidth)];
397
415
  });
398
416
 
399
- function addToCheckedValues(id) {
417
+ function addToCheckedValues(id: string) {
400
418
  if (checkboxesInternal.value.includes(id)) {
401
419
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
402
420
  } else {
@@ -405,17 +423,17 @@ function addToCheckedValues(id) {
405
423
  checkboxesInternal.value = [ ...checkboxesInternal.value ]
406
424
  }
407
425
 
408
- const columnsListed = computed(() => props.resource?.columns?.filter(c => c.showIn.list));
426
+ const columnsListed = computed(() => props.resource?.columns?.filter((c: AdminForthResourceColumnCommon) => c.showIn?.list));
409
427
 
410
- async function selectAll(value) {
428
+ async function selectAll() {
411
429
  if (!allFromThisPageChecked.value) {
412
- props.rows.forEach((r) => {
430
+ props.rows?.forEach((r) => {
413
431
  if (!checkboxesInternal.value.includes(r._primaryKeyValue)) {
414
432
  checkboxesInternal.value.push(r._primaryKeyValue)
415
433
  }
416
434
  });
417
435
  } else {
418
- props.rows.forEach((r) => {
436
+ props.rows?.forEach((r) => {
419
437
  checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r._primaryKeyValue);
420
438
  });
421
439
  }
@@ -428,15 +446,15 @@ const allFromThisPageChecked = computed(() => {
428
446
  if (!props.rows || !props.rows.length) return false;
429
447
  return props.rows.every((r) => checkboxesInternal.value.includes(r._primaryKeyValue));
430
448
  });
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));
449
+ const ascArr = computed(() => sort.value.filter((s:any) => s.direction === 'asc').map((s: any) => s.field));
450
+ const descArr = computed(() => sort.value.filter((s: any) => s.direction === 'desc').map((s: any) => s.field));
433
451
 
434
452
 
435
- function onSortButtonClick(event, field) {
453
+ function onSortButtonClick(event: any, field: string) {
436
454
  // if ctrl key is pressed, add to sort otherwise sort by this field
437
455
  // in any case if field is already in sort, toggle direction
438
456
 
439
- const sortIndex = sort.value.findIndex((s) => s.field === field);
457
+ const sortIndex = sort.value.findIndex((s: any) => s.field === field);
440
458
  if (sortIndex === -1) {
441
459
  // field is not in sort, add it
442
460
  if (event.ctrlKey) {
@@ -447,9 +465,9 @@ function onSortButtonClick(event, field) {
447
465
  } else {
448
466
  const sortField = sort.value[sortIndex];
449
467
  if (sortField.direction === 'asc') {
450
- sort.value = sort.value.map((s) => s.field === field ? {field, direction: 'desc'} : s);
468
+ sort.value = sort.value.map((s: any) => s.field === field ? {field, direction: 'desc'} : s);
451
469
  } else {
452
- sort.value = sort.value.filter((s) => s.field !== field);
470
+ sort.value = sort.value.filter((s: any) => s.field !== field);
453
471
  }
454
472
  }
455
473
  }
@@ -457,11 +475,11 @@ function onSortButtonClick(event, field) {
457
475
 
458
476
  const clickTarget = ref(null);
459
477
 
460
- async function onClick(e,row) {
478
+ async function onClick(e: any, row: any) {
461
479
  if(clickTarget.value === e.target) return;
462
480
  clickTarget.value = e.target;
463
481
  await new Promise((resolve) => setTimeout(resolve, 100));
464
- if (window.getSelection().toString()) return;
482
+ if (window.getSelection()?.toString()) return;
465
483
  else {
466
484
  if (row._clickUrl === null) {
467
485
  // user asked to nothing on click
@@ -476,7 +494,7 @@ async function onClick(e,row) {
476
494
  router.resolve({
477
495
  name: 'resource-show',
478
496
  params: {
479
- resourceId: props.resource.resourceId,
497
+ resourceId: props.resource?.resourceId,
480
498
  primaryKey: row._primaryKeyValue,
481
499
  },
482
500
  }).href,
@@ -494,7 +512,7 @@ async function onClick(e,row) {
494
512
  router.push({
495
513
  name: 'resource-show',
496
514
  params: {
497
- resourceId: props.resource.resourceId,
515
+ resourceId: props.resource?.resourceId,
498
516
  primaryKey: row._primaryKeyValue,
499
517
  },
500
518
  });
@@ -503,7 +521,7 @@ async function onClick(e,row) {
503
521
  }
504
522
  }
505
523
 
506
- async function deleteRecord(row) {
524
+ async function deleteRecord(row: any) {
507
525
  const data = await adminforth.confirm({
508
526
  message: t('Are you sure you want to delete this item?'),
509
527
  yes: t('Delete'),
@@ -515,7 +533,7 @@ async function deleteRecord(row) {
515
533
  path: '/delete_record',
516
534
  method: 'POST',
517
535
  body: {
518
- resourceId: props.resource.resourceId,
536
+ resourceId: props.resource?.resourceId,
519
537
  primaryKey: row._primaryKeyValue,
520
538
  }
521
539
  });
@@ -533,16 +551,16 @@ async function deleteRecord(row) {
533
551
  }
534
552
  }
535
553
 
536
- const actionLoadingStates = ref({});
554
+ const actionLoadingStates = ref<Record<string | number, boolean>>({});
537
555
 
538
- async function startCustomAction(actionId, row) {
556
+ async function startCustomAction(actionId: string, row: any) {
539
557
  actionLoadingStates.value[actionId] = true;
540
558
 
541
559
  const data = await callAdminForthApi({
542
560
  path: '/start_custom_action',
543
561
  method: 'POST',
544
562
  body: {
545
- resourceId: props.resource.resourceId,
563
+ resourceId: props.resource?.resourceId,
546
564
  actionId: actionId,
547
565
  recordId: row._primaryKeyValue
548
566
  }
@@ -580,10 +598,6 @@ async function startCustomAction(actionId, row) {
580
598
  }
581
599
  }
582
600
 
583
- function onPageInput(event) {
584
- pageInput.value = event.target.innerText;
585
- }
586
-
587
601
  function validatePageInput() {
588
602
  const newPage = parseInt(pageInput.value) || 1;
589
603
  const validPage = Math.max(1, Math.min(newPage, totalPages.value));
@@ -600,4 +614,16 @@ input[type="checkbox"][disabled] {
600
614
  input[type="checkbox"]:not([disabled]) {
601
615
  @apply cursor-pointer;
602
616
  }
617
+ td.sticky-column {
618
+ @apply sticky left-0 z-10;
619
+ &:not(:first-child) {
620
+ @apply left-[56px];
621
+ }
622
+ }
623
+ tr.list-table-body-row:not(:first-child):hover {
624
+ td.sticky-column:not(.list-table-header-cell) {
625
+ @apply bg-lightListTableRowHover dark:bg-darkListTableRowHover;
626
+ }
627
+ }
628
+
603
629
  </style>