adminforth 3.35.1 → 4.0.1
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/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +20 -3
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/styleGenerator.d.ts +13 -0
- package/dist/modules/styleGenerator.d.ts.map +1 -1
- package/dist/modules/styleGenerator.js +274 -1
- package/dist/modules/styleGenerator.js.map +1 -1
- package/dist/modules/styles.d.ts +24 -0
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +452 -427
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +1 -13
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +2 -275
- package/dist/modules/utils.js.map +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +1373 -1136
- package/dist/spa/package.json +18 -15
- package/dist/spa/pnpm-lock.yaml +1067 -648
- package/dist/spa/src/App.vue +84 -92
- package/dist/spa/src/afcl/AreaChart.vue +8 -24
- package/dist/spa/src/afcl/BarChart.vue +8 -23
- package/dist/spa/src/afcl/Button.vue +8 -2
- package/dist/spa/src/afcl/ButtonGroup.vue +9 -9
- package/dist/spa/src/afcl/Card.vue +40 -19
- package/dist/spa/src/afcl/Checkbox.vue +6 -6
- package/dist/spa/src/afcl/CountryFlag.vue +17 -11
- package/dist/spa/src/afcl/DatePicker.vue +7 -7
- package/dist/spa/src/afcl/Dialog.vue +8 -9
- package/dist/spa/src/afcl/DropdownMenu.vue +210 -0
- package/dist/spa/src/afcl/DropdownMenuAccordion.vue +56 -0
- package/dist/spa/src/afcl/DropdownMenuItem.vue +86 -0
- package/dist/spa/src/afcl/Dropzone.vue +10 -26
- package/dist/spa/src/afcl/Input.vue +4 -4
- package/dist/spa/src/afcl/Link.vue +1 -1
- package/dist/spa/src/afcl/LinkButton.vue +15 -1
- package/dist/spa/src/afcl/MixedChart.vue +4 -18
- package/dist/spa/src/afcl/Modal.vue +9 -9
- package/dist/spa/src/afcl/PieChart.vue +8 -29
- package/dist/spa/src/afcl/ProgressBar.vue +2 -2
- package/dist/spa/src/afcl/Select.vue +20 -20
- package/dist/spa/src/afcl/Skeleton.vue +1 -1
- package/dist/spa/src/afcl/Spinner.vue +1 -1
- package/dist/spa/src/afcl/Table.vue +19 -26
- package/dist/spa/src/afcl/Textarea.vue +1 -1
- package/dist/spa/src/afcl/Toggle.vue +4 -4
- package/dist/spa/src/afcl/Tooltip.vue +1 -1
- package/dist/spa/src/afcl/TreeMapChart.vue +5 -20
- package/dist/spa/src/afcl/VerticalTabs.vue +4 -4
- package/dist/spa/src/afcl/buttonStyles.ts +73 -33
- package/dist/spa/src/afcl/chartUtils.ts +19 -0
- package/dist/spa/src/afcl/classMerge.ts +11 -0
- package/dist/spa/src/afcl/dropdownMenuContext.ts +10 -0
- package/dist/spa/src/afcl/fileSize.ts +12 -0
- package/dist/spa/src/afcl/index.ts +3 -0
- package/dist/spa/src/components/AcceptModal.vue +17 -19
- package/dist/spa/src/components/Breadcrumbs.vue +11 -13
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +2 -2
- package/dist/spa/src/components/CallActionWrapper.vue +3 -0
- package/dist/spa/src/components/ColumnValueInput.vue +5 -5
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +7 -6
- package/dist/spa/src/components/CustomDateRangePicker.vue +8 -8
- package/dist/spa/src/components/CustomRangePicker.vue +2 -2
- package/dist/spa/src/components/ErrorMessage.vue +3 -4
- package/dist/spa/src/components/Filters.vue +13 -13
- package/dist/spa/src/components/GroupsTable.vue +12 -12
- package/dist/spa/src/components/ListActionsThreeDots.vue +89 -207
- package/dist/spa/src/components/MenuLink.vue +23 -20
- package/dist/spa/src/components/RangePicker.vue +9 -10
- package/dist/spa/src/components/ResourceListTable.vue +68 -65
- package/dist/spa/src/components/ShowTable.vue +8 -8
- package/dist/spa/src/components/Sidebar.vue +31 -38
- package/dist/spa/src/components/ThreeDotsMenu.vue +88 -136
- package/dist/spa/src/components/Toast.vue +16 -23
- package/dist/spa/src/components/UserMenuSettingsButton.vue +15 -36
- package/dist/spa/src/components/ValueRenderer.vue +13 -8
- package/dist/spa/src/components/icons/IconCalendar.vue +3 -3
- package/dist/spa/src/components/icons/IconTime.vue +3 -3
- package/dist/spa/src/index.scss +6 -2
- package/dist/spa/src/main.ts +2 -0
- package/dist/spa/src/renderers/CompactField.vue +3 -3
- package/dist/spa/src/renderers/CompactUUID.vue +3 -3
- package/dist/spa/src/renderers/SensitiveBlurCell.vue +9 -9
- package/dist/spa/src/utils/threeDotsMenu.ts +7 -0
- package/dist/spa/src/utils/userMenu.ts +5 -0
- package/dist/spa/src/utils/utils.ts +12 -39
- package/dist/spa/src/views/CreateEditSkeleton.vue +3 -3
- package/dist/spa/src/views/CreateView.vue +5 -6
- package/dist/spa/src/views/EditView.vue +5 -6
- package/dist/spa/src/views/ListView.vue +114 -115
- package/dist/spa/src/views/LoginView.vue +4 -4
- package/dist/spa/src/views/ShowView.vue +17 -18
- package/dist/spa/vite.config.ts +23 -16
- package/package.json +1 -2
|
@@ -4,7 +4,6 @@ import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull
|
|
|
4
4
|
import router from "../router";
|
|
5
5
|
import { useCoreStore } from '../stores/core';
|
|
6
6
|
import { useUserStore } from '../stores/user';
|
|
7
|
-
import { Dropdown } from 'flowbite';
|
|
8
7
|
import adminforth, { useAdminforth } from '../adminforth';
|
|
9
8
|
import DOMPurify from 'dompurify'
|
|
10
9
|
import debounce from 'debounce';
|
|
@@ -241,22 +240,22 @@ export function getIcon(icon: string) {
|
|
|
241
240
|
return resolveComponent(compName);
|
|
242
241
|
}
|
|
243
242
|
|
|
243
|
+
// URLs of the files an app config references as '@/...' (SPA src) or '@@/...' (app custom folder, copied to src/custom).
|
|
244
|
+
// The glob is explicit: Vite turns a template in new URL(`../${path}`, import.meta.url) into a one-level glob,
|
|
245
|
+
// which misses files in nested folders like '@@/assets/logo.svg'.
|
|
246
|
+
const assetUrls = import.meta.glob<string>(
|
|
247
|
+
['../**', '!../**/*.{vue,ts,tsx,js,mjs,cjs,css,scss,sass,json,yaml,yml,md,html}'],
|
|
248
|
+
{ eager: true, query: '?url', import: 'default' },
|
|
249
|
+
);
|
|
250
|
+
|
|
244
251
|
export const loadFile = (file: string) => {
|
|
245
252
|
if (file.startsWith('http')) {
|
|
246
253
|
return file;
|
|
247
254
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (file.startsWith('@/')) {
|
|
251
|
-
path = file.replace('@/', '');
|
|
252
|
-
baseUrl = new URL(`../${path}`, import.meta.url).href;
|
|
253
|
-
} else if (file.startsWith('@@/')) {
|
|
254
|
-
path = file.replace('@@/', '');
|
|
255
|
-
baseUrl = new URL(`../custom/${path}`, import.meta.url).href;
|
|
256
|
-
} else {
|
|
257
|
-
baseUrl = new URL(`../${file}`, import.meta.url).href;
|
|
255
|
+
if (file.startsWith('@@/')) {
|
|
256
|
+
return assetUrls[`../custom/${file.slice('@@/'.length)}`];
|
|
258
257
|
}
|
|
259
|
-
return
|
|
258
|
+
return assetUrls[`../${file.replace('@/', '')}`];
|
|
260
259
|
}
|
|
261
260
|
|
|
262
261
|
|
|
@@ -288,21 +287,6 @@ export function checkAcessByAllowedActions(allowedActions:any, action:any ) {
|
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
289
|
|
|
291
|
-
export function initThreeDotsDropdown() {
|
|
292
|
-
const threeDotsDropdown: HTMLElement | null = document.querySelector('#listThreeDotsDropdown');
|
|
293
|
-
if (threeDotsDropdown) {
|
|
294
|
-
// this resource has three dots dropdown
|
|
295
|
-
const dd = new Dropdown(
|
|
296
|
-
threeDotsDropdown,
|
|
297
|
-
document.querySelector('[data-dropdown-toggle="listThreeDotsDropdown"]') as HTMLElement,
|
|
298
|
-
{ placement: 'bottom-end' }
|
|
299
|
-
);
|
|
300
|
-
adminforth.list.closeThreeDotsDropdown = () => {
|
|
301
|
-
dd.hide();
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
290
|
export function applyRegexValidation(value: any, validation: ValidationObject[] | undefined) {
|
|
307
291
|
|
|
308
292
|
if ( validation?.length ) {
|
|
@@ -350,18 +334,7 @@ export function verySimpleHash(str: string): string {
|
|
|
350
334
|
return `${str.split('').reduce((a, b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)}`;
|
|
351
335
|
}
|
|
352
336
|
|
|
353
|
-
export
|
|
354
|
-
if (!size) {
|
|
355
|
-
return '';
|
|
356
|
-
}
|
|
357
|
-
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
358
|
-
let i = 0
|
|
359
|
-
while (size >= 1024 && i < units.length - 1) {
|
|
360
|
-
size /= 1024
|
|
361
|
-
i++
|
|
362
|
-
}
|
|
363
|
-
return `${size.toFixed(1)} ${units[i]}`
|
|
364
|
-
}
|
|
337
|
+
export { humanifySize } from '../afcl/fileSize';
|
|
365
338
|
|
|
366
339
|
export function protectAgainstXSS(value: string) {
|
|
367
340
|
return DOMPurify.sanitize(value, {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-full mt-[36px]">
|
|
3
|
-
<div class="w-full border dark:border-darkFormBorder border-
|
|
3
|
+
<div class="w-full border dark:border-darkFormBorder border-lightFormBorder rounded-lg overflow-hidden shadow-resourseFormShadow dark:shadow-darkResourseFormShadow bg-lightForm dark:bg-darkForm">
|
|
4
4
|
|
|
5
|
-
<div class="flex px-
|
|
5
|
+
<div class="flex px-5 items-center border-b border-lightFormBorder dark:border-darkFormBorder bg-lightFormHeading dark:bg-darkFormHeading h-[40px]">
|
|
6
6
|
<div class="w-[208px] flex-shrink-0 pr-6 text-xs text-lightListTableHeadingText uppercase dark:text-darkListTableHeadingText font-bold">
|
|
7
7
|
{{ $t('Field') }}
|
|
8
8
|
</div>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<div
|
|
15
15
|
v-for="i in 10"
|
|
16
16
|
:key="i"
|
|
17
|
-
class="flex items-center bg-lightForm dark:bg-darkForm border-b border-
|
|
17
|
+
class="flex items-center bg-lightForm dark:bg-darkForm border-b border-lightFormBorder dark:border-darkFormBorder h-[58px]"
|
|
18
18
|
>
|
|
19
19
|
<div class="w-[208px] flex-shrink-0 px-6 flex items-center">
|
|
20
20
|
<Skeleton class="w-24 h-[10px]" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<BreadcrumbsWithButtons>
|
|
14
14
|
<!-- save and cancle -->
|
|
15
15
|
<Button @click="() => {cancelButtonClicked = true; $router.back()}"
|
|
16
|
-
class="af-cancel-button h-[2.125rem] px-3
|
|
16
|
+
class="af-cancel-button h-[2.125rem] px-3"
|
|
17
17
|
variant="secondary"
|
|
18
18
|
>
|
|
19
19
|
{{ $t('Cancel') }}
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
<Button
|
|
22
22
|
@click="() => saveRecord()"
|
|
23
23
|
class="af-save-button h-[2.125rem] px-3 text-lightCreateViewSaveButtonText hover:text-lightCreateViewSaveButtonTextHover dark:text-darkCreateViewSaveButtonText dark:hover:text-darkCreateViewSaveButtonTextHover"
|
|
24
|
-
variant="
|
|
24
|
+
variant="primary"
|
|
25
25
|
:disabled="saving || (validatingMode && !isValid) || resourceFormRef?.isValidating"
|
|
26
26
|
>
|
|
27
27
|
<Spinner v-if="saving || resourceFormRef?.isValidating" class="w-4 h-4" />
|
|
28
|
-
<
|
|
28
|
+
<IconSave v-else class="w-4 h-4" />
|
|
29
29
|
{{ $t('Save') }}
|
|
30
30
|
</Button>
|
|
31
31
|
|
|
@@ -79,8 +79,8 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
79
79
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
80
80
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
81
81
|
import { useCoreStore } from '@/stores/core';
|
|
82
|
-
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions,
|
|
83
|
-
import {
|
|
82
|
+
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, checkShowIf, compareOldAndNewRecord, onBeforeRouteLeaveCreateEditViewGuard, leaveGuardActiveClass, formatComponent, atob_function } from '@/utils';
|
|
83
|
+
import { IconSave } from '@iconify-prerendered/vue-lucide';
|
|
84
84
|
import { onMounted, onBeforeMount, onBeforeUnmount, ref } from 'vue';
|
|
85
85
|
import { useRoute, useRouter } from 'vue-router';
|
|
86
86
|
import { showErrorTost } from '@/composables/useFrontendApi';
|
|
@@ -192,7 +192,6 @@ onMounted(async () => {
|
|
|
192
192
|
record.value = initialValues.value;
|
|
193
193
|
loading.value = false;
|
|
194
194
|
checkAcessByAllowedActions(coreStore.resourceOptions!.allowedActions,'create');
|
|
195
|
-
initThreeDotsDropdown();
|
|
196
195
|
});
|
|
197
196
|
|
|
198
197
|
async function saveRecord() {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<BreadcrumbsWithButtons>
|
|
13
13
|
<!-- save and cancle -->
|
|
14
14
|
<Button @click="() => {cancelButtonClicked = true; $router.back()}"
|
|
15
|
-
class="h-[2.125rem] px-3
|
|
15
|
+
class="h-[2.125rem] px-3 text-lightEditViewButtonText dark:text-darkEditViewButtonText"
|
|
16
16
|
variant="secondary"
|
|
17
17
|
>
|
|
18
18
|
{{ $t('Cancel') }}
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
@click="() => saveRecord()"
|
|
22
22
|
class="h-[2.125rem] px-3 text-sm text-lightEditViewSaveButtonText hover:text-lightEditViewSaveButtonTextHover dark:text-darkEditViewSaveButtonText dark:hover:text-darkEditViewSaveButtonTextHover"
|
|
23
23
|
:disabled="saving || (validatingMode && !isValid) || resourceFormRef?.isValidating"
|
|
24
|
-
variant="
|
|
24
|
+
variant="primary"
|
|
25
25
|
>
|
|
26
26
|
<Spinner v-if="saving || resourceFormRef?.isValidating" class="w-4 h-4" />
|
|
27
|
-
<
|
|
27
|
+
<IconSave v-else class="w-4 h-4" />
|
|
28
28
|
{{ $t('Save') }}
|
|
29
29
|
</Button>
|
|
30
30
|
|
|
@@ -77,8 +77,8 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
|
77
77
|
import ResourceForm from '@/components/ResourceForm.vue';
|
|
78
78
|
import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
|
|
79
79
|
import { useCoreStore } from '@/stores/core';
|
|
80
|
-
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions,
|
|
81
|
-
import {
|
|
80
|
+
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, compareOldAndNewRecord, generateMessageHtmlForRecordChange, leaveGuardActiveClass, onBeforeRouteLeaveCreateEditViewGuard } from '@/utils';
|
|
81
|
+
import { IconSave } from '@iconify-prerendered/vue-lucide';
|
|
82
82
|
import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch, onBeforeUnmount } from 'vue';
|
|
83
83
|
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
|
|
84
84
|
import { showErrorTost } from '@/composables/useFrontendApi';
|
|
@@ -170,7 +170,6 @@ onMounted(async () => {
|
|
|
170
170
|
await coreStore.fetchResourceFull({
|
|
171
171
|
resourceId: route.params.resourceId as string
|
|
172
172
|
});
|
|
173
|
-
initThreeDotsDropdown();
|
|
174
173
|
|
|
175
174
|
await coreStore.fetchRecord({
|
|
176
175
|
resourceId: route.params.resourceId as string,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</Teleport>
|
|
13
13
|
|
|
14
14
|
<component
|
|
15
|
-
v-if="
|
|
15
|
+
v-if="isResourceReady"
|
|
16
16
|
v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs as AdminForthComponentDeclaration[] || []"
|
|
17
17
|
:is="getCustomComponent(formatComponent(c))"
|
|
18
18
|
:meta="(c as AdminForthComponentDeclarationFull).meta"
|
|
@@ -21,125 +21,122 @@
|
|
|
21
21
|
/>
|
|
22
22
|
|
|
23
23
|
<BreadcrumbsWithButtons>
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<Button
|
|
35
|
-
@click="()=>{checkboxes = []}"
|
|
36
|
-
v-if="checkboxes.length"
|
|
37
|
-
data-tooltip-target="tooltip-remove-all"
|
|
38
|
-
class="h-[2.125rem] px-2.5"
|
|
39
|
-
variant="secondary"
|
|
40
|
-
>
|
|
41
|
-
<Tooltip>
|
|
42
|
-
<IconBanOutline class="w-5 h-5 "/>
|
|
43
|
-
<template #tooltip >
|
|
44
|
-
Remove selection
|
|
45
|
-
</template>
|
|
46
|
-
</Tooltip>
|
|
47
|
-
</Button>
|
|
48
|
-
|
|
49
|
-
<div
|
|
50
|
-
v-if="checkboxes.length"
|
|
51
|
-
v-for="(action,i) in coreStore.resource?.options?.bulkActions"
|
|
52
|
-
>
|
|
24
|
+
<template v-if="isResourceReady">
|
|
25
|
+
<component
|
|
26
|
+
v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeActionButtons as AdminForthComponentDeclaration[] || []"
|
|
27
|
+
:is="getCustomComponent(formatComponent(c))"
|
|
28
|
+
:meta="(c as AdminForthComponentDeclarationFull).meta"
|
|
29
|
+
:resource="coreStore.resource"
|
|
30
|
+
:adminUser="coreStore.adminUser"
|
|
31
|
+
:checkboxes="checkboxes"
|
|
32
|
+
:clearCheckboxes="clearCheckboxes"
|
|
33
|
+
/>
|
|
53
34
|
<Button
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class="h-[2.125rem] px-2.5"
|
|
58
|
-
:class="action.buttonCustomCssClass || ''"
|
|
35
|
+
@click="()=>{checkboxes = []}"
|
|
36
|
+
v-if="checkboxes.length"
|
|
37
|
+
data-tooltip-target="tooltip-remove-all"
|
|
59
38
|
variant="secondary"
|
|
39
|
+
iconOnly
|
|
60
40
|
>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class="w-5 h-5 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
|
|
68
|
-
/>
|
|
69
|
-
{{ `${action.label} (${checkboxes.length})` }}
|
|
70
|
-
<div v-if="action.badge" class="text-white bg-gradient-to-r from-purple-500
|
|
71
|
-
via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none
|
|
72
|
-
focus:ring-purple-300 dark:focus:ring-purple-800
|
|
73
|
-
font-medium rounded-default text-xs px-1 ml-1 text-center ">
|
|
74
|
-
{{ action.badge }}
|
|
75
|
-
</div>
|
|
41
|
+
<Tooltip>
|
|
42
|
+
<IconBan class="w-4 h-4"/>
|
|
43
|
+
<template #tooltip >
|
|
44
|
+
Remove selection
|
|
45
|
+
</template>
|
|
46
|
+
</Tooltip>
|
|
76
47
|
</Button>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
48
|
+
|
|
49
|
+
<div
|
|
50
|
+
v-if="checkboxes.length"
|
|
51
|
+
v-for="(action,i) in coreStore.resource?.options?.bulkActions"
|
|
52
|
+
>
|
|
53
|
+
<Button
|
|
54
|
+
v-if="!action.showInThreeDotsDropdown"
|
|
55
|
+
:key="action.id"
|
|
56
|
+
@click="startBulkActionInner(action.id!)"
|
|
57
|
+
class="h-[2.125rem] px-2.5"
|
|
58
|
+
:class="action.buttonCustomCssClass || ''"
|
|
59
|
+
variant="secondary"
|
|
60
|
+
>
|
|
61
|
+
<component
|
|
62
|
+
v-if="action.icon && !bulkActionLoadingStates[action.id!]"
|
|
63
|
+
:is="getIcon(action.icon)"
|
|
64
|
+
class="w-4 h-4 transition duration-75"></component>
|
|
65
|
+
<Spinner
|
|
66
|
+
v-if="bulkActionLoadingStates[action.id!]"
|
|
67
|
+
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
|
|
68
|
+
/>
|
|
69
|
+
{{ `${action.label} (${checkboxes.length})` }}
|
|
70
|
+
<div v-if="action.badge" class="inline-flex items-center h-[1.125rem] px-1.5 ml-1 rounded-full bg-purple-600 text-white text-[11px] font-semibold">
|
|
71
|
+
{{ action.badge }}
|
|
72
|
+
</div>
|
|
73
|
+
</Button>
|
|
74
|
+
</div>
|
|
75
|
+
<div
|
|
76
|
+
v-if="checkboxes.length"
|
|
77
|
+
v-for="(action,i) in coreStore.resource?.options?.actions?.filter(a => a.showIn?.bulkButton)"
|
|
78
|
+
>
|
|
79
|
+
<Button
|
|
80
|
+
:key="action.id"
|
|
81
|
+
@click="startCustomBulkActionInner(action.id!)"
|
|
82
|
+
class="h-[2.125rem] px-2.5"
|
|
83
|
+
variant="secondary"
|
|
84
|
+
>
|
|
85
|
+
<component
|
|
86
|
+
v-if="action.icon && !customActionLoadingStates[action.id!]"
|
|
87
|
+
:is="getIcon(action.icon)"
|
|
88
|
+
class="w-4 h-4 transition duration-75"></component>
|
|
89
|
+
<Spinner
|
|
90
|
+
v-if="customActionLoadingStates[action.id!]"
|
|
91
|
+
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
|
|
92
|
+
/>
|
|
93
|
+
{{ `${action.name} (${checkboxes.length})` }}
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<LinkButton v-if="coreStore.resource?.options?.allowedActions?.create"
|
|
98
|
+
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
99
|
+
class="af-create-button h-[2.125rem] px-2.5"
|
|
100
|
+
>
|
|
101
|
+
<IconPlus class="w-4 h-4"/>
|
|
102
|
+
<span>{{ $t('Create') }}</span>
|
|
103
|
+
</LinkButton>
|
|
104
|
+
|
|
82
105
|
<Button
|
|
83
|
-
|
|
84
|
-
@click="
|
|
85
|
-
|
|
106
|
+
class="af-filter-button h-[2.125rem] px-3 py-3"
|
|
107
|
+
@click="()=>{filtersShow = !filtersShow}"
|
|
108
|
+
v-if="coreStore.resource?.options?.allowedActions?.filter"
|
|
86
109
|
variant="secondary"
|
|
87
110
|
>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class="w-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class="w-5 h-5 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
|
|
95
|
-
/>
|
|
96
|
-
{{ `${action.name} (${checkboxes.length})` }}
|
|
97
|
-
</Button>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<LinkButton v-if="coreStore.resource?.options?.allowedActions?.create"
|
|
101
|
-
:to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
|
|
102
|
-
class="af-create-button h-[2.125rem] px-2.5"
|
|
103
|
-
>
|
|
104
|
-
<IconPlusOutline class="w-4 h-4"/>
|
|
105
|
-
<span>{{ $t('Create') }}</span>
|
|
106
|
-
</LinkButton>
|
|
107
|
-
|
|
108
|
-
<Button
|
|
109
|
-
class="af-filter-button h-[2.125rem] px-3 py-3"
|
|
110
|
-
@click="()=>{filtersShow = !filtersShow}"
|
|
111
|
-
v-if="coreStore.resource?.options?.allowedActions?.filter"
|
|
112
|
-
variant="secondary"
|
|
113
|
-
>
|
|
114
|
-
<IconFilterOutline class="w-4 h-4"/>
|
|
115
|
-
<span>{{ $t('Filter') }}</span>
|
|
116
|
-
<div v-if="filtersStore.visibleFiltersCount" class="flex items-center gap-1 ms-1">
|
|
117
|
-
<span class="bg-red-100 text-red-800 text-xs font-medium px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">
|
|
118
|
-
{{ filtersStore.visibleFiltersCount }}
|
|
119
|
-
</span>
|
|
111
|
+
<IconListFilter class="w-4 h-4"/>
|
|
112
|
+
<span>{{ $t('Filter') }}</span>
|
|
113
|
+
<div v-if="filtersStore.visibleFiltersCount" class="flex items-center gap-1 ms-1">
|
|
114
|
+
<span class="inline-flex items-center justify-center min-w-[1.125rem] h-[1.125rem] px-1 rounded-full text-[11px] font-semibold tabular-nums bg-lightPrimary text-lightPrimaryContrast dark:bg-darkPrimary dark:text-darkPrimaryContrast">
|
|
115
|
+
{{ filtersStore.visibleFiltersCount }}
|
|
116
|
+
</span>
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
<div
|
|
119
|
+
@click.stop="filtersStore.clearFilters()"
|
|
120
|
+
class="p-0.5 rounded text-lightInputIconColor hover:text-lightInputText hover:bg-lightButtonGroupBackgroundHover dark:text-darkInputIconColor dark:hover:text-darkInputText dark:hover:bg-darkButtonGroupBackgroundHover transition-colors"
|
|
121
|
+
>
|
|
122
|
+
<IconX class="w-3.5 h-3.5" />
|
|
123
|
+
</div>
|
|
126
124
|
</div>
|
|
127
|
-
</
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
></ThreeDotsMenu>
|
|
125
|
+
</Button>
|
|
126
|
+
|
|
127
|
+
<ThreeDotsMenu
|
|
128
|
+
:threeDotsDropdownItems="(coreStore.resourceOptions?.pageInjections?.list?.threeDotsDropdownItems as [])"
|
|
129
|
+
:bulkActions="coreStore.resource?.options?.bulkActions"
|
|
130
|
+
:checkboxes="checkboxes"
|
|
131
|
+
@startBulkAction="startBulkActionInner"
|
|
132
|
+
:updateList="getListInner"
|
|
133
|
+
:clearCheckboxes="clearCheckboxes"
|
|
134
|
+
></ThreeDotsMenu>
|
|
135
|
+
</template>
|
|
139
136
|
</BreadcrumbsWithButtons>
|
|
140
137
|
|
|
141
138
|
<component
|
|
142
|
-
v-if="
|
|
139
|
+
v-if="isResourceReady"
|
|
143
140
|
v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs as AdminForthComponentDeclaration[] || []"
|
|
144
141
|
:is="getCustomComponent(formatComponent(c))"
|
|
145
142
|
:meta="(c as AdminForthComponentDeclarationFull).meta"
|
|
@@ -209,18 +206,18 @@ import { useFiltersStore } from '@/stores/filters';
|
|
|
209
206
|
import { callAdminForthApi, currentQuery, getIcon, setQuery, formatComponent, executeCustomBulkAction } from '@/utils';
|
|
210
207
|
import { computed, onMounted, onUnmounted, ref, watch, type Ref } from 'vue';
|
|
211
208
|
import { useRoute } from 'vue-router';
|
|
212
|
-
import { getCustomComponent,
|
|
209
|
+
import { getCustomComponent, getList, startBulkAction } from '@/utils';
|
|
213
210
|
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
|
|
214
211
|
import { Tooltip, Spinner, Button, LinkButton } from '@/afcl'
|
|
215
212
|
import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull, AdminForthFilterOperators, AdminForthResourceColumnCommon } from '@/types/Common';
|
|
216
213
|
import { useI18n } from 'vue-i18n';
|
|
217
214
|
|
|
218
215
|
import {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} from '@iconify-prerendered/vue-
|
|
216
|
+
IconBan,
|
|
217
|
+
IconListFilter,
|
|
218
|
+
IconPlus,
|
|
219
|
+
IconX
|
|
220
|
+
} from '@iconify-prerendered/vue-lucide';
|
|
224
221
|
|
|
225
222
|
import Filters from '@/components/Filters.vue';
|
|
226
223
|
import { useAdminforth } from '@/adminforth';
|
|
@@ -233,6 +230,9 @@ const { t } = useI18n();
|
|
|
233
230
|
|
|
234
231
|
const route = useRoute();
|
|
235
232
|
|
|
233
|
+
// previous resource stays in the store until the new one is applied, its injections must not render here
|
|
234
|
+
const isResourceReady = computed(() => coreStore.resource?.resourceId === route.params.resourceId);
|
|
235
|
+
|
|
236
236
|
const page = ref(1);
|
|
237
237
|
const columnsMinMax = ref({});
|
|
238
238
|
const sort = ref();
|
|
@@ -524,7 +524,6 @@ watch(() => filtersStore.filters, async (to, from) => {
|
|
|
524
524
|
onMounted(async () => {
|
|
525
525
|
initInProcess = true;
|
|
526
526
|
await init();
|
|
527
|
-
initThreeDotsDropdown();
|
|
528
527
|
initInProcess = false;
|
|
529
528
|
});
|
|
530
529
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="relative flex items-center justify-center min-h-screen bg-lightHtml dark:bg-darkHtml w-
|
|
2
|
+
<div class="relative flex items-center justify-center min-h-screen bg-lightHtml dark:bg-darkHtml w-full h-screen"
|
|
3
3
|
:style="coreStore.config?.loginBackgroundImage && backgroundPosition === 'over' ? {
|
|
4
4
|
'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
|
|
5
5
|
'background-size': 'cover',
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
:type="!showPw ? 'password': 'text'" name="password" id="password" placeholder="••••••••" class="w-full" required>
|
|
77
77
|
<template #rightIcon>
|
|
78
78
|
<button type="button" @click="showPw = !showPw" class="text-lightLoginViewSubTextColor dark:text-darkLoginViewSubTextColor">
|
|
79
|
-
<
|
|
80
|
-
<
|
|
79
|
+
<IconEye class="w-4 h-4" v-if="!showPw" />
|
|
80
|
+
<IconEyeOff class="w-4 h-4" v-else />
|
|
81
81
|
</button>
|
|
82
82
|
</template>
|
|
83
83
|
</Input>
|
|
@@ -142,7 +142,7 @@ import { getCustomComponent, formatComponent } from '@/utils';
|
|
|
142
142
|
import { onMounted, ref, computed } from 'vue';
|
|
143
143
|
import { useCoreStore } from '@/stores/core';
|
|
144
144
|
import { useUserStore } from '@/stores/user';
|
|
145
|
-
import {
|
|
145
|
+
import { IconEye, IconEyeOff } from '@iconify-prerendered/vue-lucide';
|
|
146
146
|
import { callAdminForthApi, loadFile } from '@/utils';
|
|
147
147
|
import { useRouter, useRoute } from 'vue-router';
|
|
148
148
|
import { Button, Checkbox, Input, Spinner } from '@/afcl';
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
:to="nextRecordRoute ?? $route"
|
|
16
16
|
@click="handleNextClick()"
|
|
17
17
|
:class="!nextRecordRoute ? 'opacity-50 pointer-events-none cursor-not-allowed' : ''"
|
|
18
|
-
class="h-[2.125rem] text-lightListViewButtonText dark:text-darkListViewButtonText px-3
|
|
18
|
+
class="h-[2.125rem] text-lightListViewButtonText dark:text-darkListViewButtonText px-3"
|
|
19
19
|
variant="secondary"
|
|
20
20
|
>
|
|
21
|
-
<Spinner v-if="isFetchingNextPage" class="w-4 h-4 text-
|
|
21
|
+
<Spinner v-if="isFetchingNextPage" class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText" />
|
|
22
22
|
{{ $t('Next') }}
|
|
23
23
|
</LinkButton>
|
|
24
24
|
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
<component
|
|
41
41
|
v-if="action.icon && !actionLoadingStates[action.id!]"
|
|
42
42
|
:is="getIcon(action.icon)"
|
|
43
|
-
class="w-4 h-4
|
|
43
|
+
class="w-4 h-4 text-lightInputIconColor dark:text-darkInputIconColor"
|
|
44
44
|
/>
|
|
45
45
|
<Spinner
|
|
46
46
|
v-if="actionLoadingStates[action.id!]"
|
|
47
|
-
class="w-
|
|
47
|
+
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
|
|
48
48
|
/>
|
|
49
49
|
{{ action.name }}
|
|
50
50
|
</Button>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
class="af-add-new-button h-[2.125rem] px-2.5"
|
|
57
57
|
variant="secondary"
|
|
58
58
|
>
|
|
59
|
-
<
|
|
59
|
+
<IconPlus class="w-4 h-4"/>
|
|
60
60
|
{{ $t('Add new') }}
|
|
61
61
|
</LinkButton>
|
|
62
62
|
|
|
@@ -64,15 +64,15 @@
|
|
|
64
64
|
class="h-[2.125rem] af-edit-button px-2.5"
|
|
65
65
|
variant="secondary"
|
|
66
66
|
>
|
|
67
|
-
<
|
|
67
|
+
<IconPencil class="w-4 h-4" />
|
|
68
68
|
{{ $t('Edit') }}
|
|
69
69
|
</LinkButton>
|
|
70
70
|
|
|
71
71
|
<Button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
|
|
72
|
-
class="h-[2.125rem] af-delete-button px-3 text-
|
|
72
|
+
class="h-[2.125rem] af-delete-button px-3 text-lightInputErrorColor hover:text-lightInputErrorColor focus-visible:z-10 dark:text-darkInputErrorColor dark:hover:text-darkInputErrorColor"
|
|
73
73
|
variant="secondary"
|
|
74
74
|
>
|
|
75
|
-
<
|
|
75
|
+
<IconTrash2 class="w-4 h-4" />
|
|
76
76
|
{{ $t('Delete') }}
|
|
77
77
|
</Button>
|
|
78
78
|
|
|
@@ -100,18 +100,18 @@
|
|
|
100
100
|
>
|
|
101
101
|
<div
|
|
102
102
|
v-if="groups && groups.length > 0"
|
|
103
|
-
class="text-
|
|
103
|
+
class="text-sm font-semibold px-5 py-3 flex flex-1 items-center bg-lightShowTableHeadingBackground dark:bg-darkShowTableHeadingBackground dark:text-darkShowTableHeadingText border-b border-lightShowTableBodyBorder dark:border-darkShowTableBodyBorder"
|
|
104
104
|
>
|
|
105
|
-
<div class="h-
|
|
105
|
+
<div class="h-3 bg-lightSkeletonBackgroundColor dark:bg-darkSkeletonBackgroundColor rounded-full w-32"></div>
|
|
106
106
|
</div>
|
|
107
107
|
|
|
108
108
|
<table class="w-full text-sm text-left table-fixed border-collapse">
|
|
109
109
|
<thead class="bg-lightShowTableUnderHeadingBackground dark:bg-darkShowTableUnderHeadingBackground block md:table-row-group">
|
|
110
110
|
<tr class="block md:table-row">
|
|
111
|
-
<th scope="col" class="px-
|
|
111
|
+
<th scope="col" class="px-5 py-2.5 text-xs font-medium hidden md:w-56 md:table-cell text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText">
|
|
112
112
|
{{ $t('Field') }}
|
|
113
113
|
</th>
|
|
114
|
-
<th scope="col" class="px-
|
|
114
|
+
<th scope="col" class="px-5 py-2.5 text-xs font-medium hidden md:table-cell text-lightShowTableUnderHeadingText dark:text-darkShowTableUnderHeadingText">
|
|
115
115
|
{{ $t('Value') }}
|
|
116
116
|
</th>
|
|
117
117
|
</tr>
|
|
@@ -125,13 +125,13 @@
|
|
|
125
125
|
>
|
|
126
126
|
<td class="px-6 py-[15.5px] relative block md:table-cell pb-0 md:pb-[15.5px]">
|
|
127
127
|
<div class="md:absolute md:inset-0 flex items-center px-6 py-[15.5px]">
|
|
128
|
-
<div class="h-2.5 bg-
|
|
128
|
+
<div class="h-2.5 bg-lightSkeletonBackgroundColor dark:bg-darkSkeletonBackgroundColor rounded-full w-24"></div>
|
|
129
129
|
</div>
|
|
130
130
|
</td>
|
|
131
131
|
|
|
132
132
|
<td class="px-6 py-[15.5px] whitespace-pre-wrap block md:table-cell">
|
|
133
133
|
<div class="flex items-center h-full min-h-[21px]">
|
|
134
|
-
<div class="h-2.5 bg-
|
|
134
|
+
<div class="h-2.5 bg-lightSkeletonBackgroundColor dark:bg-darkSkeletonBackgroundColor rounded-full w-full max-w-[280px]"></div>
|
|
135
135
|
</div>
|
|
136
136
|
</td>
|
|
137
137
|
</tr>
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
</template>
|
|
173
173
|
</div>
|
|
174
174
|
|
|
175
|
-
<div v-else class="text-center text-
|
|
175
|
+
<div v-else class="text-center text-sm text-lightListTablePaginationHelpText dark:text-darkListTablePaginationHelpText mt-10">
|
|
176
176
|
{{ $t('Ooops. Record not found') }}
|
|
177
177
|
</div>
|
|
178
178
|
|
|
@@ -196,8 +196,8 @@
|
|
|
196
196
|
import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
|
|
197
197
|
|
|
198
198
|
import { useCoreStore } from '@/stores/core';
|
|
199
|
-
import { getCustomComponent, checkAcessByAllowedActions,
|
|
200
|
-
import {
|
|
199
|
+
import { getCustomComponent, checkAcessByAllowedActions, formatComponent, executeCustomAction } from '@/utils';
|
|
200
|
+
import { IconPencil, IconTrash2, IconPlus } from '@iconify-prerendered/vue-lucide';
|
|
201
201
|
import { onMounted, onUnmounted, ref, computed, watch } from 'vue';
|
|
202
202
|
import { useRoute,useRouter } from 'vue-router';
|
|
203
203
|
import {callAdminForthApi} from '@/utils';
|
|
@@ -271,7 +271,6 @@ onMounted(async () => {
|
|
|
271
271
|
await coreStore.fetchResourceFull({
|
|
272
272
|
resourceId: route.params.resourceId as string,
|
|
273
273
|
});
|
|
274
|
-
initThreeDotsDropdown();
|
|
275
274
|
await coreStore.fetchRecord({
|
|
276
275
|
resourceId: route.params.resourceId as string,
|
|
277
276
|
primaryKey: route.params.primaryKey as string,
|