adminforth 1.2.96 → 1.2.97

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 (115) hide show
  1. package/dataConnectors/baseConnector.ts +26 -14
  2. package/dataConnectors/clickhouse.ts +57 -37
  3. package/dataConnectors/mongo.ts +45 -14
  4. package/dataConnectors/postgres.ts +53 -36
  5. package/dataConnectors/sqlite.ts +37 -35
  6. package/dist/dataConnectors/baseConnector.js +16 -7
  7. package/dist/dataConnectors/clickhouse.js +58 -48
  8. package/dist/dataConnectors/mongo.js +29 -12
  9. package/dist/dataConnectors/postgres.js +62 -52
  10. package/dist/dataConnectors/sqlite.js +52 -45
  11. package/dist/index.js +5 -1
  12. package/dist/modules/operationalResource.js +88 -0
  13. package/dist/modules/restApi.js +22 -23
  14. package/dist/spa/index.html +4 -4
  15. package/dist/spa/package-lock.json +421 -6
  16. package/dist/spa/package.json +10 -2
  17. package/dist/spa/src/App.vue +236 -94
  18. package/dist/spa/src/assets/base.css +2 -0
  19. package/dist/spa/src/assets/logo.svg +19 -1
  20. package/dist/spa/src/components/AcceptModal.vue +3 -10
  21. package/dist/spa/src/components/Breadcrumbs.vue +3 -2
  22. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  23. package/dist/spa/src/components/CustomDatePicker.vue +25 -10
  24. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  25. package/dist/spa/src/components/Dropdown.vue +19 -5
  26. package/dist/spa/src/components/Filters.vue +103 -38
  27. package/dist/spa/src/components/MenuLink.vue +3 -3
  28. package/dist/spa/src/components/ResourceForm.vue +205 -116
  29. package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +6 -3
  30. package/dist/spa/{spa/src → src}/components/Toast.vue +9 -3
  31. package/dist/spa/src/components/ValueRenderer.vue +30 -8
  32. package/dist/spa/src/index.scss +2 -1
  33. package/dist/spa/src/main.ts +2 -2
  34. package/dist/spa/src/router/index.ts +29 -19
  35. package/dist/spa/src/stores/core.ts +65 -59
  36. package/dist/spa/src/stores/modal.ts +13 -3
  37. package/dist/spa/src/types/AdminForthConfig.ts +1477 -0
  38. package/dist/spa/src/types/FrontendAPI.ts +121 -0
  39. package/dist/spa/src/utils.ts +69 -10
  40. package/dist/spa/src/views/CreateView.vue +69 -16
  41. package/dist/spa/src/views/EditView.vue +68 -14
  42. package/dist/spa/src/views/ListView.vue +100 -371
  43. package/dist/spa/src/views/LoginView.vue +67 -29
  44. package/dist/spa/src/views/ResourceParent.vue +1 -2
  45. package/dist/spa/src/views/ShowView.vue +116 -25
  46. package/dist/spa/tailwind.config.js +8 -3
  47. package/dist/spa/vite.config.ts +13 -0
  48. package/dist/types/AdminForthConfig.js +37 -0
  49. package/index.ts +12 -3
  50. package/modules/operationalResource.ts +97 -0
  51. package/modules/restApi.ts +27 -26
  52. package/package.json +1 -1
  53. package/types/AdminForthConfig.ts +91 -8
  54. package/dist/spa/.vscode/extensions.json +0 -6
  55. package/dist/spa/public/favicon.ico +0 -0
  56. package/dist/spa/spa/.eslintrc.cjs +0 -14
  57. package/dist/spa/spa/.vscode/extensions.json +0 -6
  58. package/dist/spa/spa/README.md +0 -39
  59. package/dist/spa/spa/env.d.ts +0 -1
  60. package/dist/spa/spa/index.html +0 -23
  61. package/dist/spa/spa/package-lock.json +0 -4573
  62. package/dist/spa/spa/package.json +0 -49
  63. package/dist/spa/spa/postcss.config.js +0 -6
  64. package/dist/spa/spa/public/favicon.ico +0 -0
  65. package/dist/spa/spa/src/App.vue +0 -314
  66. package/dist/spa/spa/src/assets/base.css +0 -0
  67. package/dist/spa/spa/src/assets/logo.svg +0 -19
  68. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  69. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  70. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  71. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  72. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  73. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  74. package/dist/spa/spa/src/components/Filters.vue +0 -222
  75. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  76. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  77. package/dist/spa/spa/src/components/ResourceForm.vue +0 -289
  78. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  79. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -66
  80. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  81. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  82. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  83. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  84. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  85. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  86. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  87. package/dist/spa/spa/src/index.scss +0 -27
  88. package/dist/spa/spa/src/main.ts +0 -18
  89. package/dist/spa/spa/src/router/index.ts +0 -63
  90. package/dist/spa/spa/src/stores/core.ts +0 -144
  91. package/dist/spa/spa/src/stores/modal.ts +0 -48
  92. package/dist/spa/spa/src/utils.ts +0 -103
  93. package/dist/spa/spa/src/views/CreateView.vue +0 -156
  94. package/dist/spa/spa/src/views/EditView.vue +0 -157
  95. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  96. package/dist/spa/spa/src/views/ListView.vue +0 -258
  97. package/dist/spa/spa/src/views/LoginView.vue +0 -143
  98. package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
  99. package/dist/spa/spa/src/views/ShowView.vue +0 -184
  100. package/dist/spa/spa/tailwind.config.js +0 -17
  101. package/dist/spa/spa/tsconfig.app.json +0 -14
  102. package/dist/spa/spa/tsconfig.json +0 -11
  103. package/dist/spa/spa/tsconfig.node.json +0 -19
  104. package/dist/spa/spa/vite.config.ts +0 -56
  105. package/dist/spa/src/views/HomeView.vue +0 -8
  106. package/dist/types.js +0 -30
  107. /package/dist/spa/{spa/public → public}/assets/favicon.png +0 -0
  108. /package/dist/spa/{spa/src → src}/components/CustomRangePicker.vue +0 -0
  109. /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
  110. /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
  111. /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
  112. /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
  113. /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
  114. /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
  115. /package/dist/spa/{spa/src → src}/stores/user.ts +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-darkListTable 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,436 +51,147 @@
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 }}
63
- </span>
71
+ v-if="filtersStore.filters.length">
72
+ {{ filtersStore.filters.length }}
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
+ :sort="sort"
93
+ :pageSize="pageSize"
94
+ :totalRows="totalRows"
95
+ :checkboxes="checkboxes"
96
+ />
97
+
98
+ <component
99
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
100
+ :is="getCustomComponent(c)"
101
+ :meta="c.meta"
102
+ :resource="coreStore.resource"
103
+ :adminUser="coreStore.adminUser"
104
+ />
81
105
 
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
106
  </div>
