adminforth 1.2.42 → 1.2.44

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 +5 -0
  2. package/dist/spa/index.html +4 -4
  3. package/dist/spa/package-lock.json +206 -5
  4. package/dist/spa/package.json +8 -2
  5. package/dist/spa/public/assets/favicon.png +0 -0
  6. package/dist/spa/src/App.vue +171 -72
  7. package/dist/spa/src/assets/logo.svg +19 -1
  8. package/dist/spa/src/components/AcceptModal.vue +3 -10
  9. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  10. package/dist/spa/src/components/CustomDatePicker.vue +19 -6
  11. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  12. package/dist/spa/src/components/CustomRangePicker.vue +148 -0
  13. package/dist/spa/src/components/Dropdown.vue +19 -5
  14. package/dist/spa/src/components/Filters.vue +91 -37
  15. package/dist/spa/src/components/MenuLink.vue +2 -2
  16. package/dist/spa/src/components/ResourceForm.vue +163 -103
  17. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  18. package/dist/spa/src/components/Toast.vue +66 -0
  19. package/dist/spa/src/components/ValueRenderer.vue +22 -6
  20. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  21. package/dist/spa/src/composables/useStores.ts +113 -0
  22. package/dist/spa/src/main.ts +1 -1
  23. package/dist/spa/src/router/index.ts +30 -19
  24. package/dist/spa/src/spa_types/core.ts +51 -0
  25. package/dist/spa/src/stores/core.ts +65 -59
  26. package/dist/spa/src/stores/filters.ts +22 -0
  27. package/dist/spa/src/stores/modal.ts +13 -3
  28. package/dist/spa/src/stores/toast.ts +15 -0
  29. package/dist/spa/src/stores/user.ts +54 -0
  30. package/dist/spa/src/utils.ts +65 -8
  31. package/dist/spa/src/views/CreateView.vue +76 -16
  32. package/dist/spa/src/views/EditView.vue +76 -14
  33. package/dist/spa/src/views/ListView.vue +88 -360
  34. package/dist/spa/src/views/LoginView.vue +17 -5
  35. package/dist/spa/src/views/ResourceParent.vue +1 -1
  36. package/dist/spa/src/views/ShowView.vue +114 -19
  37. package/dist/spa/tailwind.config.js +5 -2
  38. package/dist/spa/vite.config.ts +6 -0
  39. package/index.ts +5 -0
  40. package/package.json +1 -1
  41. package/dist/spa/public/favicon.ico +0 -0
  42. package/dist/spa/spa/public/favicon.ico +0 -0
  43. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  44. package/dist/spa/src/views/HomeView.vue +0 -8
  45. package/dist/types.js +0 -30
