adminforth 1.1.3 → 1.1.5
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/dist/index.js +6 -0
- package/dist/modules/styles.js +7 -6
- package/dist/plugins/ForeignInlineListPlugin/index.js +32 -1
- package/dist/plugins/base.js +4 -0
- package/dist/spa/spa/src/App.vue +2 -2
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/spa/src/components/Filters.vue +19 -15
- package/dist/spa/spa/src/components/ResourceForm.vue +12 -12
- package/dist/spa/spa/src/components/ResourceListTable.vue +29 -15
- package/dist/spa/spa/src/composables/useStores.ts +63 -3
- package/dist/spa/spa/src/stores/core.ts +7 -0
- package/dist/spa/spa/src/stores/filters.ts +22 -0
- package/dist/spa/spa/src/stores/modal.ts +0 -1
- package/dist/spa/spa/src/views/CreateView.vue +8 -3
- package/dist/spa/spa/src/views/EditView.vue +2 -2
- package/dist/spa/spa/src/views/ListView.vue +22 -22
- package/dist/spa/spa/src/views/ShowView.vue +3 -3
- package/index.ts +10 -0
- package/modules/styles.ts +7 -6
- package/package.json +1 -1
- package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +107 -16
- package/plugins/ForeignInlineListPlugin/index.ts +32 -5
- package/plugins/base.ts +7 -0
- package/spa/src/App.vue +2 -2
- package/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/spa/src/components/Filters.vue +19 -15
- package/spa/src/components/ResourceForm.vue +12 -12
- package/spa/src/components/ResourceListTable.vue +29 -15
- package/spa/src/composables/useStores.ts +63 -3
- package/spa/src/stores/core.ts +7 -0
- package/spa/src/stores/filters.ts +22 -0
- package/spa/src/stores/modal.ts +0 -1
- package/spa/src/views/CreateView.vue +8 -3
- package/spa/src/views/EditView.vue +2 -2
- package/spa/src/views/ListView.vue +22 -22
- package/spa/src/views/ShowView.vue +3 -3
- package/types/AdminForthConfig.ts +32 -1
- package/types/FrontendAPI.ts +47 -0
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ class AdminForth {
|
|
|
35
35
|
});
|
|
36
36
|
this.config = Object.assign(Object.assign({}, __classPrivateFieldGet(this, _AdminForth_defaultConfig, "f")), config);
|
|
37
37
|
this.codeInjector = new CodeInjector(this);
|
|
38
|
+
this.activatedPlugins = [];
|
|
38
39
|
this.validateConfig();
|
|
39
40
|
this.activatePlugins();
|
|
40
41
|
this.validateConfig(); // revalidate after plugins
|
|
@@ -48,6 +49,7 @@ class AdminForth {
|
|
|
48
49
|
for (let resource of this.config.resources) {
|
|
49
50
|
for (let pluginInstance of resource.plugins || []) {
|
|
50
51
|
pluginInstance.modifyResourceConfig(this, resource);
|
|
52
|
+
this.activatedPlugins.push(pluginInstance);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
;
|
|
@@ -950,6 +952,10 @@ class AdminForth {
|
|
|
950
952
|
};
|
|
951
953
|
})
|
|
952
954
|
});
|
|
955
|
+
// setup endpoints for all plugins
|
|
956
|
+
this.activatedPlugins.forEach((plugin) => {
|
|
957
|
+
plugin.setupEndpoints(server);
|
|
958
|
+
});
|
|
953
959
|
}
|
|
954
960
|
}
|
|
955
961
|
_a = AdminForth, _AdminForth_defaultConfig = new WeakMap();
|
package/dist/modules/styles.js
CHANGED
|
@@ -2,19 +2,20 @@ export const styles = () => `
|
|
|
2
2
|
|
|
3
3
|
"colors": {
|
|
4
4
|
"html-bg": "#FFFFFF", // Changed to white
|
|
5
|
-
"header-bg": "#
|
|
5
|
+
"header-bg": "#FFFFFF", // Light grey
|
|
6
6
|
"header-bg-hover": "#D3D3D3", // Light grey hover
|
|
7
7
|
"header-text": "#333333", // Dark grey text
|
|
8
8
|
"header-text-hover": "#333333", // Dark grey text hover
|
|
9
9
|
"header-icons": "#333333", // Dark grey icons
|
|
10
10
|
"header-icons-hover": "#333333", // Dark grey icons hover
|
|
11
11
|
"header-logo-color": "#333333", // Dark grey logo
|
|
12
|
-
"header-border-color": "
|
|
12
|
+
"header-border-color": "rgb(229 231 235)", // Light grey border
|
|
13
13
|
|
|
14
|
-
"nav-menu-bg": "#
|
|
15
|
-
"nav-menu-
|
|
16
|
-
"nav-menu-bg-
|
|
17
|
-
"nav-menu-
|
|
14
|
+
"nav-menu-bg": "#f9fafb", // Very light grey
|
|
15
|
+
"nav-menu-border": "rgb(229 231 235)",
|
|
16
|
+
"nav-menu-bg-hover": "rgb(243 244 246)", // Light grey hover
|
|
17
|
+
"nav-menu-bg-active": "rgb(233, 234, 236)", // Light grey active
|
|
18
|
+
"nav-menu-hover": "#rgb(209 213 219)", // Light grey hover
|
|
18
19
|
"nav-menu-active": "#CCCCCC", // Light grey active
|
|
19
20
|
"nav-menu-active-text": "#333333", // Dark grey active text
|
|
20
21
|
"nav-menu-text": "#333333", // Dark grey text
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { AdminForthResourcePages } from "../../types/AdminForthConfig.js";
|
|
2
11
|
import AdminForthPlugin from "../base.js";
|
|
3
12
|
export default class ForeignInlineListPlugin extends AdminForthPlugin {
|
|
@@ -5,8 +14,27 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
|
|
|
5
14
|
super(options, import.meta.url);
|
|
6
15
|
this.options = options;
|
|
7
16
|
}
|
|
17
|
+
setupEndpoints(server) {
|
|
18
|
+
server.endpoint({
|
|
19
|
+
method: 'POST',
|
|
20
|
+
path: `/plugin/${this.pluginInstanceId}/get_resource`,
|
|
21
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
22
|
+
const resource = this.adminforth.config.resources.find((res) => this.options.foreignResourceId === res.resourceId);
|
|
23
|
+
if (!resource) {
|
|
24
|
+
return { error: `Resource ${this.options.foreignResourceId} not found` };
|
|
25
|
+
}
|
|
26
|
+
// exclude "plugins" key
|
|
27
|
+
const resourceCopy = Object.assign(Object.assign({}, resource), { plugins: undefined });
|
|
28
|
+
if (this.options.modifyTableResourceConfig) {
|
|
29
|
+
this.options.modifyTableResourceConfig(resourceCopy);
|
|
30
|
+
}
|
|
31
|
+
return { resource: resourceCopy };
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
8
35
|
modifyResourceConfig(adminforth, resourceConfig) {
|
|
9
36
|
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
37
|
+
this.adminforth = adminforth;
|
|
10
38
|
// get resource with foreignResourceId
|
|
11
39
|
this.foreignResource = adminforth.config.resources.find((resource) => resource.resourceId === this.options.foreignResourceId);
|
|
12
40
|
if (!this.foreignResource) {
|
|
@@ -19,7 +47,10 @@ export default class ForeignInlineListPlugin extends AdminForthPlugin {
|
|
|
19
47
|
virtual: true,
|
|
20
48
|
showIn: [AdminForthResourcePages.show],
|
|
21
49
|
components: {
|
|
22
|
-
showRow: {
|
|
50
|
+
showRow: {
|
|
51
|
+
file: this.componentPath('InlineList.vue'),
|
|
52
|
+
meta: Object.assign(Object.assign({}, this.options), { pluginInstanceId: this.pluginInstanceId })
|
|
53
|
+
}
|
|
23
54
|
},
|
|
24
55
|
});
|
|
25
56
|
}
|
package/dist/plugins/base.js
CHANGED
|
@@ -2,11 +2,15 @@ import { getComponentNameFromPath } from '../modules/utils.js';
|
|
|
2
2
|
import { currentFileDir } from '../modules/utils.js';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
6
|
export default class AdminForthPlugin {
|
|
6
7
|
constructor(pluginOptions, metaUrl) {
|
|
7
8
|
this.customFolderName = 'custom';
|
|
8
9
|
// set up plugin here
|
|
9
10
|
this.pluginDir = currentFileDir(metaUrl);
|
|
11
|
+
this.pluginInstanceId = uuidv4();
|
|
12
|
+
}
|
|
13
|
+
setupEndpoints(server) {
|
|
10
14
|
}
|
|
11
15
|
modifyResourceConfig(adminforth, resourceConfig) {
|
|
12
16
|
this.adminforth = adminforth;
|
package/dist/spa/spa/src/App.vue
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady"
|
|
61
61
|
|
|
62
|
-
id="logo-sidebar" class="fixed border-none top-0 left-0 z-
|
|
62
|
+
id="logo-sidebar" class="fixed border-none top-0 left-0 z-20 w-64 h-screen transition-transform bg-nav-menu-bg border-r border-nav-menu-border sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700"
|
|
63
63
|
:class="{ '-translate-x-full': !sideBarOpen, 'transform-none': sideBarOpen }"
|
|
64
64
|
aria-label="Sidebar"
|
|
65
65
|
>
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
<div v-if="sideBarOpen"
|
|
137
137
|
@click="sideBarOpen = false"
|
|
138
138
|
|
|
139
|
-
drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-
|
|
139
|
+
drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-5"></div>
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
v-if="c.foreignResource"
|
|
27
27
|
:options="columnOptions[c.name] || []"
|
|
28
28
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
29
|
-
:modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
29
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
30
30
|
/>
|
|
31
31
|
<Dropdown
|
|
32
32
|
v-else-if="c.type === 'boolean'"
|
|
33
33
|
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
34
34
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
35
|
-
:modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
35
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
36
36
|
/>
|
|
37
37
|
|
|
38
38
|
<Dropdown
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
:options="c.enum"
|
|
41
41
|
:allowCustom="c.allowCustom"
|
|
42
42
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
43
|
-
:modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
43
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
44
44
|
/>
|
|
45
45
|
|
|
46
46
|
<input
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
<CustomDateRangePicker
|
|
55
55
|
v-else-if="['datetime'].includes(c.type)"
|
|
56
56
|
:column="c"
|
|
57
|
-
:valueStart="filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
|
|
57
|
+
:valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
|
|
58
58
|
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
|
|
59
|
-
:valueEnd="filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
|
|
59
|
+
:valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
|
|
60
60
|
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
61
61
|
/>
|
|
62
62
|
|
|
@@ -101,9 +101,9 @@
|
|
|
101
101
|
|
|
102
102
|
<div class="flex justify-end gap-2">
|
|
103
103
|
<button
|
|
104
|
-
:disabled="!filters.length"
|
|
104
|
+
:disabled="!filtersStore.filters.length"
|
|
105
105
|
type="button"
|
|
106
|
-
class="flex items-center py-1 px-3
|
|
106
|
+
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 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
107
107
|
@click="clear">Clear all</button>
|
|
108
108
|
|
|
109
109
|
</div>
|
|
@@ -122,6 +122,10 @@ import { callAdminForthApi } from '@/utils';
|
|
|
122
122
|
import { useRouter } from 'vue-router';
|
|
123
123
|
import { computedAsync } from '@vueuse/core'
|
|
124
124
|
import CustomRangePicker from "@/components/CustomRangePicker.vue";
|
|
125
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
126
|
+
|
|
127
|
+
const filtersStore = useFiltersStore();
|
|
128
|
+
|
|
125
129
|
|
|
126
130
|
// props: columns
|
|
127
131
|
// add support for v-model:filers
|
|
@@ -180,28 +184,28 @@ watch(() => props.show, (show) => {
|
|
|
180
184
|
|
|
181
185
|
function setFilterItem({ column, operator, value }) {
|
|
182
186
|
|
|
183
|
-
const index =
|
|
187
|
+
const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
|
|
184
188
|
if (value === undefined) {
|
|
185
189
|
if (index !== -1) {
|
|
186
|
-
|
|
190
|
+
filtersStore.filters.splice(index, 1);
|
|
187
191
|
}
|
|
188
|
-
emits('update:filters', [...
|
|
192
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
189
193
|
return;
|
|
190
194
|
} else {
|
|
191
195
|
if (index === -1) {
|
|
192
|
-
|
|
196
|
+
filtersStore.setFilter({ field: column.name, value, operator });
|
|
193
197
|
} else {
|
|
194
|
-
|
|
198
|
+
filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
|
-
emits('update:filters', [...
|
|
201
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
function getFilterItem({ column, operator }) {
|
|
201
|
-
return
|
|
205
|
+
return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
async function clear() {
|
|
205
|
-
|
|
209
|
+
filtersStore.clearFilters();
|
|
206
210
|
}
|
|
207
211
|
</script>
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
/>
|
|
31
31
|
</template>
|
|
32
32
|
<template v-else>
|
|
33
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
<td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
|
|
34
|
+
{{ column.label }}
|
|
35
|
+
<span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
|
|
36
|
+
<IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
|
|
37
|
+
:class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
|
|
38
|
+
/>
|
|
39
|
+
</span>
|
|
40
|
+
<div :id="`tooltip-show-${i}`"
|
|
41
|
+
role="tooltip" 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">
|
|
42
|
+
Required field
|
|
43
|
+
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
44
|
+
</div>
|
|
45
45
|
</td>
|
|
46
46
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
|
|
47
47
|
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
<input
|
|
126
126
|
id="checkbox-table-search-1"
|
|
127
127
|
type="checkbox"
|
|
128
|
-
:checked="
|
|
128
|
+
:checked="checkboxesInternal.includes(row.id)"
|
|
129
129
|
@change="(e)=>{addToCheckedValues(row.id)}"
|
|
130
130
|
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">
|
|
131
131
|
<label for="checkbox-table-search-1" class="sr-only">checkbox</label>
|
|
@@ -161,10 +161,12 @@
|
|
|
161
161
|
</div>
|
|
162
162
|
|
|
163
163
|
<RouterLink v-if="resource.options?.allowedActions.edit"
|
|
164
|
-
:to="{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
:to="{
|
|
165
|
+
name: 'resource-edit',
|
|
166
|
+
params: {
|
|
167
|
+
resourceId: resource.id,
|
|
168
|
+
primaryKey: row._primaryKeyValue
|
|
169
|
+
}
|
|
168
170
|
}"
|
|
169
171
|
class="font-medium text-blue-600 dark:text-blue-500 hover:underline ms-3"
|
|
170
172
|
:data-tooltip-target="`tooltip-edit-${rowI}`"
|
|
@@ -239,6 +241,7 @@
|
|
|
239
241
|
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"
|
|
240
242
|
@click="page = totalPages" :disabled="page >= totalPages">
|
|
241
243
|
{{ totalPages }}
|
|
244
|
+
|
|
242
245
|
<!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
|
|
243
246
|
</button>
|
|
244
247
|
<button
|
|
@@ -253,6 +256,7 @@
|
|
|
253
256
|
</svg>
|
|
254
257
|
</button>
|
|
255
258
|
</div>
|
|
259
|
+
|
|
256
260
|
</div>
|
|
257
261
|
</template>
|
|
258
262
|
|
|
@@ -287,6 +291,7 @@ const props = defineProps([
|
|
|
287
291
|
'rows',
|
|
288
292
|
'totalRows',
|
|
289
293
|
'pageSize',
|
|
294
|
+
'checkboxes'
|
|
290
295
|
])
|
|
291
296
|
|
|
292
297
|
// emits, update page
|
|
@@ -297,7 +302,7 @@ const emits = defineEmits([
|
|
|
297
302
|
|
|
298
303
|
]);
|
|
299
304
|
|
|
300
|
-
const
|
|
305
|
+
const checkboxesInternal = ref([]);
|
|
301
306
|
const page = ref(1);
|
|
302
307
|
const sort = ref([]);
|
|
303
308
|
|
|
@@ -310,32 +315,41 @@ watch(() => sort.value, (newSort) => {
|
|
|
310
315
|
emits('update:sort', newSort);
|
|
311
316
|
});
|
|
312
317
|
|
|
313
|
-
watch(() =>
|
|
318
|
+
watch(() => checkboxesInternal.value, (newCheckboxes) => {
|
|
319
|
+
console.log('checkboxesInternal ch changed, emiting', newCheckboxes)
|
|
320
|
+
|
|
314
321
|
emits('update:checkboxes', newCheckboxes);
|
|
315
322
|
});
|
|
316
323
|
|
|
324
|
+
watch(() => props.checkboxes, (newCheckboxes) => {
|
|
325
|
+
console.log('Props ch changed', newCheckboxes)
|
|
326
|
+
checkboxesInternal.value = newCheckboxes;
|
|
327
|
+
});
|
|
328
|
+
|
|
317
329
|
function addToCheckedValues(id) {
|
|
318
|
-
if (
|
|
319
|
-
|
|
330
|
+
if (checkboxesInternal.value.includes(id)) {
|
|
331
|
+
checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
|
|
320
332
|
} else {
|
|
321
|
-
|
|
333
|
+
checkboxesInternal.value.push(id);
|
|
322
334
|
}
|
|
335
|
+
checkboxesInternal.value = [ ...checkboxesInternal.value ]
|
|
323
336
|
}
|
|
324
337
|
|
|
325
338
|
const columnsListed = computed(() => props.resource?.columns?.filter(c => c.showIn.includes('list')));
|
|
326
339
|
|
|
327
340
|
async function selectAll(value) {
|
|
328
|
-
rows.
|
|
329
|
-
if (!
|
|
330
|
-
|
|
341
|
+
props.rows.forEach((r) => {
|
|
342
|
+
if (!checkboxesInternal.value.includes(r.id)) {
|
|
343
|
+
checkboxesInternal.value.push(r.id)
|
|
331
344
|
} else {
|
|
332
|
-
|
|
345
|
+
checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r.id)
|
|
333
346
|
}
|
|
334
347
|
});
|
|
348
|
+
checkboxesInternal.value = [ ...checkboxesInternal.value ]
|
|
335
349
|
// checkboxes.value = rows.value.map((v) => v.id);
|
|
336
350
|
}
|
|
337
351
|
|
|
338
|
-
const totalPages = computed(() => Math.ceil(props.totalRows
|
|
352
|
+
const totalPages = computed(() => Math.ceil(props.totalRows / props.pageSize));
|
|
339
353
|
|
|
340
354
|
|
|
341
355
|
const allFromThisPageChecked = computed(() => {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type { FrontendAPIInterface, ConfirmParams, AlertParams } from '../types/FrontendAPI';
|
|
1
|
+
import type { FrontendAPIInterface, ConfirmParams, AlertParams, FilterParams,Operator } from '../types/FrontendAPI';
|
|
2
2
|
import { useToastStore } from '../stores/toast';
|
|
3
3
|
import { useModalStore } from '../stores/modal';
|
|
4
|
+
import { useCoreStore } from '@/stores/core';
|
|
5
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
6
|
+
import router from '@/router'
|
|
7
|
+
|
|
8
|
+
|
|
4
9
|
|
|
5
10
|
|
|
6
11
|
|
|
@@ -9,13 +14,19 @@ declare global {
|
|
|
9
14
|
adminforth: {
|
|
10
15
|
confirm: (params: ConfirmParams) => Promise<void>;
|
|
11
16
|
alert: (params: AlertParams) => void;
|
|
17
|
+
setListFilter: (filter: any) => void;
|
|
18
|
+
updateListFilter: (filter: any) => void;
|
|
19
|
+
clearListFilters: () => void;
|
|
12
20
|
};
|
|
13
21
|
}
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
export class FrontendAPI implements FrontendAPIInterface {
|
|
25
|
+
private validOperators: Operator[] = ['lte', 'gte', 'in','ilike'];
|
|
17
26
|
private toastStore:any
|
|
18
27
|
private modalStore:any
|
|
28
|
+
private filtersStore:any
|
|
29
|
+
private coreStore:any
|
|
19
30
|
init() {
|
|
20
31
|
if (window.adminforth) {
|
|
21
32
|
throw new Error('adminforth already initialized');
|
|
@@ -25,7 +36,10 @@ export class FrontendAPI implements FrontendAPIInterface {
|
|
|
25
36
|
console.log(this.toastStore, this.modalStore,'init of adminforth frontend api')
|
|
26
37
|
window.adminforth = {
|
|
27
38
|
confirm: this.confirm.bind(this),
|
|
28
|
-
alert: this.alert.bind(this)
|
|
39
|
+
alert: this.alert.bind(this),
|
|
40
|
+
setListFilter: this.setListFilter.bind(this),
|
|
41
|
+
updateListFilter: this.updateListFilter.bind(this),
|
|
42
|
+
clearListFilters: this.clearListFilters.bind(this),
|
|
29
43
|
}
|
|
30
44
|
}
|
|
31
45
|
|
|
@@ -44,8 +58,54 @@ export class FrontendAPI implements FrontendAPIInterface {
|
|
|
44
58
|
variant: params.variant
|
|
45
59
|
})
|
|
46
60
|
}
|
|
47
|
-
}
|
|
48
61
|
|
|
62
|
+
listFilterValidation(filter: FilterParams): boolean {
|
|
63
|
+
if(router.currentRoute.value.meta.type !== 'list'){
|
|
64
|
+
throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
|
|
65
|
+
} else {
|
|
66
|
+
if(!this.coreStore) this.coreStore = useCoreStore()
|
|
67
|
+
console.log(this.coreStore.resourceColumnsWithFilters,'core store')
|
|
68
|
+
const filterField = this.coreStore.resourceColumnsWithFilters.find((col) => col.name === filter.field)
|
|
69
|
+
if(!filterField){
|
|
70
|
+
throw new Error(`Field ${filter.field} is not available for filtering`)
|
|
71
|
+
}
|
|
72
|
+
if(filterField) {
|
|
73
|
+
if(!this.validOperators.includes(filter.operator)){
|
|
74
|
+
throw new Error(`Operator ${filter.operator} is not valid`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setListFilter(filter: FilterParams): void {
|
|
82
|
+
if(this.listFilterValidation(filter)){
|
|
83
|
+
this.filtersStore = useFiltersStore()
|
|
84
|
+
if(this.filtersStore.filters.some((f) => {return f.field === filter.field && f.operator === filter.operator})){
|
|
85
|
+
throw new Error(`Filter ${filter.field} with operator ${filter.operator} already exists`)
|
|
86
|
+
} else {
|
|
87
|
+
this.filtersStore.setFilter(filter)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
clearListFilters(): void {
|
|
93
|
+
this.filtersStore = useFiltersStore()
|
|
94
|
+
this.filtersStore.clearFilters()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
updateListFilter(filter: FilterParams): void {
|
|
98
|
+
if(this.listFilterValidation(filter)){
|
|
99
|
+
this.filtersStore = useFiltersStore()
|
|
100
|
+
const index = this.filtersStore.filters.findIndex((f) => f.field === filter.field)
|
|
101
|
+
if(index === -1) {
|
|
102
|
+
this.filtersStore.setFilter(filter)
|
|
103
|
+
} else {
|
|
104
|
+
this.filtersStore.setFilters([...this.filtersStore.filters.slice(0, index), filter, ...this.filtersStore.filters.slice(index + 1)])
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
49
108
|
|
|
50
109
|
|
|
51
110
|
|
|
111
|
+
}
|
|
@@ -9,6 +9,12 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
9
9
|
const record = ref({});
|
|
10
10
|
const resourceColumns = ref(null);
|
|
11
11
|
const resource = ref(null);
|
|
12
|
+
const resourceColumnsWithFilters = computed(() => {
|
|
13
|
+
if (!resourceColumns.value) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return resourceColumns.value.filter((col) => col.showIn.includes('filter'));
|
|
17
|
+
})
|
|
12
18
|
|
|
13
19
|
const resourceOptions = ref(null);
|
|
14
20
|
const resourceColumnsError = ref('');
|
|
@@ -132,5 +138,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
132
138
|
logout,
|
|
133
139
|
resource,
|
|
134
140
|
adminUser,
|
|
141
|
+
resourceColumnsWithFilters
|
|
135
142
|
}
|
|
136
143
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { callAdminForthApi } from '@/utils';
|
|
4
|
+
|
|
5
|
+
export const useFiltersStore = defineStore('filters', () => {
|
|
6
|
+
const filters = ref([]);
|
|
7
|
+
const setFilter = (filter: any) => {
|
|
8
|
+
filters.value = [...filters.value, filter];
|
|
9
|
+
}
|
|
10
|
+
const setFilters = (f: any) => {
|
|
11
|
+
filters.value = [...f];
|
|
12
|
+
}
|
|
13
|
+
const getFilters = () => {
|
|
14
|
+
return filters.value;
|
|
15
|
+
}
|
|
16
|
+
const clearFilters = () => {
|
|
17
|
+
filters.value = [];
|
|
18
|
+
}
|
|
19
|
+
return {setFilter, getFilters,clearFilters, filters,setFilters}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
<BreadcrumbsWithButtons>
|
|
14
14
|
<!-- save and cancle -->
|
|
15
15
|
<button @click="$router.back()"
|
|
16
|
-
class="flex items-center py-1 px-3 me-2
|
|
16
|
+
class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default 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"
|
|
17
17
|
>
|
|
18
18
|
Cancel
|
|
19
19
|
</button>
|
|
20
20
|
|
|
21
21
|
<button @click="saveRecord"
|
|
22
|
-
class="flex items-center py-1 px-3
|
|
22
|
+
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50"
|
|
23
23
|
:disabled="saving || (validating && !isValid)"
|
|
24
24
|
>
|
|
25
25
|
<svg v-if="saving" x
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
<ResourceForm
|
|
47
47
|
v-else
|
|
48
|
-
:record="
|
|
48
|
+
:record="initalValues"
|
|
49
49
|
:resourceColumns="coreStore.resourceColumns"
|
|
50
50
|
@update:record="onUpdateRecord"
|
|
51
51
|
@update:isValid="isValid = $event"
|
|
@@ -77,6 +77,7 @@ import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions } from
|
|
|
77
77
|
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
|
|
78
78
|
import { onMounted, ref } from 'vue';
|
|
79
79
|
import { useRoute, useRouter } from 'vue-router';
|
|
80
|
+
import { computed } from 'vue';
|
|
80
81
|
|
|
81
82
|
|
|
82
83
|
const isValid = ref(false);
|
|
@@ -93,6 +94,10 @@ let createComponentsPerColumn = {};
|
|
|
93
94
|
|
|
94
95
|
const coreStore = useCoreStore();
|
|
95
96
|
|
|
97
|
+
const initalValues = computed(() => {
|
|
98
|
+
return route.query || {};
|
|
99
|
+
});
|
|
100
|
+
|
|
96
101
|
async function onUpdateRecord(newRecord) {
|
|
97
102
|
console.log('newRecord', newRecord);
|
|
98
103
|
record.value = newRecord;
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
<BreadcrumbsWithButtons>
|
|
13
13
|
<!-- save and cancle -->
|
|
14
14
|
<button @click="$router.back()"
|
|
15
|
-
class="flex items-center py-1 px-3 me-2
|
|
15
|
+
class="flex items-center py-1 px-3 me-2 text-sm font-medium text-gray-900 rounded-default 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"
|
|
16
16
|
>
|
|
17
17
|
Cancel
|
|
18
18
|
</button>
|
|
19
19
|
|
|
20
20
|
<button @click="saveRecord"
|
|
21
|
-
class="flex items-center py-1 px-3
|
|
21
|
+
class="flex items-center py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-red-500 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
|
22
22
|
:disabled="saving || (validating && !isValid)"
|
|
23
23
|
>
|
|
24
24
|
<IconFloppyDiskSolid class="w-4 h-4" />
|