310
107
  </template>
311
108
 
312
109
  <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
110
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
319
- import {initFlowbite} from 'flowbite'
111
+ import ResourceListTable from '@/components/ResourceListTable.vue';
112
+ import { useCoreStore } from '@/stores/core';
113
+ import { useModalStore } from '@/stores/modal';
114
+ import { useFiltersStore } from '@/stores/filters';
115
+ import { callAdminForthApi, getIcon } from '@/utils';
116
+ import { initFlowbite } from 'flowbite';
117
+ import { computed, onMounted, ref, watch } from 'vue';
118
+ import { useRoute } from 'vue-router';
119
+ import { showErrorTost } from '@/composables/useFrontendApi'
320
120
 
321
121
  import ValueRenderer from '@/components/ValueRenderer.vue';
122
+ import { getCustomComponent } from '@/utils';
322
123
 
323
- import {
324
- IconChevronDoubleLeftOutline,
325
- IconChevronDoubleRightOutline,
326
- IconInboxFullSolid,
327
- IconInboxOutline,
328
- IconPlusOutline
329
- } from '@iconify-prerendered/vue-flowbite';
330
124
 
331
125
  import {
332
- IconEyeSolid,
333
- IconTrashBinSolid,
334
- IconPenSolid,
335
- IconFilterOutline, IconBanOutline
126
+ IconBanOutline,
127
+ IconFilterOutline,
128
+ IconPlusOutline
336
129
  } from '@iconify-prerendered/vue-flowbite';
337
130
 
338
131
  import Filters from '@/components/Filters.vue';
339
132
 
340
133
  const filtersShow = ref(false);
341
134
 
342
-
343
135
  const coreStore = useCoreStore();
