adminforth 1.0.13 → 1.0.15
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.
- package/index.js +21 -0
- package/modules/codeInjector.js +59 -15
- package/package.json +1 -1
- package/spa/src/App.vue +1 -1
- package/spa/src/components/Filters.vue +1 -1
- package/spa/src/components/ResourceForm.vue +1 -1
- package/spa/src/stores/core.ts +0 -1
- package/spa/src/views/ListView.vue +272 -222
- package/spa/vite.config.ts +2 -1
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import CodeInjector from './modules/codeInjector.js';
|
|
|
7
7
|
import { guessLabelFromName } from './modules/utils.js';
|
|
8
8
|
import ExpressServer from './servers/express.js';
|
|
9
9
|
import {v1 as uuid} from 'uuid';
|
|
10
|
+
import fs from 'fs';
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
import { AdminForthFilterOperators, AdminForthTypes } from './types.js';
|
|
@@ -63,6 +64,14 @@ class AdminForth {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
if (!this.config.customization) {
|
|
68
|
+
this.config.customization = {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!this.config.customization.customComponentsDir) {
|
|
72
|
+
this.config.customization.customComponentsDir = './custom';
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
|
|
67
76
|
const errors = [];
|
|
68
77
|
if (!this.config.baseUrl) {
|
|
@@ -176,6 +185,17 @@ class AdminForth {
|
|
|
176
185
|
if (item.component && !item.path) {
|
|
177
186
|
errors.push(`Menu item with component must have path : ${JSON.stringify(item)}`);
|
|
178
187
|
}
|
|
188
|
+
// make sure component starts with @@
|
|
189
|
+
if (item.component) {
|
|
190
|
+
if (!item.component.startsWith('@@')) {
|
|
191
|
+
errors.push(`Menu item component must start with @@ : ${JSON.stringify(item)}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const path = item.component.replace('@@', this.config.customization.customComponentsDir);
|
|
195
|
+
if ( !fs.existsSync(path) ) {
|
|
196
|
+
errors.push(`Menu item component "${item.component.replace('@@', '')}" does not exist in "${this.config.customization.customComponentsDir}"`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
179
199
|
|
|
180
200
|
if (item.homepage) {
|
|
181
201
|
homepages++;
|
|
@@ -188,6 +208,7 @@ class AdminForth {
|
|
|
188
208
|
}
|
|
189
209
|
});
|
|
190
210
|
};
|
|
211
|
+
browseMenu(this.config.menu);
|
|
191
212
|
|
|
192
213
|
}
|
|
193
214
|
|
package/modules/codeInjector.js
CHANGED
|
@@ -87,7 +87,6 @@ class CodeInjector {
|
|
|
87
87
|
await fs.promises.mkdir(spaTmpPath, { recursive: true });
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const customFiles = [];
|
|
91
90
|
const icons = [];
|
|
92
91
|
let routes = '';
|
|
93
92
|
|
|
@@ -97,11 +96,10 @@ class CodeInjector {
|
|
|
97
96
|
icons.push(item.icon);
|
|
98
97
|
}
|
|
99
98
|
if (item.component) {
|
|
100
|
-
customFiles.push(item.component);
|
|
101
99
|
routes += `{
|
|
102
100
|
path: '${item.path}',
|
|
103
101
|
name: '${item.path}',
|
|
104
|
-
component: import('
|
|
102
|
+
component: import('${item.component}'),
|
|
105
103
|
},\n`
|
|
106
104
|
}
|
|
107
105
|
if (item.children) {
|
|
@@ -111,10 +109,6 @@ class CodeInjector {
|
|
|
111
109
|
};
|
|
112
110
|
collectAssetsFromMenu(this.adminforth.config.menu);
|
|
113
111
|
|
|
114
|
-
if (this.adminforth.config.customization?.vueUsesFile) {
|
|
115
|
-
customFiles.push(this.adminforth.config.customization.vueUsesFile);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
112
|
// create spa_tmp folder, or ignore if it exists
|
|
119
113
|
try {
|
|
120
114
|
await fs.promises.mkdir(spaTmpPath);
|
|
@@ -136,12 +130,12 @@ class CodeInjector {
|
|
|
136
130
|
},
|
|
137
131
|
});
|
|
138
132
|
|
|
139
|
-
// copy custom
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
133
|
+
// copy whole custom directory
|
|
134
|
+
if (this.adminforth.config.customization?.customComponentsDir) {
|
|
135
|
+
await fsExtra.copy(this.adminforth.config.customization.customComponentsDir, path.join(spaTmpPath, 'src', 'custom'), {
|
|
136
|
+
recursive: true,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
145
139
|
}
|
|
146
140
|
|
|
147
141
|
//collect all 'icon' fields from resources bulkActions
|
|
@@ -184,7 +178,7 @@ class CodeInjector {
|
|
|
184
178
|
let imports = iconImports + '\n';
|
|
185
179
|
|
|
186
180
|
if (this.adminforth.config.customization?.vueUsesFile) {
|
|
187
|
-
imports += `import addCustomUses from '
|
|
181
|
+
imports += `import addCustomUses from '${this.adminforth.config.customization.vueUsesFile}';\n`;
|
|
188
182
|
}
|
|
189
183
|
|
|
190
184
|
// inject that code into spa_tmp/src/App.vue
|
|
@@ -286,11 +280,61 @@ class CodeInjector {
|
|
|
286
280
|
});
|
|
287
281
|
}
|
|
288
282
|
|
|
283
|
+
async watchCustomComponentsForCopy() {
|
|
284
|
+
const customComponentsDir = this.adminforth.config.customization.customComponentsDir;
|
|
285
|
+
|
|
286
|
+
// check if folder exists
|
|
287
|
+
try {
|
|
288
|
+
await fs.promises.access(customComponentsDir, fs.constants.F_OK);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// get all subdirs
|
|
294
|
+
const directories = [];
|
|
295
|
+
const collectDirectories = async (dir) => {
|
|
296
|
+
directories.push(dir);
|
|
297
|
+
|
|
298
|
+
const files = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
299
|
+
for (const file of files) {
|
|
300
|
+
if (file.isDirectory()) {
|
|
301
|
+
await collectDirectories(path.join(dir, file.name));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
await collectDirectories(customComponentsDir);
|
|
307
|
+
|
|
308
|
+
const watcher = filewatcher();
|
|
309
|
+
directories.forEach((dir) => {
|
|
310
|
+
watcher.add(dir);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
watcher.on(
|
|
314
|
+
'change',
|
|
315
|
+
async (file) => {
|
|
316
|
+
// copy one file
|
|
317
|
+
// TODO: non optimal, copy only changed file, test on both nested and parent dir
|
|
318
|
+
await fsExtra.copy(this.adminforth.config.customization.customComponentsDir, path.join(spaTmpPath, 'src', 'custom'), {
|
|
319
|
+
recursive: true,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
process.on('exit', () => {
|
|
324
|
+
watcher.removeAll();
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
289
328
|
async bundleNow({hotReload = false, verbose = false}) {
|
|
290
329
|
this.adminforth.config.runningHotReload = hotReload;
|
|
291
330
|
|
|
292
331
|
await this.prepareSources({ verbose });
|
|
293
|
-
|
|
332
|
+
|
|
333
|
+
if (hotReload) {
|
|
334
|
+
await this.watchForReprepare();
|
|
335
|
+
await this.watchCustomComponentsForCopy();
|
|
336
|
+
}
|
|
337
|
+
|
|
294
338
|
console.log('AdminForth bundling');
|
|
295
339
|
|
|
296
340
|
const cwd = CodeInjector.SPA_TMP_PATH;
|
package/package.json
CHANGED
package/spa/src/App.vue
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
</button>
|
|
34
34
|
</div>
|
|
35
|
-
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600" id="dropdown-user">
|
|
35
|
+
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600 dark:shadow-xl" id="dropdown-user">
|
|
36
36
|
<div class="px-4 py-3" role="none">
|
|
37
37
|
<p class="text-sm text-gray-900 dark:text-white" role="none" v-if="coreStore.userFullname">
|
|
38
38
|
{{ coreStore.userFullname }}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<!-- drawer component -->
|
|
3
3
|
<div id="drawer-navigation"
|
|
4
4
|
|
|
5
|
-
class="fixed top-14 right-0 z-
|
|
5
|
+
class="fixed top-14 right-0 z-40 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl"
|
|
6
6
|
|
|
7
7
|
:class="show ? 'top-0 transform-none' : ''"
|
|
8
8
|
tabindex="-1" aria-labelledby="drawer-navigation-label"
|
package/spa/src/stores/core.ts
CHANGED
|
@@ -72,7 +72,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async function fetchColumns({ resourceId }) {
|
|
75
|
-
console.log('fetchColumns 1', resourceId, resourceColumnsId.value);
|
|
76
75
|
if (resourceColumnsId.value === resourceId && resourceColumns.value) {
|
|
77
76
|
// already fetched
|
|
78
77
|
return;
|
|
@@ -1,67 +1,76 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative">
|
|
3
3
|
<Teleport to="body">
|
|
4
|
-
<Filters
|
|
5
|
-
:columns="coreStore.resourceColumns"
|
|
6
|
-
v-model:filters="filters"
|
|
7
|
-
:columnsMinMax="columnsMinMax" :show="filtersShow"
|
|
4
|
+
<Filters
|
|
5
|
+
:columns="coreStore.resourceColumns"
|
|
6
|
+
v-model:filters="filters"
|
|
7
|
+
:columnsMinMax="columnsMinMax" :show="filtersShow"
|
|
8
8
|
@hide="filtersShow = false"
|
|
9
9
|
/>
|
|
10
10
|
</Teleport>
|
|
11
11
|
|
|
12
12
|
<BreadcrumbsWithButtons>
|
|
13
|
-
<button
|
|
13
|
+
<button
|
|
14
|
+
@click="()=>{checkboxes = []}"
|
|
14
15
|
v-if="checkboxes.length"
|
|
15
|
-
|
|
16
|
+
data-tooltip-target="tooltip-remove-all"
|
|
16
17
|
data-tooltip-placement="bottom"
|
|
17
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"
|
|
18
|
-
|
|
19
|
-
<IconBanOutline class="w-5 h-5 "
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
>
|
|
20
|
+
<IconBanOutline class="w-5 h-5 "/>
|
|
21
|
+
|
|
22
|
+
<div id="tooltip-remove-all" role="tooltip"
|
|
23
|
+
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">
|
|
24
|
+
Remove selection
|
|
25
|
+
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
26
|
+
</div>
|
|
25
27
|
</button>
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
<button
|
|
30
|
+
v-if="checkboxes.length" v-for="(action,i) in allCheckedActions" :key="action.id"
|
|
31
|
+
@click="startBulkAction(action.id)"
|
|
27
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"
|
|
28
33
|
: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',
|
|
29
34
|
'bg-blue-100 text-blue-800 border-blue-400 dark:bg-blue-700 dark:text-blue-400 dark:border-blue-400':action.state==='active',
|
|
30
35
|
}"
|
|
31
|
-
|
|
32
|
-
<component
|
|
36
|
+
>
|
|
37
|
+
<component
|
|
38
|
+
v-if="action.icon"
|
|
39
|
+
:is="getIcon(action.icon)"
|
|
40
|
+
class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"></component>
|
|
33
41
|
|
|
34
42
|
{{ `${action.label} (${checkboxes.length})` }}
|
|
35
43
|
</button>
|
|
36
|
-
|
|
44
|
+
|
|
45
|
+
<RouterLink
|
|
46
|
+
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
37
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"
|
|
38
48
|
>
|
|
39
|
-
<IconPlusOutline class="w-4 h-4 me-2"
|
|
40
|
-
|
|
49
|
+
<IconPlusOutline class="w-4 h-4 me-2"/>
|
|
50
|
+
Create
|
|
41
51
|
</RouterLink>
|
|
42
|
-
|
|
52
|
+
|
|
53
|
+
<button
|
|
43
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"
|
|
44
55
|
@click="()=>{filtersShow = !filtersShow}"
|
|
45
56
|
>
|
|
46
|
-
<IconFilterOutline class="w-4 h-4 me-2"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
<IconFilterOutline class="w-4 h-4 me-2"/>
|
|
58
|
+
Filter
|
|
59
|
+
<span
|
|
60
|
+
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">
|
|
50
62
|
{{ filters.length }}
|
|
51
63
|
</span>
|
|
52
64
|
</button>
|
|
53
|
-
|
|
54
|
-
|
|
55
65
|
</BreadcrumbsWithButtons>
|
|
56
66
|
|
|
57
67
|
<!-- table -->
|
|
58
|
-
<div class="relative overflow-x-auto shadow-md sm:rounded-lg">
|
|
68
|
+
<div class="relative overflow-x-auto shadow-md sm:rounded-lg dark:shadow-2xl">
|
|
59
69
|
|
|
60
|
-
|
|
61
70
|
|
|
62
71
|
<!-- skelet loader -->
|
|
63
|
-
<div
|
|
64
|
-
|
|
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">
|
|
65
74
|
<div class="flex items-center justify-between h-16">
|
|
66
75
|
<div>
|
|
67
76
|
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
@@ -69,234 +78,262 @@
|
|
|
69
78
|
</div>
|
|
70
79
|
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
71
80
|
</div>
|
|
72
|
-
|
|
81
|
+
|
|
82
|
+
<div class="flex items-center justify-between h-16 pt-4" v-for="i in new Array(10)">
|
|
73
83
|
<div>
|
|
74
84
|
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5"></div>
|
|
75
85
|
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
|
|
76
86
|
</div>
|
|
77
87
|
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
78
88
|
</div>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
89
|
<span class="sr-only">Loading...</span>
|
|
83
90
|
</div>
|
|
84
91
|
|
|
85
92
|
<table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
86
93
|
<thead class="text-xs text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 cursor-pointer">
|
|
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'
|
|
104
116
|
viewBox="0 0 24 24">
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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>
|
|
108
120
|
</div>
|
|
109
|
-
</
|
|
110
|
-
|
|
111
|
-
<th scope="col" class="px-6 py-3">
|
|
112
|
-
Actions
|
|
113
|
-
</th>
|
|
121
|
+
</div>
|
|
122
|
+
</th>
|
|
114
123
|
|
|
115
|
-
|
|
124
|
+
<th scope="col" class="px-6 py-3">
|
|
125
|
+
Actions
|
|
126
|
+
</th>
|
|
127
|
+
</tr>
|
|
116
128
|
</thead>
|
|
117
129
|
<tbody>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
</div>
|
|
128
|
-
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
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>
|
|
129
139
|
</div>
|
|
130
|
-
<div class="
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<div class="h-2
|
|
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>
|
|
136
146
|
</div>
|
|
137
|
-
<div class="
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<div class="h-2
|
|
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>
|
|
143
153
|
</div>
|
|
144
|
-
<div class="
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<div class="h-2
|
|
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>
|
|
150
160
|
</div>
|
|
151
|
-
<div class="
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<div class="h-2
|
|
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>
|
|
157
167
|
</div>
|
|
158
|
-
<
|
|
168
|
+
<div class="h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12"></div>
|
|
159
169
|
</div>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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>
|
|
168
182
|
</div>
|
|
169
183
|
|
|
170
|
-
|
|
171
|
-
|
|
184
|
+
</td>
|
|
185
|
+
</tr>
|
|
172
186
|
|
|
173
|
-
|
|
187
|
+
<tr v-else v-for="(row, rowI) in rows" :key="row.id"
|
|
174
188
|
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<
|
|
184
|
-
</
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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>
|
|
198
219
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
<RouterLink
|
|
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>
|
|
210
234
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
235
|
+
<button v-if="allowDelete"
|
|
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>
|
|
225
251
|
</tbody>
|
|
226
252
|
</table>
|
|
227
253
|
</div>
|
|
228
254
|
<!-- pagination -->
|
|
229
255
|
<div class="flex flex-col items-center mt-4 xs:flex-row xs:justify-between xs:items-center"
|
|
230
|
-
|
|
256
|
+
v-if="rows && totalRows >= pageSize && totalRows > 0"
|
|
231
257
|
>
|
|
232
|
-
|
|
233
|
-
|
|
258
|
+
<!-- Help text -->
|
|
259
|
+
<span class="text-sm text-gray-700 dark:text-gray-400">
|
|
234
260
|
Showing <span class="font-semibold text-gray-900 dark:text-white">
|
|
235
261
|
{{ (page - 1) * pageSize + 1 }}
|
|
236
262
|
</span> to <span class="font-semibold text-gray-900 dark:text-white">
|
|
237
263
|
{{ Math.min(page * pageSize, totalRows) }}
|
|
238
264
|
</span> of <span class="font-semibold text-gray-900 dark:text-white">{{
|
|
239
|
-
|
|
240
|
-
|
|
265
|
+
totalRows
|
|
266
|
+
}}</span> Entries
|
|
241
267
|
</span>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
</
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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>
|
|
277
308
|
</div>
|
|
278
309
|
</div>
|
|
279
310
|
</template>
|
|
280
311
|
|
|
281
312
|
<script setup>
|
|
282
|
-
import {
|
|
283
|
-
import {
|
|
284
|
-
import {
|
|
285
|
-
import {
|
|
286
|
-
import {
|
|
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';
|
|
287
318
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
288
|
-
import {
|
|
319
|
+
import {initFlowbite} from 'flowbite'
|
|
289
320
|
|
|
290
321
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
291
322
|
|
|
292
|
-
import {
|
|
323
|
+
import {
|
|
324
|
+
IconChevronDoubleLeftOutline,
|
|
325
|
+
IconChevronDoubleRightOutline,
|
|
326
|
+
IconInboxFullSolid,
|
|
327
|
+
IconInboxOutline,
|
|
328
|
+
IconPlusOutline
|
|
329
|
+
} from '@iconify-prerendered/vue-flowbite';
|
|
293
330
|
|
|
294
|
-
import {
|
|
295
|
-
IconEyeSolid,
|
|
331
|
+
import {
|
|
332
|
+
IconEyeSolid,
|
|
296
333
|
IconTrashBinSolid,
|
|
297
334
|
IconPenSolid,
|
|
298
|
-
IconFilterOutline,IconBanOutline
|
|
299
|
-
|
|
335
|
+
IconFilterOutline, IconBanOutline
|
|
336
|
+
} from '@iconify-prerendered/vue-flowbite';
|
|
300
337
|
|
|
301
338
|
import Filters from '@/components/Filters.vue';
|
|
302
339
|
|
|
@@ -313,6 +350,7 @@ const page = ref(1);
|
|
|
313
350
|
const filters = ref([]);
|
|
314
351
|
const columnsMinMax = ref({});
|
|
315
352
|
const sort = ref([]);
|
|
353
|
+
const fetchStatus = ref({pending: false, error: null, success: false});
|
|
316
354
|
|
|
317
355
|
const rows = ref(null);
|
|
318
356
|
const totalRows = ref(0);
|
|
@@ -325,7 +363,13 @@ const columnsListed = computed(() => coreStore.resourceColumns?.filter(c => c.sh
|
|
|
325
363
|
|
|
326
364
|
async function selectAll(value) {
|
|
327
365
|
console.log('select all');
|
|
328
|
-
rows.value.forEach((r)
|
|
366
|
+
rows.value.forEach((r) => {
|
|
367
|
+
if (!checkboxes.value.includes(r.id)) {
|
|
368
|
+
checkboxes.value.push(r.id)
|
|
369
|
+
} else {
|
|
370
|
+
checkboxes.value = checkboxes.value.filter((item) => item !== r.id)
|
|
371
|
+
}
|
|
372
|
+
});
|
|
329
373
|
// checkboxes.value = rows.value.map((v) => v.id);
|
|
330
374
|
}
|
|
331
375
|
|
|
@@ -345,17 +389,18 @@ watch([page], async () => {
|
|
|
345
389
|
watch([filters], async () => {
|
|
346
390
|
page.value = 1;
|
|
347
391
|
await getList();
|
|
348
|
-
}, {
|
|
392
|
+
}, {deep: true});
|
|
349
393
|
|
|
350
394
|
watch([sort], async () => {
|
|
351
395
|
await init();
|
|
352
|
-
}, {
|
|
396
|
+
}, {deep: true});
|
|
353
397
|
|
|
354
398
|
function onSortButtonClick(field) {
|
|
399
|
+
if (fetchStatus.value.pending) return;
|
|
355
400
|
const sortIndex = sort.value.findIndex((s) => s.field === field);
|
|
356
401
|
console.log('sortIndex', sortIndex);
|
|
357
402
|
if (sortIndex === -1) {
|
|
358
|
-
sort.value = [{
|
|
403
|
+
sort.value = [{field, direction: 'asc'}, ...sort.value];
|
|
359
404
|
} else {
|
|
360
405
|
const sortField = sort.value[sortIndex];
|
|
361
406
|
if (sortField.direction === 'asc') {
|
|
@@ -368,6 +413,7 @@ function onSortButtonClick(field) {
|
|
|
368
413
|
|
|
369
414
|
async function getList() {
|
|
370
415
|
rows.value = null;
|
|
416
|
+
fetchStatus.value.pending = true;
|
|
371
417
|
const data = await callAdminForthApi({
|
|
372
418
|
path: '/get_resource_data',
|
|
373
419
|
method: 'POST',
|
|
@@ -379,12 +425,13 @@ async function getList() {
|
|
|
379
425
|
sort: sort.value,
|
|
380
426
|
}
|
|
381
427
|
});
|
|
428
|
+
fetchStatus.value.pending = false;
|
|
382
429
|
rows.value = data.data?.map(row => {
|
|
383
430
|
row._primaryKeyValue = row[coreStore.resourceColumns.find(c => c.primaryKey).name];
|
|
384
431
|
return row;
|
|
385
432
|
});
|
|
386
433
|
totalRows.value = data.total;
|
|
387
|
-
allCheckedActions.value = data.options?.bulkActions||[];
|
|
434
|
+
allCheckedActions.value = data.options?.bulkActions || [];
|
|
388
435
|
allowDelete.value = data.options?.allowDelete;
|
|
389
436
|
|
|
390
437
|
|
|
@@ -395,7 +442,7 @@ async function getList() {
|
|
|
395
442
|
}
|
|
396
443
|
|
|
397
444
|
|
|
398
|
-
function showDeleteModal
|
|
445
|
+
function showDeleteModal(row) {
|
|
399
446
|
if (!coreStore.config?.deleteConfirmation) {
|
|
400
447
|
return deleteRecord(row);
|
|
401
448
|
}
|
|
@@ -404,11 +451,13 @@ function showDeleteModal (row){
|
|
|
404
451
|
acceptText: 'Delete',
|
|
405
452
|
cancelText: 'Cancel',
|
|
406
453
|
});
|
|
407
|
-
modalStore.setOnAcceptFunction(()=>{
|
|
454
|
+
modalStore.setOnAcceptFunction(() => {
|
|
455
|
+
return deleteRecord(row)
|
|
456
|
+
})
|
|
408
457
|
console.log('row', row);
|
|
409
458
|
modalStore.togleModal();
|
|
410
459
|
|
|
411
|
-
}
|
|
460
|
+
}
|
|
412
461
|
|
|
413
462
|
async function deleteRecord(row) {
|
|
414
463
|
await callAdminForthApi({
|
|
@@ -417,32 +466,32 @@ async function deleteRecord(row) {
|
|
|
417
466
|
body: {
|
|
418
467
|
resourceId: route.params.resourceId,
|
|
419
468
|
primaryKey: row._primaryKeyValue,
|
|
420
|
-
recordId:row.id
|
|
469
|
+
recordId: row.id
|
|
421
470
|
}
|
|
422
471
|
});
|
|
423
472
|
await getList();
|
|
424
473
|
modalStore.resetmodalState()
|
|
425
474
|
}
|
|
426
475
|
|
|
427
|
-
async function startBulkAction(actionId){
|
|
428
|
-
const data =await callAdminForthApi({
|
|
476
|
+
async function startBulkAction(actionId) {
|
|
477
|
+
const data = await callAdminForthApi({
|
|
429
478
|
path: '/start_bulk_action',
|
|
430
479
|
method: 'POST',
|
|
431
480
|
body: {
|
|
432
481
|
resourceId: route.params.resourceId,
|
|
433
482
|
actionId: actionId,
|
|
434
483
|
recordIds: checkboxes.value
|
|
435
|
-
|
|
484
|
+
|
|
436
485
|
}
|
|
437
486
|
});
|
|
438
|
-
if (data?.status === 'success'){
|
|
487
|
+
if (data?.status === 'success') {
|
|
439
488
|
checkboxes.value = [];
|
|
440
489
|
}
|
|
441
490
|
await getList();
|
|
442
491
|
}
|
|
443
492
|
|
|
444
|
-
function addToCheckedValues(id){
|
|
445
|
-
if (checkboxes.value.includes(id)){
|
|
493
|
+
function addToCheckedValues(id) {
|
|
494
|
+
if (checkboxes.value.includes(id)) {
|
|
446
495
|
checkboxes.value = checkboxes.value.filter((item) => item !== id);
|
|
447
496
|
} else {
|
|
448
497
|
checkboxes.value.push(id);
|
|
@@ -463,9 +512,10 @@ async function init() {
|
|
|
463
512
|
}
|
|
464
513
|
});
|
|
465
514
|
}
|
|
515
|
+
|
|
466
516
|
onMounted(async () => {
|
|
467
517
|
await init();
|
|
468
|
-
});
|
|
518
|
+
});
|
|
469
519
|
|
|
470
520
|
// on route param change
|
|
471
521
|
watch(() => route.params.resourceId, async () => {
|
package/spa/vite.config.ts
CHANGED
|
@@ -36,7 +36,8 @@ export default defineConfig({
|
|
|
36
36
|
],
|
|
37
37
|
resolve: {
|
|
38
38
|
alias: {
|
|
39
|
-
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
39
|
+
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
40
|
+
'@@': fileURLToPath(new URL('./src/custom', import.meta.url)),
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
})
|