@@ -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,437 +51,158 @@
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-md sm:rounded-lg dark:shadow-2xl">
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-gray-700 bg-gray-50 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-white 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-white 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-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 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
- <ValueRenderer :column="c" :row="row"/>
202
- </td>
203
- <td class="flex items-center px-6 py-4">
204
-
205
- <RouterLink
206
- :to="{ name: 'resource-show', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
207
- class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
208
- :data-tooltip-target="`tooltip-show-${rowI}`"
209
- >
210
- <IconEyeSolid class="w-5 h-5 me-2"/>
211
- </RouterLink>
212
-
213
- <div :id="`tooltip-show-${rowI}`"
214
- role="tooltip"
215
- 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">
216
- Show item
217
- <div class="tooltip-arrow" data-popper-arrow></div>
218
- </div>
219
-
220
- <RouterLink v-if="allowedActions.edit"
221
- :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: row._primaryKeyValue } }"
222
- class="font-medium text-blue-600 dark:text-blue-500 hover:underline ms-3"
223
- :data-tooltip-target="`tooltip-edit-${rowI}`"
224
- >
225
- <IconPenSolid class="w-5 h-5 me-2"/>
226
- </RouterLink>
227
-
228
- <div :id="`tooltip-edit-${rowI}`"
229
- role="tooltip"
230
- 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">
231
- Edit
232
- <div class="tooltip-arrow" data-popper-arrow></div>
233
- </div>
234
-
235
- <button v-if="allowedActions.delete"
236
- class="font-medium text-red-600 dark:text-red-500 hover:underline ms-3"
237
- :data-tooltip-target="`tooltip-delete-${rowI}`"
238
- @click="showDeleteModal(row)"
239
- >
240
- <IconTrashBinSolid class="w-5 h-5 me-2"/>
241
- </button>
242
-
243
- <div :id="`tooltip-delete-${rowI}`"
244
- role="tooltip"
245
- 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">
246
- Delete
247
- <div class="tooltip-arrow" data-popper-arrow></div>
248
- </div>
249
- </td>
250
- </tr>
251
- </tbody>
252
- </table>
253
- </div>
254
- <!-- pagination -->
255
- <div class="flex flex-col items-center mt-4 xs:flex-row xs:justify-between xs:items-center"
256
- v-if="rows && totalRows >= pageSize && totalRows > 0"
257
- >
258
- <!-- Help text -->
259
- <span class="text-sm text-gray-700 dark:text-gray-400">
260
- Showing <span class="font-semibold text-gray-900 dark:text-white">
261
- {{ (page - 1) * pageSize + 1 }}
262
- </span> to <span class="font-semibold text-gray-900 dark:text-white">
263
- {{ Math.min(page * pageSize, totalRows) }}
264
- </span> of <span class="font-semibold text-gray-900 dark:text-white">{{
265
- totalRows
266
- }}</span> Entries
267
- </span>
268
- <div class="inline-flex mt-2 xs:mt-0">
269
- <!-- Buttons -->
270
- <button
271
- 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"
272
- @click="page--" :disabled="page <= 1">
273
- <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"
274
- viewBox="0 0 14 10">
275
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
276
- d="M13 5H1m0 0 4 4M1 5l4-4"/>
277
- </svg>
278
- Prev
279
- </button>
280
- <button
281
- 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"
282
- @click="page = 1" :disabled="page <= 1">
283
- <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
284
- 1
285
- </button>
286
- <input type="text"
287
- 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"
288
- v-model="page"/>
289
-
290
- <button
291
- 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"
292
- @click="page = totalPages" :disabled="page >= totalPages">
293
- {{ totalPages }}
294
- <!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
295
- </button>
296
- <button
297
- 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"
298
-
299
- @click="page++" :disabled="page >= totalPages">
300
- Next
301
- <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"
302
- viewBox="0 0 14 10">
303
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
304
- d="M1 5h12m0 0L9 1m4 4L9 9"/>
305
- </svg>
306
- </button>
307
- </div>
308
- </div>
309
105
  </div>
310
106
  </template>
311
107
 
312
108
  <script setup>
313
- import {ref, onMounted, watch, computed} from 'vue';
314
- import {callAdminForthApi, getIcon} from '@/utils';
315
- import {useRoute} from 'vue-router';
316
- import {useCoreStore} from '@/stores/core';
317
- import {useModalStore} from '@/stores/modal';
318
109
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
319
- import {initFlowbite} from 'flowbite'
110
+ import ResourceListTable from '@/components/ResourceListTable.vue';
111
+ import { useCoreStore } from '@/stores/core';
112
+ import { useModalStore } from '@/stores/modal';
113
+ import { useFiltersStore } from '@/stores/filters';
114
+ import { callAdminForthApi, getIcon } from '@/utils';
115
+ import { initFlowbite } from 'flowbite';
116
+ import { computed, onMounted, ref, watch } from 'vue';
117
+ import { useRoute } from 'vue-router';
118
+ import { showErrorTost } from '@/composables/useFrontendApi'
320
119
 
321
120
  import ValueRenderer from '@/components/ValueRenderer.vue';
121
+ import { getCustomComponent } from '@/utils';
322
122
 
