adminforth 1.3.56-next.9 → 1.3.57-next.0
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/auth.d.ts.map +1 -1
- package/dist/auth.js +8 -4
- package/dist/auth.js.map +1 -1
- package/dist/basePlugin.d.ts +5 -0
- package/dist/basePlugin.d.ts.map +1 -1
- package/dist/basePlugin.js +6 -1
- package/dist/basePlugin.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +3 -1
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +15 -2
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/styles.js +2 -2
- package/dist/modules/styles.js.map +1 -1
- package/dist/servers/express.js +2 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +16 -0
- package/dist/spa/package.json +1 -0
- package/dist/spa/src/App.vue +13 -31
- package/dist/spa/src/components/AfTooltip.vue +43 -0
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +3 -4
- package/dist/spa/src/components/CustomDatePicker.vue +2 -2
- package/dist/spa/src/components/Dropdown.vue +2 -2
- package/dist/spa/src/components/GroupsTable.vue +170 -0
- package/dist/spa/src/components/ResourceForm.vue +70 -156
- package/dist/spa/src/components/ResourceListTable.vue +123 -124
- package/dist/spa/src/renderers/CompactField.vue +45 -0
- package/dist/spa/src/renderers/CompactUUID.vue +12 -15
- package/dist/spa/src/renderers/CountryFlag.vue +16 -21
- package/dist/spa/src/renderers/HumanNumber.vue +11 -16
- package/dist/spa/src/renderers/RelativeTime.vue +41 -0
- package/dist/spa/src/renderers/URL.vue +18 -0
- package/dist/spa/src/stores/user.ts +0 -5
- package/dist/spa/src/types/AdminForthConfig.ts +25 -11
- package/dist/spa/src/views/CreateView.vue +1 -1
- package/dist/spa/src/views/EditView.vue +1 -1
- package/dist/spa/src/views/ListView.vue +3 -8
- package/dist/spa/src/views/ResourceParent.vue +36 -2
- package/dist/spa/src/views/ShowView.vue +16 -15
- package/dist/types/AdminForthConfig.d.ts +24 -11
- package/dist/types/AdminForthConfig.d.ts.map +1 -1
- package/dist/types/AdminForthConfig.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,164 +1,62 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="rounded-default">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
:record="currentValues"
|
|
47
|
-
@update:inValidity="customComponentsInValidity[column.name] = $event"
|
|
48
|
-
@update:emptiness="customComponentsEmptiness[column.name] = $event"
|
|
49
|
-
/>
|
|
50
|
-
</template>
|
|
51
|
-
<template v-else>
|
|
52
|
-
<Dropdown
|
|
53
|
-
single
|
|
54
|
-
v-if="column.foreignResource"
|
|
55
|
-
:options="columnOptions[column.name] || []"
|
|
56
|
-
:placeholder = "columnOptions[column.name]?.length ?'Select...': 'There are no options available'"
|
|
57
|
-
:modelValue="currentValues[column.name]"
|
|
58
|
-
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
59
|
-
></Dropdown>
|
|
60
|
-
<Dropdown
|
|
61
|
-
single
|
|
62
|
-
v-else-if="column.enum"
|
|
63
|
-
:options="column.enum"
|
|
64
|
-
:modelValue="currentValues[column.name]"
|
|
65
|
-
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
66
|
-
/>
|
|
67
|
-
<Dropdown
|
|
68
|
-
single
|
|
69
|
-
v-else-if="column.type === 'boolean'"
|
|
70
|
-
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
71
|
-
:modelValue="currentValues[column.name]"
|
|
72
|
-
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
73
|
-
/>
|
|
74
|
-
<input
|
|
75
|
-
v-else-if="['integer'].includes(column.type)"
|
|
76
|
-
type="number"
|
|
77
|
-
step="1"
|
|
78
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
79
|
-
placeholder="0"
|
|
80
|
-
:value="currentValues[column.name]"
|
|
81
|
-
@input="setCurrentValue(column.name, $event.target.value)"
|
|
82
|
-
>
|
|
83
|
-
<CustomDatePicker
|
|
84
|
-
v-else-if="['datetime'].includes(column.type)"
|
|
85
|
-
:column="column"
|
|
86
|
-
:valueStart="currentValues[column.name]"
|
|
87
|
-
auto-hide
|
|
88
|
-
@update:valueStart="setCurrentValue(column.name, $event)"
|
|
89
|
-
/>
|
|
90
|
-
<input
|
|
91
|
-
v-else-if="['decimal', 'float'].includes(column.type)"
|
|
92
|
-
type="number"
|
|
93
|
-
step="0.1"
|
|
94
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-40 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
95
|
-
placeholder="0.0"
|
|
96
|
-
:value="currentValues[column.name]"
|
|
97
|
-
@input="setCurrentValue(column.name, $event.target.value)"
|
|
98
|
-
/>
|
|
99
|
-
<textarea
|
|
100
|
-
v-else-if="['text', 'richtext'].includes(column.type)"
|
|
101
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
102
|
-
placeholder="Text"
|
|
103
|
-
:value="currentValues[column.name]"
|
|
104
|
-
@input="setCurrentValue(column.name, $event.target.value)"
|
|
105
|
-
>
|
|
106
|
-
</textarea>
|
|
107
|
-
<textarea
|
|
108
|
-
v-else-if="['json'].includes(column.type)"
|
|
109
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
110
|
-
placeholder="Text"
|
|
111
|
-
:value="currentValues[column.name]"
|
|
112
|
-
@input="setCurrentValue(column.name, $event.target.value)"
|
|
113
|
-
>
|
|
114
|
-
</textarea>
|
|
115
|
-
<input
|
|
116
|
-
v-else
|
|
117
|
-
:type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
|
|
118
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
119
|
-
placeholder="Text"
|
|
120
|
-
:value="currentValues[column.name]"
|
|
121
|
-
@input="setCurrentValue(column.name, $event.target.value)"
|
|
122
|
-
autocomplete="false"
|
|
123
|
-
data-lpignore="true"
|
|
124
|
-
readonly
|
|
125
|
-
onfocus="this.removeAttribute('readonly');"
|
|
126
|
-
>
|
|
127
|
-
|
|
128
|
-
<button
|
|
129
|
-
v-if="column.masked"
|
|
130
|
-
type="button"
|
|
131
|
-
@click="unmasked[column.name] = !unmasked[column.name]"
|
|
132
|
-
class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
133
|
-
>
|
|
134
|
-
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
135
|
-
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
136
|
-
</button>
|
|
137
|
-
</template>
|
|
138
|
-
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
139
|
-
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
|
|
140
|
-
|
|
141
|
-
</td>
|
|
142
|
-
</tr>
|
|
143
|
-
|
|
144
|
-
</tbody>
|
|
145
|
-
</table>
|
|
146
|
-
</form>
|
|
147
|
-
</div>
|
|
3
|
+
<form autocomplete="off" @submit.prevent>
|
|
4
|
+
<div v-if="!coreStore.resource.options.createEditGroups || coreStore.resource.options.createEditGroups.length === 0">
|
|
5
|
+
<GroupsTable
|
|
6
|
+
:group="{groupName: '', columns: editableColumns}"
|
|
7
|
+
:currentValues="currentValues"
|
|
8
|
+
:editableColumns="editableColumns"
|
|
9
|
+
:mode="mode"
|
|
10
|
+
:unmasked="unmasked"
|
|
11
|
+
:columnOptions="columnOptions"
|
|
12
|
+
:validating="validating"
|
|
13
|
+
:columnError="columnError"
|
|
14
|
+
:setCurrentValue="setCurrentValue"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
<div v-else class="flex flex-col gap-4">
|
|
18
|
+
<template v-for="group in groupedColumns" :key="group.groupName" class="flex flex-col gap-4">
|
|
19
|
+
<GroupsTable
|
|
20
|
+
:group="group"
|
|
21
|
+
:currentValues="currentValues"
|
|
22
|
+
:editableColumns="editableColumns"
|
|
23
|
+
:mode="mode"
|
|
24
|
+
:unmasked="unmasked"
|
|
25
|
+
:columnOptions="columnOptions"
|
|
26
|
+
:validating="validating"
|
|
27
|
+
:columnError="columnError"
|
|
28
|
+
:setCurrentValue="setCurrentValue"
|
|
29
|
+
/>
|
|
30
|
+
</template>
|
|
31
|
+
<div v-if="otherColumns.length > 0">
|
|
32
|
+
<GroupsTable
|
|
33
|
+
:group="{groupName: 'Other', columns: otherColumns}"
|
|
34
|
+
:currentValues="currentValues"
|
|
35
|
+
:editableColumns="editableColumns"
|
|
36
|
+
:mode="mode"
|
|
37
|
+
:unmasked="unmasked"
|
|
38
|
+
:columnOptions="columnOptions"
|
|
39
|
+
:validating="validating"
|
|
40
|
+
:columnError="columnError"
|
|
41
|
+
:setCurrentValue="setCurrentValue"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</form>
|
|
148
46
|
</div>
|
|
149
47
|
|
|
150
48
|
</template>
|
|
151
49
|
|
|
152
50
|
<script setup>
|
|
153
51
|
|
|
154
|
-
import
|
|
155
|
-
import Dropdown from '@/components/Dropdown.vue';
|
|
156
|
-
import { applyRegexValidation, callAdminForthApi, getCustomComponent } from '@/utils';
|
|
157
|
-
import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
|
|
52
|
+
import { applyRegexValidation, callAdminForthApi} from '@/utils';
|
|
158
53
|
import { computedAsync } from '@vueuse/core';
|
|
159
|
-
import { initFlowbite } from 'flowbite';
|
|
160
54
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
161
55
|
import { useRouter, useRoute } from 'vue-router';
|
|
56
|
+
import { useCoreStore } from "@/stores/core";
|
|
57
|
+
import GroupsTable from '@/components/GroupsTable.vue';
|
|
58
|
+
|
|
59
|
+
const coreStore = useCoreStore();
|
|
162
60
|
|
|
163
61
|
const router = useRouter();
|
|
164
62
|
const route = useRoute();
|
|
@@ -179,7 +77,6 @@ const currentValues = ref(null);
|
|
|
179
77
|
|
|
180
78
|
const customComponentsInValidity = ref({});
|
|
181
79
|
const customComponentsEmptiness = ref({});
|
|
182
|
-
|
|
183
80
|
|
|
184
81
|
const columnError = (column) => {
|
|
185
82
|
const val = computed(() => {
|
|
@@ -281,9 +178,6 @@ onMounted(() => {
|
|
|
281
178
|
currentValues.value[column.name] = JSON.stringify(currentValues.value[column.name], null, 2);
|
|
282
179
|
}
|
|
283
180
|
});
|
|
284
|
-
console.log('currentValues', currentValues.value);
|
|
285
|
-
|
|
286
|
-
initFlowbite();
|
|
287
181
|
emit('update:isValid', isValid.value);
|
|
288
182
|
});
|
|
289
183
|
|
|
@@ -310,14 +204,34 @@ const columnOptions = computedAsync(async () => {
|
|
|
310
204
|
|
|
311
205
|
|
|
312
206
|
const editableColumns = computed(() => {
|
|
313
|
-
|
|
314
|
-
return props.resource?.columns?.filter(column => column.showIn.includes(mode));
|
|
207
|
+
return props.resource?.columns?.filter(column => column.showIn.includes(mode.value));
|
|
315
208
|
});
|
|
316
209
|
|
|
317
210
|
const isValid = computed(() => {
|
|
318
211
|
return editableColumns.value?.every(column => !columnError(column));
|
|
319
212
|
});
|
|
320
213
|
|
|
214
|
+
|
|
215
|
+
const groups = coreStore.resource.options.createEditGroups;
|
|
216
|
+
|
|
217
|
+
const groupedColumns = computed(() => {
|
|
218
|
+
if (!groups || groups.length === 0) return [];
|
|
219
|
+
|
|
220
|
+
return groups.map(group => ({
|
|
221
|
+
...group,
|
|
222
|
+
columns: props.resource.columns.filter(col => group.columns.includes(col.name) && editableColumns.value.includes(col))
|
|
223
|
+
}));
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const getOtherColumns = () => {
|
|
227
|
+
if (!groups || groups.length === 0) return;
|
|
228
|
+
|
|
229
|
+
const groupedColumnNames = new Set(groupedColumns.value.flatMap(group => group.columns.map(col => col.name)));
|
|
230
|
+
return editableColumns.value.filter(col => !groupedColumnNames.has(col.name));
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const otherColumns = getOtherColumns();
|
|
234
|
+
|
|
321
235
|
watch(() => isValid.value, (value) => {
|
|
322
236
|
emit('update:isValid', value);
|
|
323
237
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- table -->
|
|
3
|
-
<div class="relative
|
|
3
|
+
<div class="relative shadow-listTableShadow dark:shadow-darkListTableShadow overflow-auto "
|
|
4
4
|
:class="{'rounded-default': !noRoundings}"
|
|
5
5
|
>
|
|
6
6
|
<!-- skelet loader -->
|
|
@@ -11,53 +11,55 @@
|
|
|
11
11
|
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
|
+
<table v-else class=" w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
|
|
14
15
|
|
|
15
|
-
<table v-else class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 rounded-default">
|
|
16
|
-
<thead class="text-xs text-view-table-heading-text bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-gray-400">
|
|
17
|
-
<tr>
|
|
18
|
-
<th scope="col" class="p-4">
|
|
19
|
-
<div v-if="rows && rows.length" class="flex items-center">
|
|
20
|
-
<input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
|
|
21
|
-
class="w-4 h-4 cursor-pointer 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">
|
|
22
|
-
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
|
23
|
-
</div>
|
|
24
|
-
</th>
|
|
25
|
-
|
|
26
|
-
<th v-for="c in columnsListed" scope="col" class="px-2 md:px-3 lg:px-6 py-3">
|
|
27
|
-
|
|
28
|
-
<div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
|
|
29
|
-
class="flex items-center " :class="{'cursor-pointer':c.sortable}">
|
|
30
|
-
{{ c.label }}
|
|
31
|
-
|
|
32
|
-
<div v-if="c.sortable"
|
|
33
|
-
:style="{ 'color':ascArr.includes(c.name)?'green':descArr.includes(c.name)?'red':'currentColor'}">
|
|
34
|
-
<svg v-if="ascArr.includes(c.name) || descArr.includes(c.name)" class="w-3 h-3 ms-1.5"
|
|
35
|
-
:class="{'rotate-180':descArr.includes(c.name)}" viewBox="0 0 24 24">
|
|
36
|
-
<path
|
|
37
|
-
d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847"/>
|
|
38
|
-
</svg>
|
|
39
|
-
<svg v-else class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
|
40
|
-
fill='currentColor'
|
|
41
|
-
viewBox="0 0 24 24">
|
|
42
|
-
<path
|
|
43
|
-
d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"/>
|
|
44
|
-
</svg>
|
|
45
|
-
</div>
|
|
46
|
-
<span
|
|
47
|
-
class="bg-red-100 text-red-800 text-xs font-medium me-1 px-1 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
|
|
48
|
-
v-if="sort.findIndex((s) => s.field === c.name) !== -1 && sort?.length > 1">
|
|
49
|
-
{{ sort.findIndex((s) => s.field === c.name) + 1 }}
|
|
50
|
-
</span>
|
|
51
|
-
|
|
52
|
-
</div>
|
|
53
|
-
</th>
|
|
54
|
-
|
|
55
|
-
<th scope="col" class="px-6 py-3">
|
|
56
|
-
Actions
|
|
57
|
-
</th>
|
|
58
|
-
</tr>
|
|
59
|
-
</thead>
|
|
60
16
|
<tbody>
|
|
17
|
+
|
|
18
|
+
<!-- table header -->
|
|
19
|
+
<tr class="t-header sticky z-10 top-0 text-xs bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-gray-400">
|
|
20
|
+
<td scope="col" class="p-4">
|
|
21
|
+
<div v-if="rows && rows.length" class="flex items-center">
|
|
22
|
+
<input id="checkbox-all-search" type="checkbox" :checked="allFromThisPageChecked" @change="selectAll()"
|
|
23
|
+
class="w-4 h-4 cursor-pointer text-blue-600 bg-gray-100 border-gray-300 rounded
|
|
24
|
+
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">
|
|
25
|
+
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
|
26
|
+
</div>
|
|
27
|
+
</td>
|
|
28
|
+
|
|
29
|
+
<td v-for="c in columnsListed" scope="col" class="px-2 md:px-3 lg:px-6 py-3">
|
|
30
|
+
|
|
31
|
+
<div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
|
|
32
|
+
class="flex items-center " :class="{'cursor-pointer':c.sortable}">
|
|
33
|
+
{{ c.label }}
|
|
34
|
+
|
|
35
|
+
<div v-if="c.sortable">
|
|
36
|
+
<svg v-if="ascArr.includes(c.name) || descArr.includes(c.name)" class="w-3 h-3 ms-1.5"
|
|
37
|
+
fill='currentColor'
|
|
38
|
+
:class="{'rotate-180':descArr.includes(c.name)}" viewBox="0 0 24 24">
|
|
39
|
+
<path
|
|
40
|
+
d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847"/>
|
|
41
|
+
</svg>
|
|
42
|
+
<svg v-else class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
fill='currentColor'
|
|
44
|
+
viewBox="0 0 24 24">
|
|
45
|
+
<path
|
|
46
|
+
d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"/>
|
|
47
|
+
</svg>
|
|
48
|
+
</div>
|
|
49
|
+
<span
|
|
50
|
+
class="bg-red-100 text-red-800 text-xs font-medium me-1 px-1 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
|
|
51
|
+
v-if="sort.findIndex((s) => s.field === c.name) !== -1 && sort?.length > 1">
|
|
52
|
+
{{ sort.findIndex((s) => s.field === c.name) + 1 }}
|
|
53
|
+
</span>
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
</td>
|
|
57
|
+
|
|
58
|
+
<td scope="col" class="px-6 py-3">
|
|
59
|
+
Actions
|
|
60
|
+
</td>
|
|
61
|
+
</tr>
|
|
62
|
+
<!-- table header end -->
|
|
61
63
|
<SkeleteLoader
|
|
62
64
|
v-if="!rows"
|
|
63
65
|
:columns="resource?.columns.filter(c => c.showIn.includes('list')).length + 2"
|
|
@@ -106,65 +108,57 @@
|
|
|
106
108
|
/>
|
|
107
109
|
</td>
|
|
108
110
|
<td class=" items-center px-2 md:px-3 lg:px-6 py-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
|
|
109
|
-
<div class="flex">
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
</
|
|
160
|
-
|
|
161
|
-
<div :id="`tooltip-delete-${rowI}`"
|
|
162
|
-
role="tooltip"
|
|
163
|
-
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">
|
|
164
|
-
Delete
|
|
165
|
-
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
166
|
-
</div>
|
|
167
|
-
|
|
111
|
+
<div class="flex text-lightPrimary dark:text-darkPrimary items-center">
|
|
112
|
+
<AfTooltip>
|
|
113
|
+
<RouterLink
|
|
114
|
+
v-if="resource.options?.allowedActions.show"
|
|
115
|
+
:to="{
|
|
116
|
+
name: 'resource-show',
|
|
117
|
+
params: {
|
|
118
|
+
resourceId: resource.resourceId,
|
|
119
|
+
primaryKey: row._primaryKeyValue,
|
|
120
|
+
}
|
|
121
|
+
}"
|
|
122
|
+
|
|
123
|
+
>
|
|
124
|
+
<IconEyeSolid class="w-5 h-5 me-2"/>
|
|
125
|
+
</RouterLink>
|
|
126
|
+
|
|
127
|
+
<template v-slot:tooltip>
|
|
128
|
+
Show item
|
|
129
|
+
</template>
|
|
130
|
+
</AfTooltip>
|
|
131
|
+
|
|
132
|
+
<AfTooltip>
|
|
133
|
+
<RouterLink
|
|
134
|
+
v-if="resource.options?.allowedActions.edit"
|
|
135
|
+
:to="{
|
|
136
|
+
name: 'resource-edit',
|
|
137
|
+
params: {
|
|
138
|
+
resourceId: resource.resourceId,
|
|
139
|
+
primaryKey: row._primaryKeyValue,
|
|
140
|
+
}
|
|
141
|
+
}"
|
|
142
|
+
>
|
|
143
|
+
<IconPenSolid class="w-5 h-5 me-2"/>
|
|
144
|
+
</RouterLink>
|
|
145
|
+
<template v-slot:tooltip>
|
|
146
|
+
Edit item
|
|
147
|
+
</template>
|
|
148
|
+
</AfTooltip>
|
|
149
|
+
|
|
150
|
+
<AfTooltip>
|
|
151
|
+
<button
|
|
152
|
+
v-if="resource.options?.allowedActions.delete"
|
|
153
|
+
@click="deleteRecord(row)"
|
|
154
|
+
>
|
|
155
|
+
<IconTrashBinSolid class="w-5 h-5 me-2"/>
|
|
156
|
+
</button>
|
|
157
|
+
|
|
158
|
+
<template v-slot:tooltip>
|
|
159
|
+
Delete item
|
|
160
|
+
</template>
|
|
161
|
+
</AfTooltip>
|
|
168
162
|
|
|
169
163
|
<template v-if="coreStore.resourceOptions?.pageInjections?.list?.customActionIcons">
|
|
170
164
|
<component
|
|
@@ -185,30 +179,23 @@
|
|
|
185
179
|
<!-- pagination
|
|
186
180
|
totalRows in v-if is used to not hide page input during loading when user puts cursor into it and edit directly (rows gets null there during edit)
|
|
187
181
|
-->
|
|
188
|
-
<div class="flex flex-
|
|
182
|
+
<div class="flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3"
|
|
189
183
|
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
190
184
|
>
|
|
191
|
-
|
|
192
|
-
<
|
|
193
|
-
Showing <span class="font-semibold text-gray-900 dark:text-white">
|
|
194
|
-
{{ ((page || 1) - 1) * pageSize + 1 }}
|
|
195
|
-
</span> to <span class="font-semibold text-gray-900 dark:text-white">
|
|
196
|
-
{{ Math.min((page || 1) * pageSize, totalRows) }}
|
|
197
|
-
</span> of <span class="font-semibold text-gray-900 dark:text-white">{{
|
|
198
|
-
totalRows
|
|
199
|
-
}}</span> Entries
|
|
200
|
-
</span>
|
|
201
|
-
<div class="inline-flex mt-2 xs:mt-0">
|
|
185
|
+
|
|
186
|
+
<div class="inline-flex ">
|
|
202
187
|
<!-- Buttons -->
|
|
203
188
|
<button
|
|
204
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary 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"
|
|
189
|
+
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 rounded-s border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary 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"
|
|
205
190
|
@click="page--" :disabled="page <= 1">
|
|
206
|
-
<svg class="w-3.5 h-3.5
|
|
191
|
+
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
207
192
|
viewBox="0 0 14 10">
|
|
208
193
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
209
194
|
d="M13 5H1m0 0 4 4M1 5l4-4"/>
|
|
210
195
|
</svg>
|
|
211
|
-
|
|
196
|
+
<span class="hidden sm:inline">
|
|
197
|
+
Prev
|
|
198
|
+
</span>
|
|
212
199
|
</button>
|
|
213
200
|
<button
|
|
214
201
|
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-r-0 border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary 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"
|
|
@@ -232,11 +219,10 @@
|
|
|
232
219
|
<!-- <IconChevronDoubleRightOutline class="w-4 h-4" /> -->
|
|
233
220
|
</button>
|
|
234
221
|
<button
|
|
235
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary 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"
|
|
236
|
-
|
|
222
|
+
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-gray-900 focus:outline-none bg-white border-l-0 rounded-e border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary 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"
|
|
237
223
|
@click="page++" :disabled="page >= totalPages">
|
|
238
|
-
Next
|
|
239
|
-
<svg class="w-3.5 h-3.5
|
|
224
|
+
<span class="hidden sm:inline">Next</span>
|
|
225
|
+
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
240
226
|
viewBox="0 0 14 10">
|
|
241
227
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
242
228
|
d="M1 5h12m0 0L9 1m4 4L9 9"/>
|
|
@@ -244,6 +230,17 @@
|
|
|
244
230
|
</button>
|
|
245
231
|
</div>
|
|
246
232
|
|
|
233
|
+
<!-- Help text -->
|
|
234
|
+
<span class="text-sm text-gray-700 dark:text-gray-400">
|
|
235
|
+
<span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">Wrong Page </span>
|
|
236
|
+
<span v-else>Showing </span>
|
|
237
|
+
<span class="font-semibold text-gray-900 dark:text-white">
|
|
238
|
+
{{ ((page || 1) - 1) * pageSize + 1 }}
|
|
239
|
+
</span> to <span class="font-semibold text-gray-900 dark:text-white">
|
|
240
|
+
{{ Math.min((page || 1) * pageSize, totalRows) }}
|
|
241
|
+
</span> of <span class="font-semibold text-gray-900 dark:text-white">{{
|
|
242
|
+
totalRows }}</span> <span class="hidden sm:inline">Entries</span>
|
|
243
|
+
</span>
|
|
247
244
|
</div>
|
|
248
245
|
</template>
|
|
249
246
|
|
|
@@ -269,9 +266,11 @@ import {
|
|
|
269
266
|
IconTrashBinSolid
|
|
270
267
|
} from '@iconify-prerendered/vue-flowbite';
|
|
271
268
|
import router from '@/router';
|
|
269
|
+
import AfTooltip from './AfTooltip.vue';
|
|
272
270
|
|
|
273
271
|
const coreStore = useCoreStore();
|
|
274
272
|
|
|
273
|
+
|
|
275
274
|
const props = defineProps([
|
|
276
275
|
'page',
|
|
277
276
|
'resource',
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AfTooltip>
|
|
3
|
+
<span class="flex items-center">
|
|
4
|
+
{{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="w-5 h-5 text-lightPrimary dark:text-darkPrimary" v-if="visualValue"/>
|
|
5
|
+
</span>
|
|
6
|
+
<template #tooltip v-if="visualValue">
|
|
7
|
+
{{ props.record[props.column.name] }}
|
|
8
|
+
</template>
|
|
9
|
+
</AfTooltip>
|
|
10
|
+
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
15
|
+
import { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
|
|
16
|
+
import AfTooltip from '@/components/AfTooltip.vue';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const visualValue = computed(() => {
|
|
20
|
+
// if lenght is more then 8, show only first 4 and last 4 characters, ... in the middle
|
|
21
|
+
const val = props.record[props.column.name];
|
|
22
|
+
if (val && val.length > 8) {
|
|
23
|
+
return `${val.substr(0, 4)}...${val.substr(val.length - 4)}`;
|
|
24
|
+
}
|
|
25
|
+
return val;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const props = defineProps(['column', 'record', 'meta']);
|
|
29
|
+
|
|
30
|
+
const id = ref();
|
|
31
|
+
|
|
32
|
+
function copyToCB() {
|
|
33
|
+
navigator.clipboard.writeText(props.record[props.column.name]);
|
|
34
|
+
window.adminforth.alert({
|
|
35
|
+
message: 'ID copied to clipboard',
|
|
36
|
+
variant: 'success',
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onMounted(async () => {
|
|
41
|
+
id.value = Math.random().toString(36).substring(7);
|
|
42
|
+
await nextTick();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
</script>
|