adminforth 1.1.34 → 1.1.35

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 (45) hide show
  1. package/dist/index.js +7 -1
  2. package/dist/spa/index.html +2 -2
  3. package/dist/spa/package-lock.json +17 -4
  4. package/dist/spa/package.json +2 -1
  5. package/dist/spa/spa/src/components/ResourceListTable.vue +1 -10
  6. package/dist/spa/src/App.vue +125 -45
  7. package/dist/spa/src/assets/logo.svg +19 -1
  8. package/dist/spa/src/components/AcceptModal.vue +2 -9
  9. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  10. package/dist/spa/src/components/CustomDatePicker.vue +16 -3
  11. package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
  12. package/dist/spa/src/components/Dropdown.vue +6 -1
  13. package/dist/spa/src/components/Filters.vue +40 -19
  14. package/dist/spa/src/components/ResourceForm.vue +26 -24
  15. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  16. package/dist/spa/src/components/Toast.vue +66 -0
  17. package/dist/spa/src/components/ValueRenderer.vue +14 -8
  18. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  19. package/dist/spa/src/composables/useStores.ts +113 -0
  20. package/dist/spa/src/main.ts +1 -1
  21. package/dist/spa/src/router/index.ts +30 -20
  22. package/dist/spa/src/spa_types/core.ts +51 -0
  23. package/dist/spa/src/stores/core.ts +48 -31
  24. package/dist/spa/src/stores/filters.ts +22 -0
  25. package/dist/spa/src/stores/modal.ts +13 -3
  26. package/dist/spa/src/stores/toast.ts +15 -0
  27. package/dist/spa/src/stores/user.ts +54 -0
  28. package/dist/spa/src/utils.ts +62 -7
  29. package/dist/spa/src/views/CreateView.vue +67 -15
  30. package/dist/spa/src/views/EditView.vue +45 -11
  31. package/dist/spa/src/views/ListView.vue +82 -366
  32. package/dist/spa/src/views/LoginView.vue +16 -4
  33. package/dist/spa/src/views/ShowView.vue +105 -21
  34. package/dist/spa/tailwind.config.js +1 -1
  35. package/dist/spa/vite.config.ts +6 -0
  36. package/index.ts +14 -6
  37. package/package.json +1 -1
  38. package/spa/src/components/ResourceListTable.vue +1 -10
  39. package/types/AdminForthConfig.ts +0 -3
  40. package/dist/plugins/AccessControl/index.js +0 -66
  41. package/dist/plugins/AccessControl/types.js +0 -1
  42. package/dist/spa/public/favicon.ico +0 -0
  43. package/dist/spa/spa/public/favicon.ico +0 -0
  44. package/dist/types.js +0 -30
  45. /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
@@ -2,20 +2,27 @@
2
2
  <div class="relative">
3
3
  <Teleport to="body">
4
4
  <Filters
5
- :columns="coreStore.resourceColumns"
6
- v-model:filters="filters"
5
+ :columns="coreStore.resource?.columns"
7
6
  :columnsMinMax="columnsMinMax" :show="filtersShow"
8
7
  @hide="filtersShow = false"
9
8
  />
10
9
  </Teleport>
11
10
 
11
+ <component
12
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs || []"
13
+ :is="getCustomComponent(c)"
14
+ :meta="c.meta"
15
+ :resource="coreStore.resource"
16
+ :adminUser="coreStore.adminUser"
17
+ />
18
+
12
19
  <BreadcrumbsWithButtons>
13
20
  <button
14
21
  @click="()=>{checkboxes = []}"
15
22
  v-if="checkboxes.length"
16
23
  data-tooltip-target="tooltip-remove-all"
17
24
  data-tooltip-placement="bottom"
18
- class="flex gap-1 items-center py-1 px-3 me-2 mb-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"
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-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
19
26
  >
20
27
  <IconBanOutline class="w-5 h-5 "/>
21
28
 
@@ -27,9 +34,11 @@
27
34
  </button>
28
35
 
29
36
  <button
30
- v-if="checkboxes.length" v-for="(action,i) in allCheckedActions" :key="action.id"
37
+ v-if="checkboxes.length"
38
+ v-for="(action,i) in coreStore.resource?.options?.bulkActions"
39
+ :key="action.id"
31
40
  @click="startBulkAction(action.id)"