323
- import {
324
- IconChevronDoubleLeftOutline,
325
- IconChevronDoubleRightOutline,
326
- IconInboxFullSolid,
327
- IconInboxOutline,
328
- IconPlusOutline
329
- } from '@iconify-prerendered/vue-flowbite';
330
123
 
331
124
  import {
125
+ IconBanOutline,
332
126
  IconEyeSolid,
333
- IconTrashBinSolid,
127
+ IconFilterOutline,
334
128
  IconPenSolid,
335
- IconFilterOutline, IconBanOutline
129
+ IconTrashBinSolid,
130
+ IconInboxOutline,
131
+ IconPlusOutline
336
132
  } from '@iconify-prerendered/vue-flowbite';
337
133
 
338
134
  import Filters from '@/components/Filters.vue';
339
135
 
340
136
  const filtersShow = ref(false);
341
137
 
342
-
343
138
  const coreStore = useCoreStore();
344
139
  const modalStore = useModalStore();
140
+ const filtersStore = useFiltersStore();
345
141
 
346
142
  const route = useRoute();
347
- const checkboxes = ref([]);
348
143
 
349
144
  const page = ref(1);
350
- const filters = ref([]);
145
+ const filters = filtersStore.filters;
351
146
  const columnsMinMax = ref({});
352
147
  const sort = ref([]);
353
- const fetchStatus = ref({pending: false, error: null, success: false});
354
148
 
355
149
  const rows = ref(null);
356
150
  const totalRows = ref(0);
357
- const allCheckedActions = ref([]);
358
- const allowedActions = ref({});
359
-
151
+ const checkboxes = ref([]);
360
152
 
361
153
  const DEFAULT_PAGE_SIZE = 10;
362
154
 
363
- const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
364
155
 
365
- async function selectAll(value) {
366
- console.log('select all');
367
- rows.value.forEach((r) => {
368
- if (!checkboxes.value.includes(r.id)) {
369
- checkboxes.value.push(r.id)
370
- } else {
371
- checkboxes.value = checkboxes.value.filter((item) => item !== r.id)
372
- }
373
- });
374
- // checkboxes.value = rows.value.map((v) => v.id);
375
- }
156
+ const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
376
157
 
377
- const pageSize = computed(() => coreStore.resourceById[route.params.resourceId]?.pageSize || DEFAULT_PAGE_SIZE);
378
- const totalPages = computed(() => Math.ceil(totalRows.value / pageSize.value));
379
- const allFromThisPageChecked = computed(() => {
380
- if (!rows.value) return false;
381
- return rows.value.every((r) => checkboxes.value.includes(r.id));
382
- });
383
- const ascArr = computed(() => sort.value.filter((s) => s.direction === 'asc').map((s) => s.field));
384
- const descArr = computed(() => sort.value.filter((s) => s.direction === 'desc').map((s) => s.field));
385
158
 
386
159
  watch([page], async () => {
387
- await init();
160
+ await getList();
388
161
  });
389
162
 
