adminforth 1.1.4 → 1.1.6
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 +15 -15
- package/dist/spa/spa/src/components/ResourceListTable.vue +36 -16
- package/dist/spa/spa/src/composables/useStores.ts +64 -3
- package/dist/spa/spa/src/stores/core.ts +17 -11
- 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 +24 -10
- package/dist/spa/spa/src/views/EditView.vue +5 -5
- package/dist/spa/spa/src/views/ListView.vue +25 -23
- package/dist/spa/spa/src/views/ShowView.vue +5 -5
- package/index.ts +10 -0
- package/modules/styles.ts +7 -6
- package/package.json +1 -1
- package/plugins/ForeignInlineListPlugin/custom/InlineList.vue +138 -14
- 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 +15 -15
- package/spa/src/components/ResourceListTable.vue +36 -16
- package/spa/src/composables/useStores.ts +64 -3
- package/spa/src/stores/core.ts +17 -11
- package/spa/src/stores/filters.ts +22 -0
- package/spa/src/stores/modal.ts +0 -1
- package/spa/src/views/CreateView.vue +24 -10
- package/spa/src/views/EditView.vue +5 -5
- package/spa/src/views/ListView.vue +25 -23
- package/spa/src/views/ShowView.vue +5 -5
- 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
|
|
|
@@ -152,7 +152,7 @@ const router = useRouter();
|
|
|
152
152
|
|
|
153
153
|
const props = defineProps({
|
|
154
154
|
loading: Boolean,
|
|
155
|
-
|
|
155
|
+
resource: Object,
|
|
156
156
|
record: Object,
|
|
157
157
|
validating: Boolean,
|
|
158
158
|
customComponentsPerColumn: Object,
|
|
@@ -228,7 +228,7 @@ onMounted(() => {
|
|
|
228
228
|
|
|
229
229
|
const columnOptions = computedAsync(async () => {
|
|
230
230
|
return (await Promise.all(
|
|
231
|
-
Object.values(props.
|
|
231
|
+
Object.values(props.resource.columns).map(async (column) => {
|
|
232
232
|
if (column.foreignResource) {
|
|
233
233
|
const list = await callAdminForthApi({
|
|
234
234
|
method: 'POST',
|
|
@@ -251,7 +251,7 @@ const coreStore = useCoreStore();
|
|
|
251
251
|
|
|
252
252
|
const editableColumns = computed(() => {
|
|
253
253
|
const mode = props.record ? 'edit' : 'create';
|
|
254
|
-
return props.
|
|
254
|
+
return props.resource?.columns?.filter(column => column.showIn.includes(mode));
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
const isValid = computed(() => {
|
|
@@ -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>
|
|
@@ -146,7 +146,13 @@
|
|
|
146
146
|
<div class="flex">
|
|
147
147
|
<RouterLink
|
|
148
148
|
v-if="resource.options?.allowedActions.show"
|
|
149
|
-
:to="{
|
|
149
|
+
:to="{
|
|
150
|
+
name: 'resource-show',
|
|
151
|
+
params: {
|
|
152
|
+
resourceId: resource.resourceId,
|
|
153
|
+
primaryKey: row._primaryKeyValue,
|
|
154
|
+
}
|
|
155
|
+
}"
|
|
150
156
|
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
|
151
157
|
:data-tooltip-target="`tooltip-show-${rowI}`"
|
|
152
158
|
>
|
|
@@ -161,10 +167,12 @@
|
|
|
161
167
|
</div>
|
|
162
168
|
|
|
163
169
|
<RouterLink v-if="resource.options?.allowedActions.edit"
|
|
164
|
-
:to="{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
:to="{
|
|
171
|
+
name: 'resource-edit',
|
|
172
|
+
params: {
|
|
173
|
+
resourceId: resource.resourceId,
|
|
174
|
+
primaryKey: row._primaryKeyValue
|
|
175
|
+
}
|
|
168
176
|
}"
|
|
169
177
|
class="font-medium text-blue-600 dark:text-blue-500 hover:underline ms-3"
|
|
170
178
|
:data-tooltip-target="`tooltip-edit-${rowI}`"
|
|
@@ -239,6 +247,7 @@
|
|
|
239
247
|
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
248
|
@click="page = totalPages" :disabled="page >= totalPages">
|
|
241
249
|
{{ totalPages }}
|
|
250
|
+
|
|
242
251
|
<!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
|
|
243
252
|
</button>
|
|
244
253
|
<button
|
|
@@ -253,6 +262,7 @@
|
|
|
253
262
|
</svg>
|
|
254
263
|
</button>
|
|
255
264
|
</div>
|
|
265
|
+
|
|
256
266
|
</div>
|
|
257
267
|
</template>
|
|
258
268
|
|
|
@@ -287,6 +297,7 @@ const props = defineProps([
|
|
|
287
297
|
'rows',
|
|
288
298
|
'totalRows',
|
|
289
299
|
'pageSize',
|
|
300
|
+
'checkboxes'
|
|
290
301
|
])
|
|
291
302
|
|
|
292
303
|
// emits, update page
|
|
@@ -297,7 +308,7 @@ const emits = defineEmits([
|
|
|
297
308
|
|
|
298
309
|
]);
|
|
299
310
|
|
|
300
|
-
const
|
|
311
|
+
const checkboxesInternal = ref([]);
|
|
301
312
|
const page = ref(1);
|
|
302
313
|
const sort = ref([]);
|
|
303
314
|
|
|
@@ -310,32 +321,41 @@ watch(() => sort.value, (newSort) => {
|
|
|
310
321
|
emits('update:sort', newSort);
|
|
311
322
|
});
|
|
312
323
|
|
|
313
|
-
watch(() =>
|
|
324
|
+
watch(() => checkboxesInternal.value, (newCheckboxes) => {
|
|
325
|
+
console.log('checkboxesInternal ch changed, emiting', newCheckboxes)
|
|
326
|
+
|
|
314
327
|
emits('update:checkboxes', newCheckboxes);
|
|
315
328
|
});
|
|
316
329
|
|
|
330
|
+
watch(() => props.checkboxes, (newCheckboxes) => {
|
|
331
|
+
console.log('Props ch changed', newCheckboxes)
|
|
332
|
+
checkboxesInternal.value = newCheckboxes;
|
|
333
|
+
});
|
|
334
|
+
|
|
317
335
|
function addToCheckedValues(id) {
|
|
318
|
-
if (
|
|
319
|
-
|
|
336
|
+
if (checkboxesInternal.value.includes(id)) {
|
|
337
|
+
checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== id);
|
|
320
338
|
} else {
|
|
321
|
-
|
|
339
|
+
checkboxesInternal.value.push(id);
|
|
322
340
|
}
|
|
341
|
+
checkboxesInternal.value = [ ...checkboxesInternal.value ]
|
|
323
342
|
}
|
|
324
343
|
|
|
325
344
|
const columnsListed = computed(() => props.resource?.columns?.filter(c => c.showIn.includes('list')));
|
|
326
345
|
|
|
327
346
|
async function selectAll(value) {
|
|
328
|
-
rows.
|
|
329
|
-
if (!
|
|
330
|
-
|
|
347
|
+
props.rows.forEach((r) => {
|
|
348
|
+
if (!checkboxesInternal.value.includes(r.id)) {
|
|
349
|
+
checkboxesInternal.value.push(r.id)
|
|
331
350
|
} else {
|
|
332
|
-
|
|
351
|
+
checkboxesInternal.value = checkboxesInternal.value.filter((item) => item !== r.id)
|
|
333
352
|
}
|
|
334
353
|
});
|
|
354
|
+
checkboxesInternal.value = [ ...checkboxesInternal.value ]
|
|
335
355
|
// checkboxes.value = rows.value.map((v) => v.id);
|
|
336
356
|
}
|
|
337
357
|
|
|
338
|
-
const totalPages = computed(() => Math.ceil(props.totalRows
|
|
358
|
+
const totalPages = computed(() => Math.ceil(props.totalRows / props.pageSize));
|
|
339
359
|
|
|
340
360
|
|
|
341
361
|
const allFromThisPageChecked = computed(() => {
|
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
import type { AdminForthResourceColumn } from '@/types/AdminForthConfig';
|
|
8
|
+
|
|
9
|
+
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
|
|
@@ -9,13 +15,19 @@ declare global {
|
|
|
9
15
|
adminforth: {
|
|
10
16
|
confirm: (params: ConfirmParams) => Promise<void>;
|
|
11
17
|
alert: (params: AlertParams) => void;
|
|
18
|
+
setListFilter: (filter: any) => void;
|
|
19
|
+
updateListFilter: (filter: any) => void;
|
|
20
|
+
clearListFilters: () => void;
|
|
12
21
|
};
|
|
13
22
|
}
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
export class FrontendAPI implements FrontendAPIInterface {
|
|
26
|
+
private validOperators: Operator[] = ['lte', 'gte', 'in','ilike'];
|
|
17
27
|
private toastStore:any
|
|
18
28
|
private modalStore:any
|
|
29
|
+
private filtersStore:any
|
|
30
|
+
private coreStore:any
|
|
19
31
|
init() {
|
|
20
32
|
if (window.adminforth) {
|
|
21
33
|
throw new Error('adminforth already initialized');
|
|
@@ -25,7 +37,10 @@ export class FrontendAPI implements FrontendAPIInterface {
|
|
|
25
37
|
console.log(this.toastStore, this.modalStore,'init of adminforth frontend api')
|
|
26
38
|
window.adminforth = {
|
|
27
39
|
confirm: this.confirm.bind(this),
|
|
28
|
-
alert: this.alert.bind(this)
|
|
40
|
+
alert: this.alert.bind(this),
|
|
41
|
+
setListFilter: this.setListFilter.bind(this),
|
|
42
|
+
updateListFilter: this.updateListFilter.bind(this),
|
|
43
|
+
clearListFilters: this.clearListFilters.bind(this),
|
|
29
44
|
}
|
|
30
45
|
}
|
|
31
46
|
|
|
@@ -44,8 +59,54 @@ export class FrontendAPI implements FrontendAPIInterface {
|
|
|
44
59
|
variant: params.variant
|
|
45
60
|
})
|
|
46
61
|
}
|
|
47
|
-
}
|
|
48
62
|
|
|
63
|
+
listFilterValidation(filter: FilterParams): boolean {
|
|
64
|
+
if(router.currentRoute.value.meta.type !== 'list'){
|
|
65
|
+
throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
|
|
66
|
+
} else {
|
|
67
|
+
if(!this.coreStore) this.coreStore = useCoreStore()
|
|
68
|
+
console.log(this.coreStore.resourceColumnsWithFilters,'core store')
|
|
69
|
+
const filterField = this.coreStore.resourceColumnsWithFilters.find((col: AdminForthResourceColumn) => col.name === filter.field)
|
|
70
|
+
if(!filterField){
|
|
71
|
+
throw new Error(`Field ${filter.field} is not available for filtering`)
|
|
72
|
+
}
|
|
73
|
+
if(filterField) {
|
|
74
|
+
if(!this.validOperators.includes(filter.operator)){
|
|
75
|
+
throw new Error(`Operator ${filter.operator} is not valid`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setListFilter(filter: FilterParams): void {
|
|
83
|
+
if(this.listFilterValidation(filter)){
|
|
84
|
+
this.filtersStore = useFiltersStore()
|
|
85
|
+
if(this.filtersStore.filters.some((f) => {return f.field === filter.field && f.operator === filter.operator})){
|
|
86
|
+
throw new Error(`Filter ${filter.field} with operator ${filter.operator} already exists`)
|
|
87
|
+
} else {
|
|
88
|
+
this.filtersStore.setFilter(filter)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
clearListFilters(): void {
|
|
94
|
+
this.filtersStore = useFiltersStore()
|
|
95
|
+
this.filtersStore.clearFilters()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
updateListFilter(filter: FilterParams): void {
|
|
99
|
+
if(this.listFilterValidation(filter)){
|
|
100
|
+
this.filtersStore = useFiltersStore()
|
|
101
|
+
const index = this.filtersStore.filters.findIndex((f: FilterParams) => f.field === filter.field)
|
|
102
|
+
if(index === -1) {
|
|
103
|
+
this.filtersStore.setFilter(filter)
|
|
104
|
+
} else {
|
|
105
|
+
this.filtersStore.setFilters([...this.filtersStore.filters.slice(0, index), filter, ...this.filtersStore.filters.slice(index + 1)])
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
49
109
|
|
|
50
110
|
|
|
51
111
|
|
|
112
|
+
}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { ref, computed } from 'vue'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { callAdminForthApi } from '@/utils';
|
|
4
|
+
import type { AdminForthResource, AdminForthResourceColumn } from '@/types/AdminForthConfig';
|
|
5
|
+
import type { Ref } from 'vue'
|
|
4
6
|
|
|
5
7
|
export const useCoreStore = defineStore('core', () => {
|
|
6
|
-
const resourceById = ref(
|
|
8
|
+
const resourceById: Ref<Object> = ref({});
|
|
7
9
|
const menu = ref([]);
|
|
8
10
|
const config = ref({});
|
|
9
11
|
const record = ref({});
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
+
const resource: Ref<AdminForthResource | null> = ref(null);
|
|
13
|
+
|
|
14
|
+
const resourceColumnsWithFilters = computed(() => {
|
|
15
|
+
if (!resource.value) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return resource.value.columns.filter((col: AdminForthResourceColumn) => col.showIn?.includes('filter'));
|
|
19
|
+
})
|
|
12
20
|
|
|
13
21
|
const resourceOptions = ref(null);
|
|
14
22
|
const resourceColumnsError = ref('');
|
|
@@ -24,7 +32,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
24
32
|
return
|
|
25
33
|
}
|
|
26
34
|
menu.value = resp.menu;
|
|
27
|
-
resourceById.value = resp.resources.reduce((acc, resource) => {
|
|
35
|
+
resourceById.value = resp.resources.reduce((acc: Object, resource: AdminForthResource) => {
|
|
28
36
|
acc[resource.resourceId] = resource;
|
|
29
37
|
return acc;
|
|
30
38
|
}, {});
|
|
@@ -36,7 +44,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
36
44
|
async function fetchRecord({ resourceId, primaryKey }) {
|
|
37
45
|
record.value = null;
|
|
38
46
|
|
|
39
|
-
if (!
|
|
47
|
+
if (!resource.value) {
|
|
40
48
|
throw new Error('Columns not fetched yet');
|
|
41
49
|
}
|
|
42
50
|
|
|
@@ -48,7 +56,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
48
56
|
resourceId: resourceId,
|
|
49
57
|
filters: [
|
|
50
58
|
{
|
|
51
|
-
field:
|
|
59
|
+
field: resource.value.columns.find((col: AdminForthResourceColumn) => col.primaryKey).name,
|
|
52
60
|
operator: 'eq',
|
|
53
61
|
value: primaryKey
|
|
54
62
|
}
|
|
@@ -64,13 +72,12 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
64
72
|
resourceOptions.value = respData.options;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
async function fetchResourceFull({ resourceId }) {
|
|
68
|
-
if (resourceColumnsId.value === resourceId &&
|
|
75
|
+
async function fetchResourceFull({ resourceId }: { resourceId: string }) {
|
|
76
|
+
if (resourceColumnsId.value === resourceId && resource.value) {
|
|
69
77
|
// already fetched
|
|
70
78
|
return;
|
|
71
79
|
}
|
|
72
80
|
resourceColumnsId.value = resourceId;
|
|
73
|
-
resourceColumns.value = null;
|
|
74
81
|
resourceColumnsError.value = '';
|
|
75
82
|
const res = await callAdminForthApi({
|
|
76
83
|
path: '/get_resource',
|
|
@@ -82,7 +89,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
82
89
|
if (res.error) {
|
|
83
90
|
resourceColumnsError.value = res.error;
|
|
84
91
|
} else {
|
|
85
|
-
resourceColumns.value = res.resource.columns;
|
|
86
92
|
resourceById.value[resourceId] = res.resource;
|
|
87
93
|
resource.value = res.resource;
|
|
88
94
|
resourceOptions.value = res.resource.options;
|
|
@@ -125,12 +131,12 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
125
131
|
fetchMenuAndResource,
|
|
126
132
|
fetchRecord,
|
|
127
133
|
record,
|
|
128
|
-
resourceColumns,
|
|
129
134
|
fetchResourceFull,
|
|
130
135
|
resourceColumnsError,
|
|
131
136
|
resourceOptions,
|
|
132
137
|
logout,
|
|
133
138
|
resource,
|
|
134
139
|
adminUser,
|
|
140
|
+
resourceColumnsWithFilters
|
|
135
141
|
}
|
|
136
142
|
})
|
|
@@ -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
|
+
|