32
- class="flex gap-1 items-center py-1 px-3 mb-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"
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"
33
42
  :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',
34
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',
35
44
  }"
@@ -42,383 +51,129 @@
42
51
  {{ `${action.label} (${checkboxes.length})` }}
43
52
  </button>
44
53
 
45
- <RouterLink v-if="allowedActions.create"
54
+ <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
46
55
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
47
- class="flex items-center py-1 px-3 mb-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"
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"
48
57
  >
49
58
  <IconPlusOutline class="w-4 h-4 me-2"/>
50
59
  Create
51
60
  </RouterLink>
52
61
 
53
62
  <button
54
- class="flex gap-1 items-center py-1 px-3 me-2 mb-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"
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"
55
64
  @click="()=>{filtersShow = !filtersShow}"
65
+ v-if="coreStore.resource?.options?.allowedActions?.filter"
56
66
  >
57
67
  <IconFilterOutline class="w-4 h-4 me-2"/>
58
68
  Filter
59
69
  <span
60
70
  class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
61
- v-if="filters.length">
62
- {{ filters.length }}
71
+ v-if="filtersStore.filters.length">
72
+ {{ filtersStore.filters.length }}
63
73
  </span>
64
74
  </button>
65
75
  </BreadcrumbsWithButtons>
66
76
 
67
- <!-- table -->
68
- <div class="relative overflow-x-auto shadow-list-table-shadow dark:shadow-black dark:s">
69
-
70
-
71
- <!-- skelet loader -->
72
- <div role="status" v-if="!coreStore.resourceColumns"
73
- 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">
74
- <div class="flex items-center justify-between h-16">
75
- <div>
76
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
77
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
78
- </div>
79
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
80
- </div>
77
+ <component
78
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs || []"
79
+ :is="getCustomComponent(c)"
80
+ :meta="c.meta"
81
+ :resource="coreStore.resource"
82
+ :adminUser="coreStore.adminUser"
83
+ />
84
+
85
+ <ResourceListTable
86
+ :resource="coreStore.resource"
87
+ :rows="rows"
88
+ @update:page="page = $event"
89
+ @update:sort="sort = $event"
90
+ @update:checkboxes="checkboxes = $event"
91
+ @update:records="getList"
92
+ :pageSize="pageSize"
93
+ :totalRows="totalRows"
94
+ :checkboxes="checkboxes"
95
+ />
96
+
97
+ <component
98
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
99
+ :is="getCustomComponent(c)"
100
+ :meta="c.meta"
101
+ :resource="coreStore.resource"
102
+ :adminUser="coreStore.adminUser"
103
+ />
81
104
 