390
- watch([filters], async () => {
163
+ watch(()=>filtersStore.filters, async () => {
391
164
  page.value = 1;
165
+ checkboxes.value = [];
166
+
392
167
  await getList();
393
168
  }, {deep: true});
394
169
 
395
170
  watch([sort], async () => {
396
- await init();
171
+ await getList();
397
172
  }, {deep: true});
398
173
 
399
- function onSortButtonClick(field) {
400
- if (fetchStatus.value.pending) return;
401
- const sortIndex = sort.value.findIndex((s) => s.field === field);
402
- console.log('sortIndex', sortIndex);
403
- if (sortIndex === -1) {
404
- sort.value = [{field, direction: 'asc'}, ...sort.value];
405
- } else {
406
- const sortField = sort.value[sortIndex];
407
- if (sortField.direction === 'asc') {
408
- sort.value[sortIndex].direction = 'desc';
409
- } else {
410
- sort.value.splice(sortIndex, 1);
411
- }
412
- }
413
- }
414
174
 
415
175
  async function getList() {
416
176
  rows.value = null;
417
- fetchStatus.value.pending = true;
418
177
  const data = await callAdminForthApi({
419
178
  path: '/get_resource_data',
420
179
  method: 'POST',
421
180
  body: {
181
+ source: 'list',
422
182
  resourceId: route.params.resourceId,
423
183
  limit: pageSize.value,
424
184
  offset: (page.value - 1) * pageSize.value,
425
- filters: filters.value,
185
+ filters: filtersStore.filters,
426
186
  sort: sort.value,
427
187
  }
428
188
  });
429
- fetchStatus.value.pending = false;
189
+ if (data.error) {
190
+ showErrorTost(data.error);
191
+ rows.value = [];
192
+ totalRows.value = 0;
193
+ return;
194
+ }
430
195
  rows.value = data.data?.map(row => {
431
- row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
196
+ row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
432
197
  return row;
433
198
  });
434
199
  totalRows.value = data.total;
435
- allCheckedActions.value = data.options?.bulkActions || [];
436
- allowedActions.value = data.options?.allowedActions;
437
-
438
-
439
- setTimeout(() => {
440
- initFlowbite();
441
- console.log('initFlowbite');
442
- });
443
200
  }
444
201
 
445
202
 
446
- function showDeleteModal(row) {
447
- if (!coreStore.config?.deleteConfirmation) {
448
- return deleteRecord(row);
449
- }
450
- modalStore.setModalContent({
451
- content: 'Are you sure you want to delete this item?',
452
- acceptText: 'Delete',
453
- cancelText: 'Cancel',
454
- });
455
- modalStore.setOnAcceptFunction(() => {
456
- return deleteRecord(row)
457
- })
458
- console.log('row', row);
459
- modalStore.togleModal();
460
203
 
461
- }
462
204
 
463
- async function deleteRecord(row) {
464
- await callAdminForthApi({
465
- path: '/delete_record',
466
- method: 'POST',
467
- body: {
468
- resourceId: route.params.resourceId,
469
- primaryKey: row._primaryKeyValue,
470
- recordId: row.id
471
- }
472
- });
473
- await getList();
474
- modalStore.resetmodalState()
475
- }
205
+
476
206
 
477
207
  async function startBulkAction(actionId) {
478
208
  const data = await callAdminForthApi({
@@ -491,16 +221,11 @@ async function startBulkAction(actionId) {
491
221
  await getList();
492
222
  }
493
223
 
494
- function addToCheckedValues(id) {
495
- if (checkboxes.value.includes(id)) {
496
- checkboxes.value = checkboxes.value.filter((item) => item !== id);
497
- } else {
498
- checkboxes.value.push(id);
499
- }
500
- }
224
+
501
225
 
502
226
  async function init() {
503
- await coreStore.fetchColumns({
227
+
228
+ await coreStore.fetchResourceFull({
504
229
  resourceId: route.params.resourceId
505
230
  });
506
231
 
@@ -515,12 +240,15 @@ async function init() {
515
240
  }
516
241
 
517
242
  onMounted(async () => {
243
+ initFlowbite();
244
+
518
245
  await init();
246
+
519
247
  });
520
248
 
521
249
  // on route param change
522
250
  watch(() => route.params.resourceId, async () => {
523
- filters.value = [];
251
+ filtersStore.setFilters([]);
524
252
  checkboxes.value = [];
525
253
  sort.value = [];
526
254
  await init();
@@ -1,17 +1,18 @@
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 -->
14
- <div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
15
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700 dark:shadow-black" >
15
16
  <!-- Modal header -->
16
17
  <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
17
18
  <h3 class="text-xl font-semibold text-gray-900 dark:text-white">
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div :key="`${$route?.params.resourceId}---${$route?.params.primaryKey}`">
3
3
  <RouterView/>
4
4
  </div>
5
5
  </template>