344
- const modalStore = useModalStore();
136
+ const filtersStore = useFiltersStore();
345
137
 
346
138
  const route = useRoute();
347
- const checkboxes = ref([]);
348
139
 
349
140
  const page = ref(1);
350
- const filters = ref([]);
351
141
  const columnsMinMax = ref({});
352
142
  const sort = ref([]);
353
- const fetchStatus = ref({pending: false, error: null, success: false});
354
143
 
355
144
  const rows = ref(null);
356
145
  const totalRows = ref(0);
357
- const allCheckedActions = ref([]);
358
- const allowedActions = ref({});
359
-
146
+ const checkboxes = ref([]);
360
147
 
361
148
  const DEFAULT_PAGE_SIZE = 10;
362
149
 
363
- const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.showIn.includes('list')));
364
150
 
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
- }
151
+ const pageSize = computed(() => coreStore.resource?.options?.listPageSize || DEFAULT_PAGE_SIZE);
376
152
 
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
153
 
386
154
  watch([page], async () => {
387
- await init();
155
+ await getList();
388
156
  });
389
157
 
390
- watch([filters], async () => {
158
+ watch(()=>filtersStore.filters, async () => {
391
159
  page.value = 1;
160
+ checkboxes.value = [];
161
+
392
162
  await getList();
393
163
  }, {deep: true});
394
164
 
395
165
  watch([sort], async () => {
396
- await init();
166
+ await getList();
397
167
  }, {deep: true});
398
168
 
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
169
 
415
170
  async function getList() {
416
171
  rows.value = null;
417
- fetchStatus.value.pending = true;
418
172
  const data = await callAdminForthApi({
419
173
  path: '/get_resource_data',
420
174
  method: 'POST',
421
175
  body: {
176
+ source: 'list',
422
177
  resourceId: route.params.resourceId,
423
178
  limit: pageSize.value,
424
179
  offset: (page.value - 1) * pageSize.value,
425
- filters: filters.value,
180
+ filters: filtersStore.filters,
426
181
  sort: sort.value,
427
182
  }
428
183
  });
429
- fetchStatus.value.pending = false;
184
+ if (data.error) {
185
+ showErrorTost(data.error);
186
+ rows.value = [];
187
+ totalRows.value = 0;
188
+ return;
189
+ }
430
190
  rows.value = data.data?.map(row => {
431
- row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
191
+ row._primaryKeyValue = row[coreStore.resource.columns.find(c => c.primaryKey).name];
432
192
  return row;
433
193
  });
434
194
  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
- }
444
-
445
-
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
-
461
- }
462
-
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
195
  }
476
196
 
477
197
  async function startBulkAction(actionId) {
@@ -485,25 +205,32 @@ async function startBulkAction(actionId) {
485
205
 
486
206
  }
487
207
  });
488
- if (data?.status === 'success') {
208
+ if (data?.ok) {
489
209
  checkboxes.value = [];
210
+ await getList();
490
211
  }
491
- await getList();
492
- }
493
-
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);
212
+ if (data?.error) {
213
+ showErrorTost(data.error);
499
214
  }
500
215
  }
501
216
 
217
+
218
+
502
219
  async function init() {
503
- await coreStore.fetchColumns({
220
+
221
+ await coreStore.fetchResourceFull({
504
222
  resourceId: route.params.resourceId
505
223
  });
506
224
 
225
+ if (coreStore.resource.options?.defaultSort) {
226
+ sort.value = [{
227
+ field: coreStore.resource.options.defaultSort.columnName,
228
+ direction: coreStore.resource.options.defaultSort.direction
229
+ }];
230
+ } else {
231
+ sort.value = [];
232
+ }
233
+
507
234
  await getList();
508
235
  columnsMinMax.value = await callAdminForthApi({
509
236
  path: '/get_min_max_for_columns',
@@ -515,14 +242,16 @@ async function init() {
515
242
  }
516
243
 
517
244
  onMounted(async () => {
245
+ initFlowbite();
246
+
518
247
  await init();
248
+
519
249
  });
520
250
 
521
251
  // on route param change
522
252
  watch(() => route.params.resourceId, async () => {
523
- filters.value = [];
253
+ filtersStore.setFilters([]);
524
254
  checkboxes.value = [];
525
- sort.value = [];
526
255
  await init();
527
256
  });
528
257