82
- <div class="flex items-center justify-between h-16 pt-4" v-for="i in new Array(10)">
83
- <div>
84
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
85
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
86
- </div>
87
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
88
- </div>
89
- <span class="sr-only">Loading...</span>
90
- </div>
91
-
92
- <table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
93
- <thead class="text-xs text-view-table-heading-text bg-list-view-table-heading dark:bg-gray-700 dark:text-gray-400">
94
- <tr>
95
- <th scope="col" class="p-4">
96
- <div v-if="rows && rows.length" class="flex items-center">
97
- <input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
98
- 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">
99
- <label for="checkbox-all-search" class="sr-only">checkbox</label>
100
- </div>
101
- </th>
102
-
103
- <th v-for="c in columnsListed" scope="col" class="px-6 py-3">
104
- <div @click="() => c.sortable && onSortButtonClick(c.name)" class="flex items-center " :class="{'cursor-pointer':c.sortable}">
105
- {{ c.label }}
106
-
107
- <div v-if="c.sortable"
108
- :style="{ 'color':ascArr.includes(c.name)?'green':descArr.includes(c.name)?'red':'currentColor'}">
109
- <svg v-if="ascArr.includes(c.name) || descArr.includes(c.name)" class="w-3 h-3 ms-1.5"
110
- :class="{'rotate-180':descArr.includes(c.name)}" viewBox="0 0 24 24">
111
- <path
112
- d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847"/>
113
- </svg>
114
- <svg v-else class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
115
- fill='currentColor'
116
- viewBox="0 0 24 24">
117
- <path
118
- d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"/>
119
- </svg>
120
- </div>
121
- </div>
122
- </th>
123
-
124
- <th scope="col" class="px-6 py-3">
125
- Actions
126
- </th>
127
- </tr>
128
- </thead>
129
- <tbody>
130
- <tr v-if="!rows" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
131
- <td :colspan="coreStore.resourceColumns.length + 2">
132
-
133
- <div role="status"
134
- class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700">
135
- <div class="flex items-center justify-between">
136
- <div>
137
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
138
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
139
- </div>
140
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
141
- </div>
142
- <div class="flex items-center justify-between pt-4">
143
- <div>
144
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
145
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
146
- </div>
147
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
148
- </div>
149
- <div class="flex items-center justify-between pt-4">
150
- <div>
151
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
152
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
153
- </div>
154
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
155
- </div>
156
- <div class="flex items-center justify-between pt-4">
157
- <div>
158
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
159
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
160
- </div>
161
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
162
- </div>
163
- <div class="flex items-center justify-between pt-4">
164
- <div>
165
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
166
- <div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
167
- </div>
168
- <div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
169
- </div>
170
- <span class="sr-only">Loading...</span>
171
- </div>
172
- </td>
173
- </tr>
174
- <tr v-else-if="rows.length === 0" class="bg-list-view-table-bg border-b dark:bg-gray-800 dark:border-gray-700">
175
- <td :colspan="coreStore.resourceColumns.length + 2">
176
-
177
- <div id="toast-simple"
178
- class=" mx-auto my-5 flex items-center w-full max-w-xs p-4 space-x-4 rtl:space-x-reverse text-gray-500 bg-white divide-x rtl:divide-x-reverse divide-gray-200 dark:text-gray-400 dark:divide-gray-700 space-x dark:bg-gray-800"
179
- role="alert">
180
- <IconInboxOutline class="w-6 h-6 text-gray-500 dark:text-gray-400"/>
181
- <div class="ps-4 text-sm font-normal">No items here yet</div>
182
- </div>
183
-
184
- </td>
185
- </tr>
186
-
187
- <tr v-else v-for="(row, rowI) in rows" :key="row.id"
188
- class="bg-list-view-table-bg border-b dark:bg-gray-800 border-list-view-border-color dark:border-gray-700 hover:bg-list-view-table-row-hover dark:hover:bg-gray-600">
189
- <td class="w-4 p-4">
190
- <div class="flex items center">
191
- <input
192
- id="checkbox-table-search-1"
193
- type="checkbox"
194
- :checked="checkboxes.includes(row.id)"
195
- @change="(e)=>{addToCheckedValues(row.id)}"
196
- 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">
197
- <label for="checkbox-table-search-1" class="sr-only">checkbox</label>
198
- </div>
199
- </td>
200
- <td v-for="c in columnsListed" class="px-6 py-4">
201
- <!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
202
- <component
203
- :is="listComponentsPerColumn[c.name] || ValueRenderer"
204
- :column="c"
205
- :row="row"
206
- />
207
- </td>
208
- <td class="flex items-center px-6 py-4">
209
-
210
- <RouterLink
211
- :to="{ name: 'resource-show', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
212
- class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
213
- :data-tooltip-target="`tooltip-show-${rowI}`"
214
- >
215
- <IconEyeSolid class="w-5 h-5 me-2"/>
216
- </RouterLink>
217
-
218
- <div :id="`tooltip-show-${rowI}`"
219
- role="tooltip"
220
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
221
- Show item
222
- <div class="tooltip-arrow" data-popper-arrow></div>
223
- </div>
224
-
225
- <RouterLink v-if="allowedActions.edit"
226
- :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
227
- class="font-medium text-blue-600 dark:text-blue-500 hover:underline ms-3"
228
- :data-tooltip-target="`tooltip-edit-${rowI}`"
229
- >
230
- <IconPenSolid class="w-5 h-5 me-2"/>
231
- </RouterLink>
232
-
233
- <div :id="`tooltip-edit-${rowI}`"
234
- role="tooltip"
235
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
236
- Edit
237
- <div class="tooltip-arrow" data-popper-arrow></div>
238
- </div>
239
-
240
- <button v-if="allowedActions.delete"
241
- class="font-medium text-red-600 dark:text-red-500 hover:underline ms-3"
242
- :data-tooltip-target="`tooltip-delete-${rowI}`"
243
- @click="showDeleteModal(row)"
244
- >
245
- <IconTrashBinSolid class="w-5 h-5 me-2"/>
246
- </button>
247
-
248
- <div :id="`tooltip-delete-${rowI}`"
249
- role="tooltip"
250
- class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
251
- Delete
252
- <div class="tooltip-arrow" data-popper-arrow></div>
253
- </div>
254
- </td>
255
- </tr>
256
- </tbody>
257
- </table>
258
- </div>
259
- <!-- pagination -->
260
- <div class="flex flex-col items-center mt-4 xs:flex-row xs:justify-between xs:items-center"
261
- v-if="rows && totalRows >= pageSize && totalRows > 0"
262
- >
263
- <!-- Help text -->
264
- <span class="text-sm text-gray-700 dark:text-gray-400">
265
- Showing <span class="font-semibold text-gray-900 dark:text-white">
266
- {{ (page - 1) * pageSize + 1 }}
267
- </span> to <span class="font-semibold text-gray-900 dark:text-white">
268
- {{ Math.min(page * pageSize, totalRows) }}
269
- </span> of <span class="font-semibold text-gray-900 dark:text-white">{{
270
- totalRows
271
- }}</span> Entries
272
- </span>
273
- <div class="inline-flex mt-2 xs:mt-0">
274
- <!-- Buttons -->
275
- <button
276
- class="flex items-center py-1 px-3 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-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 disabled:opacity-50"
277
- @click="page--" :disabled="page <= 1">
278
- <svg class="w-3.5 h-3.5 me-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
279
- viewBox="0 0 14 10">
280
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
281
- d="M13 5H1m0 0 4 4M1 5l4-4"/>
282
- </svg>
283
- Prev
284
- </button>
285
- <button
286
- 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-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 disabled:opacity-50"
287
- @click="page = 1" :disabled="page <= 1">
288
- <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
289
- 1
290
- </button>
291
- <input type="text"
292
- class="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"
293
- v-model="page"/>
294
-
295
- <button
296
- 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-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 disabled:opacity-50"
297
- @click="page = totalPages" :disabled="page >= totalPages">
298
- {{ totalPages }}
299
- <!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
300
- </button>
301
- <button
302
- class="flex items-center py-1 px-3 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-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 disabled:opacity-50"
303
-
304
- @click="page++" :disabled="page >= totalPages">
305
- Next
306
- <svg class="w-3.5 h-3.5 ms-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
307
- viewBox="0 0 14 10">
308
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
309
- d="M1 5h12m0 0L9 1m4 4L9 9"/>
310
- </svg>
311
- </button>
312
- </div>
313
- </div>
314
105
  </div>
315
106
  </template>
316
107
 
317
108
  <script setup>
318
109
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
110
+ import ResourceListTable from '@/components/ResourceListTable.vue';
319
111
  import { useCoreStore } from '@/stores/core';
320
112
  import { useModalStore } from '@/stores/modal';
113
+ import { useFiltersStore } from '@/stores/filters';
321
114
  import { callAdminForthApi, getIcon } from '@/utils';
322
115
  import { initFlowbite } from 'flowbite';
323
116
  import { computed, onMounted, ref, watch } from 'vue';
324
117
  import { useRoute } from 'vue-router';
118
+ import { showErrorTost } from '@/composables/useFrontendApi'
325
119
 
326
120
  import ValueRenderer from '@/components/ValueRenderer.vue';
327
121
  import { getCustomComponent } from '@/utils';
328
122
 
329
- import {
330
- IconInboxOutline,
331
- IconPlusOutline
332
- } from '@iconify-prerendered/vue-flowbite';
333
123
 
334
124
  import {
335
- IconBanOutline,
336
- IconEyeSolid,
337
- IconFilterOutline,
338
- IconPenSolid,
339
- IconTrashBinSolid
125
+ IconBanOutline,
126
+ IconEyeSolid,
127
+ IconFilterOutline,
128
+ IconPenSolid,
129
+ IconTrashBinSolid,
130
+ IconInboxOutline,
131
+ IconPlusOutline
340
132
  } from '@iconify-prerendered/vue-flowbite';
341
133
 
342
134
  import Filters from '@/components/Filters.vue';
343
135
 
344
136
  const filtersShow = ref(false);
345
137
 
346
-
347
138
  const coreStore = useCoreStore();
348
139
  const modalStore = useModalStore();
140
+ const filtersStore = useFiltersStore();
349
141
 
350
142
  const route = useRoute();
351
- const checkboxes = ref([]);
352
143
 
353
144
  const page = ref(1);
354
- const filters = ref([]);
145
+ const filters = filtersStore.filters;
355
146
  const columnsMinMax = ref({});
356
147
  const sort = ref([]);
357
- const fetchStatus = ref({pending: false, error: null, success: false});
358
148
 
359
149
  const rows = ref(null);
360
150
  const totalRows = ref(0);
361
- const allCheckedActions = ref([]);
362
- const allowedActions = ref({});
151
+ const checkboxes = ref([]);
363
152
 
364
153
  const DEFAULT_PAGE_SIZE = 10;
365
154
 
366
- const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
367
155
 
368
- async function selectAll(value) {
369
- console.log('select all');
370
- rows.value.forEach((r) => {
371
- if (!checkboxes.value.includes(r.id)) {
372
- checkboxes.value.push(r.id)
373
- } else {
374
- checkboxes.value = checkboxes.value.filter((item) => item !== r.id)
375
- }
376
- });
377
- // checkboxes.value = rows.value.map((v) => v.id);
378
- }
156
+ const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
379
157
 
380
- const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
381
- const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
382
- let listComponentsPerColumn = {};
383
- const allFromThisPageChecked = computed(() => {
384
- if (!rows.value) return false;
385
- return rows.value.every((r) => checkboxes.value.includes(r.id));
386
- });
387
- const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
388
- const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
389
158
 
390
159
  watch([page], async () => {
391
- await init();
160
+ await getList();
392
161
  });
393
162
 
394
- watch([filters], async () => {
163
+ watch(()=>filtersStore.filters, async () => {
395
164
  page.value = 1;
165
+ checkboxes.value = [];
166
+
396
167
  await getList();
397
168
  }, {deep: true});
398
169
 
399
170
  watch([sort], async () => {
400
- await init();
171
+ await getList();
401
172
  }, {deep: true});
402
173
 
403
- function onSortButtonClick(field) {
404
- if (fetchStatus.value.pending) return;
405
- const sortIndex = sort.value.findIndex((s) => s.field === field);
406
- console.log('sortIndex', sortIndex);
407
- if (sortIndex === -1) {
408
- sort.value = [{field, direction: 'asc'}, ...sort.value];
409
- } else {
410
- const sortField = sort.value[sortIndex];
411
- if (sortField.direction === 'asc') {
412
- sort.value[sortIndex].direction = 'desc';
413
- } else {
414
- sort.value.splice(sortIndex, 1);
415
- }
416
- }
417
- }
418
174
 
419
175
  async function getList() {
420
176
  rows.value = null;
421
- fetchStatus.value.pending = true;
422
177
  const data = await callAdminForthApi({
423
178
  path: '/get_resource_data',
424
179
  method: 'POST',
@@ -427,64 +182,27 @@ async function getList() {
427
182
  resourceId: route.params.resourceId,
428
183
  limit: pageSize.value,
429
184
  offset: (page.value - 1) * pageSize.value,
430
- filters: filters.value,
185
+ filters: filtersStore.filters,
431
186
  sort: sort.value,
432
187
  }
433
188
  });
434
- listComponentsPerColumn = coreStore.resourceColumns.reduce((acc, column) => {
435
- if (column.component?.list) {
436
- acc[column.name] = getCustomComponent(column.component.list);
437
- }
438
- return acc;
439
- }, {});
440
-
441
- fetchStatus.value.pending = false;
189
+ if (data.error) {
190
+ showErrorTost(data.error);
191
+ rows.value = [];
192
+ totalRows.value = 0;
193
+ return;
194
+ }
442
195
  rows.value = data.data?.map(row => {
443
- row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
196
+ row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
444
197
  return row;
445
198
  });
446
199
  totalRows.value = data.total;
447
- allCheckedActions.value = data.options?.bulkActions || [];
448
- allowedActions.value = data.options?.allowedActions;
449
-
450
-
451
- setTimeout(() => {
452
- initFlowbite();
453
- console.log('initFlowbite');
454
- });
455
200
  }
456
201
 
457
202
 
458
- function showDeleteModal(row) {
459
- if (!coreStore.config?.deleteConfirmation) {
460
- return deleteRecord(row);
461
- }
462
- modalStore.setModalContent({
463
- content: 'Are you sure you want to delete this item?',
464
- acceptText: 'Delete',
465
- cancelText: 'Cancel',
466
- });
467
- modalStore.setOnAcceptFunction(() => {
468
- return deleteRecord(row)
469
- })
470
- console.log('row', row);
471
- modalStore.togleModal();
472
203
 
473
- }
474
204
 
475
- async function deleteRecord(row) {
476
- await callAdminForthApi({
477
- path: '/delete_record',
478
- method: 'POST',
479
- body: {
480
- resourceId: route.params.resourceId,
481
- primaryKey: row._primaryKeyValue,
482
- recordId: row.id
483
- }
484
- });
485
- await getList();
486
- modalStore.resetmodalState()
487
- }
205
+
488
206
 
489
207
  async function startBulkAction(actionId) {
490
208
  const data = await callAdminForthApi({
@@ -503,16 +221,11 @@ async function startBulkAction(actionId) {
503
221
  await getList();
504
222
  }
505
223
 
506
- function addToCheckedValues(id) {
507
- if (checkboxes.value.includes(id)) {
508
- checkboxes.value = checkboxes.value.filter((item) => item !== id);
509
- } else {
510
- checkboxes.value.push(id);
511
- }
512
- }
224
+
513
225
 
514
226
  async function init() {
515
- await coreStore.fetchColumns({
227
+
228
+ await coreStore.fetchResourceFull({
516
229
  resourceId: route.params.resourceId
517
230
  });
518
231
 
@@ -527,12 +240,15 @@ async function init() {
527
240
  }
528
241
 
529
242
  onMounted(async () => {
243
+ initFlowbite();
244
+
530
245
  await init();
246
+
531
247
  });
532
248
 
533
249
  // on route param change
534
250
  watch(() => route.params.resourceId, async () => {
535
- filters.value = [];
251
+ filtersStore.setFilters([]);
536
252
  checkboxes.value = [];
537
253
  sort.value = [];
538
254
  await init();
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
3
3
  :style="coreStore.config?.loginBackgroundImage ? {
4
- 'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
4
+ 'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
6
6
  'background-position': 'center',
7
7
  'background-blend-mode': 'darken'
8
8
  }: {}"
9
9
  >
10
- <!-- Main modal -->
10
+
11
+ <!-- Main modal -->
11
12
  <div id="authentication-modal" tabindex="-1" class=" overflow-y-auto overflow-x-hidden z-50 min-w-[400px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
12
13
  <div class="relative p-4 w-full max-w-md max-h-full">
13
14
  <!-- Modal content -->
@@ -78,16 +79,21 @@
78
79
 
79
80
  <script setup>
80
81
 
81
- import { onMounted, ref } from 'vue';
82
+ import { onMounted, ref, watchEffect } from 'vue';
82
83
  import { useCoreStore } from '@/stores/core';
84
+ import { useUserStore } from '@/stores/user';
83
85
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
84
- import { callAdminForthApi } from '@/utils';
86
+ import { callAdminForthApi, loadFile } from '@/utils';
85
87
  import { useRouter } from 'vue-router';
88
+ import { initFlowbite } from 'flowbite'
89
+
86
90
 
87
91
  const router = useRouter();
88
92
  const inProgress = ref(false);
89
93
 
90
94
  const coreStore = useCoreStore();
95
+ const user = useUserStore();
96
+
91
97
 
92
98
  const showPw = ref(false);
93
99
 
@@ -112,8 +118,14 @@ async function login() {
112
118
  error.value = resp.error;
113
119
  } else {
114
120
  error.value = null;
121
+ user.authorize()
115
122
  router.push('/');
123
+ await router.isReady();
116
124
  await coreStore.fetchMenuAndResource();
125
+ setTimeout(() => {
126
+ initFlowbite();
127
+ });
128
+
117
129
  }
118
130
 
119
131
  }