adminforth 2.26.2 → 2.27.0-next.2
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/commands/createApp/templates/package.json.hbs +1 -1
- package/dist/modules/restApi.d.ts +1 -0
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +25 -1
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.js +2 -2
- package/dist/modules/styles.js.map +1 -1
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +7 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/package-lock.json +44 -7
- package/dist/spa/package.json +1 -1
- package/dist/spa/pnpm-lock.yaml +301 -299
- package/dist/spa/src/App.vue +1 -1
- package/dist/spa/src/adminforth.ts +17 -29
- package/dist/spa/src/afcl/Input.vue +1 -1
- package/dist/spa/src/afcl/Modal.vue +12 -1
- package/dist/spa/src/afcl/Select.vue +4 -2
- package/dist/spa/src/afcl/Table.vue +27 -13
- package/dist/spa/src/components/AcceptModal.vue +2 -0
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +11 -3
- package/dist/spa/src/components/CustomRangePicker.vue +16 -67
- package/dist/spa/src/components/ListActionsThreeDots.vue +9 -8
- package/dist/spa/src/components/RangePicker.vue +236 -0
- package/dist/spa/src/components/ResourceListTable.vue +45 -70
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +30 -52
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/stores/core.ts +4 -2
- package/dist/spa/src/types/Back.ts +11 -4
- package/dist/spa/src/types/Common.ts +26 -5
- package/dist/spa/src/types/FrontendAPI.ts +6 -1
- package/dist/spa/src/utils/listUtils.ts +8 -2
- package/dist/spa/src/utils/utils.ts +187 -10
- package/dist/spa/src/views/CreateView.vue +10 -10
- package/dist/spa/src/views/EditView.vue +10 -9
- package/dist/spa/src/views/ListView.vue +122 -18
- package/dist/spa/src/views/LoginView.vue +13 -13
- package/dist/spa/src/views/ShowView.vue +53 -60
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/types/Back.d.ts +8 -5
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +21 -5
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +13 -1
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="range-slider" ref="trackRef" @mousedown="onTrackMouseDown">
|
|
3
|
+
<div class="track"></div>
|
|
4
|
+
<div class="range bg-lightPrimary/30" :style="rangeStyle"></div>
|
|
5
|
+
|
|
6
|
+
<div
|
|
7
|
+
class="bg-lightPrimary thumb"
|
|
8
|
+
:style="minThumbStyle"
|
|
9
|
+
@mousedown.stop.prevent="startDrag('min', $event)"
|
|
10
|
+
@mouseenter="minHovered = true"
|
|
11
|
+
@mouseleave="minHovered = false"
|
|
12
|
+
></div>
|
|
13
|
+
<div v-if="minHovered || activeThumb === 'min'" class="thumb-tooltip" :style="minTooltipStyle">{{ minVal }}</div>
|
|
14
|
+
|
|
15
|
+
<div
|
|
16
|
+
class="bg-lightPrimary thumb"
|
|
17
|
+
:style="maxThumbStyle"
|
|
18
|
+
@mousedown.stop.prevent="startDrag('max', $event)"
|
|
19
|
+
@mouseenter="maxHovered = true"
|
|
20
|
+
@mouseleave="maxHovered = false"
|
|
21
|
+
></div>
|
|
22
|
+
<div v-if="maxHovered || activeThumb === 'max'" class="thumb-tooltip" :style="maxTooltipStyle">{{ maxVal }}</div>
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { computed, ref, watch, onBeforeUnmount } from 'vue'
|
|
29
|
+
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
modelValue: {
|
|
32
|
+
type: Array as unknown as () => [number, number],
|
|
33
|
+
default: () => [0, 100]
|
|
34
|
+
},
|
|
35
|
+
min: { type: Number, default: 0 },
|
|
36
|
+
max: { type: Number, default: 100 },
|
|
37
|
+
dotSize: { type: Number, default: 20 },
|
|
38
|
+
height: { type: String, default: '8px' }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits(['update:modelValue'])
|
|
42
|
+
|
|
43
|
+
const trackRef = ref<HTMLElement | null>(null)
|
|
44
|
+
|
|
45
|
+
const minVal = ref(props.modelValue[0])
|
|
46
|
+
const maxVal = ref(props.modelValue[1])
|
|
47
|
+
|
|
48
|
+
watch(() => props.modelValue, (val) => {
|
|
49
|
+
if (!val) return
|
|
50
|
+
minVal.value = val[0]
|
|
51
|
+
maxVal.value = val[1]
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
function clamp(val: number) {
|
|
55
|
+
return Math.min(props.max, Math.max(props.min, val))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function valueToPercent(val: number) {
|
|
59
|
+
return ((val - props.min) / (props.max - props.min)) * 100
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function percentToValue(percent: number) {
|
|
63
|
+
return props.min + ((props.max - props.min) * percent) / 100
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const minPercent = computed(() => valueToPercent(minVal.value))
|
|
67
|
+
const maxPercent = computed(() => valueToPercent(maxVal.value))
|
|
68
|
+
|
|
69
|
+
const rangeStyle = computed(() => ({
|
|
70
|
+
left: `${minPercent.value}%`,
|
|
71
|
+
width: `${maxPercent.value - minPercent.value}%`,
|
|
72
|
+
transition: isAnimating.value ? 'left 0.18s ease, width 0.18s ease' : 'none'
|
|
73
|
+
}))
|
|
74
|
+
|
|
75
|
+
const minThumbStyle = computed(() => ({
|
|
76
|
+
left: `calc(${minPercent.value}% - ${props.dotSize / 2}px)`,
|
|
77
|
+
width: `${props.dotSize}px`,
|
|
78
|
+
height: `${props.dotSize}px`,
|
|
79
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none',
|
|
80
|
+
zIndex: activeThumb.value === 'min' ? 3 : 2
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
const maxThumbStyle = computed(() => ({
|
|
84
|
+
left: `calc(${maxPercent.value}% - ${props.dotSize / 2}px)`,
|
|
85
|
+
width: `${props.dotSize}px`,
|
|
86
|
+
height: `${props.dotSize}px`,
|
|
87
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none',
|
|
88
|
+
zIndex: activeThumb.value === 'max' ? 3 : 2
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
const minTooltipStyle = computed(() => ({
|
|
92
|
+
left: `${minPercent.value}%`,
|
|
93
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none'
|
|
94
|
+
}))
|
|
95
|
+
|
|
96
|
+
const maxTooltipStyle = computed(() => ({
|
|
97
|
+
left: `${maxPercent.value}%`,
|
|
98
|
+
transition: isAnimating.value ? 'left 0.18s ease' : 'none'
|
|
99
|
+
}))
|
|
100
|
+
|
|
101
|
+
const activeThumb = ref<'min' | 'max' | null>(null)
|
|
102
|
+
const isAnimating = ref(false)
|
|
103
|
+
const minHovered = ref(false)
|
|
104
|
+
const maxHovered = ref(false)
|
|
105
|
+
|
|
106
|
+
function startDrag(type: 'min' | 'max', e: MouseEvent) {
|
|
107
|
+
activeThumb.value = type
|
|
108
|
+
document.addEventListener('mousemove', onMouseMove)
|
|
109
|
+
document.addEventListener('mouseup', stopDrag)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function onMouseMove(e: MouseEvent) {
|
|
113
|
+
if (!trackRef.value || !activeThumb.value) return
|
|
114
|
+
|
|
115
|
+
const rect = trackRef.value.getBoundingClientRect()
|
|
116
|
+
const percent = ((e.clientX - rect.left) / rect.width) * 100
|
|
117
|
+
const value = Math.round(clamp(percentToValue(percent)))
|
|
118
|
+
|
|
119
|
+
if (activeThumb.value === 'min') {
|
|
120
|
+
if (value > maxVal.value) {
|
|
121
|
+
// cross over: become the max thumb
|
|
122
|
+
minVal.value = maxVal.value
|
|
123
|
+
maxVal.value = value
|
|
124
|
+
activeThumb.value = 'max'
|
|
125
|
+
} else {
|
|
126
|
+
minVal.value = value
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
if (value < minVal.value) {
|
|
130
|
+
// cross over: become the min thumb
|
|
131
|
+
maxVal.value = minVal.value
|
|
132
|
+
minVal.value = value
|
|
133
|
+
activeThumb.value = 'min'
|
|
134
|
+
} else {
|
|
135
|
+
maxVal.value = value
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
emit('update:modelValue', [minVal.value, maxVal.value])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stopDrag() {
|
|
143
|
+
document.removeEventListener('mousemove', onMouseMove)
|
|
144
|
+
document.removeEventListener('mouseup', stopDrag)
|
|
145
|
+
activeThumb.value = null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function onTrackMouseDown(e: MouseEvent) {
|
|
149
|
+
if (!trackRef.value) return
|
|
150
|
+
|
|
151
|
+
const rect = trackRef.value.getBoundingClientRect()
|
|
152
|
+
const percent = ((e.clientX - rect.left) / rect.width) * 100
|
|
153
|
+
const value = percentToValue(percent)
|
|
154
|
+
|
|
155
|
+
const distToMin = Math.abs(value - minVal.value)
|
|
156
|
+
const distToMax = Math.abs(value - maxVal.value)
|
|
157
|
+
|
|
158
|
+
isAnimating.value = true
|
|
159
|
+
if (distToMin < distToMax) {
|
|
160
|
+
minVal.value = Math.round(Math.min(clamp(value), maxVal.value))
|
|
161
|
+
} else {
|
|
162
|
+
maxVal.value = Math.round(Math.max(clamp(value), minVal.value))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
emit('update:modelValue', [minVal.value, maxVal.value])
|
|
166
|
+
|
|
167
|
+
setTimeout(() => { isAnimating.value = false }, 200)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
onBeforeUnmount(() => {
|
|
171
|
+
stopDrag()
|
|
172
|
+
})
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<style scoped>
|
|
176
|
+
.range-slider {
|
|
177
|
+
position: relative;
|
|
178
|
+
width: 100%;
|
|
179
|
+
height: 20px;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.track {
|
|
185
|
+
position: absolute;
|
|
186
|
+
width: 100%;
|
|
187
|
+
height: 8px;
|
|
188
|
+
background: #e5e7eb;
|
|
189
|
+
border-radius: 9999px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.range {
|
|
193
|
+
position: absolute;
|
|
194
|
+
height: 8px;
|
|
195
|
+
border-radius: 9999px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.thumb {
|
|
199
|
+
position: absolute;
|
|
200
|
+
top: 50%;
|
|
201
|
+
transform: translateY(-50%);
|
|
202
|
+
border-radius: 9999px;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.thumb-tooltip {
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: -28px;
|
|
209
|
+
transform: translateX(-50%);
|
|
210
|
+
background: rgba(0, 0, 0, 0.75);
|
|
211
|
+
color: #fff;
|
|
212
|
+
font-size: 14px;
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
line-height: 1;
|
|
215
|
+
padding: 6px 6px;
|
|
216
|
+
border-radius: 4px;
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
pointer-events: none;
|
|
219
|
+
animation: tooltip-in 0.12s ease;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.thumb-tooltip::after {
|
|
223
|
+
content: '';
|
|
224
|
+
position: absolute;
|
|
225
|
+
top: 100%;
|
|
226
|
+
left: 50%;
|
|
227
|
+
transform: translateX(-50%);
|
|
228
|
+
border: 4px solid transparent;
|
|
229
|
+
border-top-color: rgba(0, 0, 0, 0.75);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@keyframes tooltip-in {
|
|
233
|
+
from { opacity: 0; transform: translateX(-50%) translateY(4px); }
|
|
234
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
235
|
+
}
|
|
236
|
+
</style>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- table -->
|
|
3
|
-
<div
|
|
4
|
-
class="relative shadow-listTableShadow dark:shadow-darkListTableShadow overflow-auto "
|
|
3
|
+
<div class="relative shadow-listTableShadow dark:shadow-darkListTableShadow overflow-auto border dark:border-gray-700"
|
|
5
4
|
:class="{'rounded-default': !noRoundings}"
|
|
6
5
|
:style="isVirtualScrollEnabled ? { maxHeight: `${containerHeight}px` } : {}"
|
|
7
6
|
@scroll="handleScroll"
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
|
|
22
21
|
<tbody>
|
|
23
22
|
<!-- table header -->
|
|
24
|
-
<tr class="t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
|
|
23
|
+
<tr class="border-b dark:border-gray-700 t-header sticky z-20 top-0 text-xs text-lightListTableHeadingText bg-lightListTableHeading dark:bg-darkListTableHeading dark:text-darkListTableHeadingText">
|
|
25
24
|
<td scope="col" class="list-table-header-cell p-4 sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading">
|
|
26
25
|
<Checkbox
|
|
27
26
|
:modelValue="allFromThisPageChecked"
|
|
@@ -34,10 +33,8 @@
|
|
|
34
33
|
|
|
35
34
|
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="list-table-header-cell px-2 md:px-3 lg:px-6 py-3" :class="{'sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading': c.listSticky}">
|
|
36
35
|
|
|
37
|
-
<div
|
|
38
|
-
|
|
39
|
-
class="flex items-center " :class="{'cursor-pointer':c.sortable}"
|
|
40
|
-
>
|
|
36
|
+
<div @click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
|
|
37
|
+
class="flex items-center font-semibold" :class="{'cursor-pointer':c.sortable}">
|
|
41
38
|
{{ c.label }}
|
|
42
39
|
|
|
43
40
|
<div v-if="c.sortable">
|
|
@@ -67,7 +64,7 @@
|
|
|
67
64
|
</div>
|
|
68
65
|
</td>
|
|
69
66
|
|
|
70
|
-
<td scope="col" class="px-6 py-3">
|
|
67
|
+
<td scope="col" class="px-6 py-3 font-semibold">
|
|
71
68
|
{{ $t('Actions') }}
|
|
72
69
|
</td>
|
|
73
70
|
</tr>
|
|
@@ -103,12 +100,12 @@
|
|
|
103
100
|
|
|
104
101
|
<component
|
|
105
102
|
v-for="(row, rowI) in rowsToRender"
|
|
106
|
-
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
103
|
+
:is="tableRowReplaceInjection ? getCustomComponent(formatComponent(tableRowReplaceInjection)) : 'tr'"
|
|
107
104
|
:key="`row_${row._primaryKeyValue}`"
|
|
108
105
|
:record="row"
|
|
109
106
|
:resource="resource"
|
|
110
107
|
:adminUser="coreStore.adminUser"
|
|
111
|
-
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
108
|
+
:meta="tableRowReplaceInjection ? formatComponent(tableRowReplaceInjection).meta : undefined"
|
|
112
109
|
@click="onClick($event, row)"
|
|
113
110
|
ref="rowRefs"
|
|
114
111
|
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
@@ -203,17 +200,17 @@
|
|
|
203
200
|
:key="action.id"
|
|
204
201
|
>
|
|
205
202
|
<component
|
|
206
|
-
|
|
207
|
-
:
|
|
203
|
+
v-if="action.customComponent"
|
|
204
|
+
:is="action.customComponent ? getCustomComponent(formatComponent(action.customComponent)) : CallActionWrapper"
|
|
205
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
208
206
|
:row="row"
|
|
209
207
|
:resource="resource"
|
|
210
|
-
:adminUser="adminUser"
|
|
211
|
-
@callAction="(payload? : Object) => startCustomAction(action.id, row, payload)"
|
|
208
|
+
:adminUser="coreStore.adminUser"
|
|
209
|
+
@callAction="(payload? : Object) => startCustomAction(action.id as string | number, row, payload)"
|
|
212
210
|
>
|
|
213
211
|
<button
|
|
214
212
|
type="button"
|
|
215
213
|
class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
|
|
216
|
-
:disabled="rowActionLoadingStates?.[action.id]"
|
|
217
214
|
>
|
|
218
215
|
<component
|
|
219
216
|
v-if="action.icon"
|
|
@@ -236,7 +233,7 @@
|
|
|
236
233
|
:deleteRecord="deleteRecord"
|
|
237
234
|
:resourceId="resource.resourceId"
|
|
238
235
|
:startCustomAction="startCustomAction"
|
|
239
|
-
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
|
|
236
|
+
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems ?? []"
|
|
240
237
|
/>
|
|
241
238
|
</div>
|
|
242
239
|
|
|
@@ -256,12 +253,12 @@
|
|
|
256
253
|
-->
|
|
257
254
|
<div class="af-pagination-container flex flex-row items-center mt-4 xs:flex-row xs:justify-between xs:items-center gap-3">
|
|
258
255
|
|
|
259
|
-
<div class="af-pagination-buttons-container inline-flex "
|
|
256
|
+
<div class="af-pagination-buttons-container af-button-shadow inline-flex rounded "
|
|
260
257
|
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
261
258
|
>
|
|
262
259
|
<!-- Buttons -->
|
|
263
260
|
<button
|
|
264
|
-
class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 rounded-s border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-
|
|
261
|
+
class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 rounded-s border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-20 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
265
262
|
@click="page--; pageInput = page.toString();"
|
|
266
263
|
:disabled="page <= 1"
|
|
267
264
|
>
|
|
@@ -273,7 +270,7 @@
|
|
|
273
270
|
</span>
|
|
274
271
|
</button>
|
|
275
272
|
<button
|
|
276
|
-
class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-
|
|
273
|
+
class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover z-10 focus:z-20 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
277
274
|
@click="page = 1;
|
|
278
275
|
pageInput = page.toString();"
|
|
279
276
|
:disabled="page <= 1"
|
|
@@ -284,13 +281,13 @@
|
|
|
284
281
|
type="text"
|
|
285
282
|
v-model="pageInput"
|
|
286
283
|
:style="{ width: `${Math.max(1, pageInput.length+4)}ch` }"
|
|
287
|
-
class="af-pagination-input min-w-10 outline-none inline-block py-1.5 px-3 text-sm text-center text-lightListTablePaginationCurrentPageText border border-lightListTablePaginationBorder dark:border-darkListTablePaginationBorder dark:text-darkListTablePaginationCurrentPageText dark:bg-darkListTablePaginationBackgoround
|
|
284
|
+
class="af-pagination-input z-10 min-w-10 outline-none inline-block py-1.5 px-3 text-sm text-center text-lightListTablePaginationCurrentPageText border border-lightListTablePaginationBorder dark:border-darkListTablePaginationBorder dark:text-darkListTablePaginationCurrentPageText dark:bg-darkListTablePaginationBackgoround"
|
|
288
285
|
@keydown="onPageKeydown($event)"
|
|
289
286
|
@blur="validatePageInput()"
|
|
290
287
|
/>
|
|
291
288
|
|
|
292
289
|
<button
|
|
293
|
-
class="af-pagination-last-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:
|
|
290
|
+
class="af-pagination-last-page-button z-10 flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:ring-4 focus:z-20 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-white dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
294
291
|
@click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
|
|
295
292
|
{{ totalPages }}
|
|
296
293
|
</button>
|
|
@@ -344,10 +341,10 @@
|
|
|
344
341
|
|
|
345
342
|
|
|
346
343
|
import { computed, onMounted, ref, watch, useTemplateRef, nextTick, type Ref } from 'vue';
|
|
347
|
-
import { callAdminForthApi } from '@/utils';
|
|
344
|
+
import { callAdminForthApi, executeCustomAction } from '@/utils';
|
|
348
345
|
import { useI18n } from 'vue-i18n';
|
|
349
346
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
350
|
-
import { getCustomComponent } from '@/utils';
|
|
347
|
+
import { getCustomComponent, formatComponent } from '@/utils';
|
|
351
348
|
import { useCoreStore } from '@/stores/core';
|
|
352
349
|
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
353
350
|
import SkeleteLoader from '@/components/SkeleteLoader.vue';
|
|
@@ -360,7 +357,7 @@ import {
|
|
|
360
357
|
} from '@iconify-prerendered/vue-flowbite';
|
|
361
358
|
import router from '@/router';
|
|
362
359
|
import { Tooltip } from '@/afcl';
|
|
363
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
360
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull, AdminForthComponentDeclaration } from '@/types/Common';
|
|
364
361
|
import { useAdminforth } from '@/adminforth';
|
|
365
362
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
366
363
|
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
|
|
@@ -383,8 +380,8 @@ const props = defineProps<{
|
|
|
383
380
|
containerHeight?: number,
|
|
384
381
|
itemHeight?: number,
|
|
385
382
|
bufferSize?: number,
|
|
386
|
-
customActionIconsThreeDotsMenuItems?:
|
|
387
|
-
tableRowReplaceInjection?:
|
|
383
|
+
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
|
|
384
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
388
385
|
isVirtualScrollEnabled: boolean
|
|
389
386
|
}>();
|
|
390
387
|
|
|
@@ -414,7 +411,7 @@ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
|
|
|
414
411
|
const showListActionsThreeDots = computed(() => {
|
|
415
412
|
return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
|
|
416
413
|
|| (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
|
|
417
|
-
|| !props.resource?.options
|
|
414
|
+
|| !props.resource?.options?.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
|
|
418
415
|
|| (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
|
|
419
416
|
})
|
|
420
417
|
|
|
@@ -609,51 +606,29 @@ async function deleteRecord(row: any) {
|
|
|
609
606
|
|
|
610
607
|
const actionLoadingStates = ref<Record<string | number, boolean>>({});
|
|
611
608
|
|
|
612
|
-
async function startCustomAction(actionId: string, row: any, extraData: Record<string, any> = {}) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (data?.redirectUrl) {
|
|
630
|
-
// Check if the URL should open in a new tab
|
|
631
|
-
if (data.redirectUrl.includes('target=_blank')) {
|
|
632
|
-
window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
|
|
633
|
-
} else {
|
|
634
|
-
// Navigate within the app
|
|
635
|
-
if (data.redirectUrl.startsWith('http')) {
|
|
636
|
-
window.location.href = data.redirectUrl;
|
|
637
|
-
} else {
|
|
638
|
-
router.push(data.redirectUrl);
|
|
609
|
+
async function startCustomAction(actionId: string | number, row: any, extraData: Record<string, any> = {}) {
|
|
610
|
+
await executeCustomAction({
|
|
611
|
+
actionId,
|
|
612
|
+
resourceId: props.resource?.resourceId || '',
|
|
613
|
+
recordId: row._primaryKeyValue,
|
|
614
|
+
extra: extraData,
|
|
615
|
+
setLoadingState: (loading: boolean) => {
|
|
616
|
+
actionLoadingStates.value[actionId] = loading;
|
|
617
|
+
},
|
|
618
|
+
onSuccess: async (data: any) => {
|
|
619
|
+
emits('update:records', true);
|
|
620
|
+
|
|
621
|
+
if (data.successMessage) {
|
|
622
|
+
alert({
|
|
623
|
+
message: data.successMessage,
|
|
624
|
+
variant: 'success'
|
|
625
|
+
});
|
|
639
626
|
}
|
|
627
|
+
},
|
|
628
|
+
onError: (error: string) => {
|
|
629
|
+
showErrorTost(error);
|
|
640
630
|
}
|
|
641
|
-
|
|
642
|
-
}
|
|
643
|
-
if (data?.ok) {
|
|
644
|
-
emits('update:records', true);
|
|
645
|
-
|
|
646
|
-
if (data.successMessage) {
|
|
647
|
-
alert({
|
|
648
|
-
message: data.successMessage,
|
|
649
|
-
variant: 'success'
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (data?.error) {
|
|
655
|
-
showErrorTost(data.error);
|
|
656
|
-
}
|
|
631
|
+
});
|
|
657
632
|
}
|
|
658
633
|
|
|
659
634
|
function validatePageInput() {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
<div v-if="coreStore
|
|
55
|
+
<div v-if="coreStore?.config?.defaultUserExists && !isLocalhost" class="p-4 mb-4 text-white rounded-lg bg-red-700/80 fill-white text-sm">
|
|
56
56
|
<IconExclamationCircleOutline class="inline-block align-text-bottom mr-0,5 w-5 h-5" />
|
|
57
57
|
Default user <strong>"adminforth"</strong> detected. Delete it and create your own account.
|
|
58
58
|
</div>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template >
|
|
2
|
-
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action:
|
|
2
|
+
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))">
|
|
3
3
|
<button
|
|
4
4
|
ref="buttonTriggerRef"
|
|
5
5
|
@click="toggleDropdownVisibility"
|
|
6
|
-
class="flex items-center py-2 px-2 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
|
|
6
|
+
class="flex transition-all items-center af-button-shadow py-2.5 px-2.5 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
|
|
7
7
|
>
|
|
8
8
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
|
|
9
9
|
<path d="M3.5 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 6.041a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 5.959a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/>
|
|
@@ -46,8 +46,9 @@
|
|
|
46
46
|
<li v-for="action in customActions" :key="action.id">
|
|
47
47
|
<div class="wrapper">
|
|
48
48
|
<component
|
|
49
|
-
|
|
50
|
-
:
|
|
49
|
+
v-if="action.customComponent"
|
|
50
|
+
:is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
|
|
51
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
51
52
|
@callAction="(payload? : Object) => handleActionClick(action, payload)"
|
|
52
53
|
>
|
|
53
54
|
<a @click.prevent class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
|
|
@@ -88,14 +89,14 @@
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
<script setup lang="ts">
|
|
91
|
-
import { getCustomComponent, getIcon } from '@/utils';
|
|
92
|
+
import { getCustomComponent, getIcon, formatComponent, executeCustomAction } from '@/utils';
|
|
92
93
|
import { useCoreStore } from '@/stores/core';
|
|
93
94
|
import { useAdminforth } from '@/adminforth';
|
|
94
95
|
import { callAdminForthApi } from '@/utils';
|
|
95
96
|
import { useRoute, useRouter } from 'vue-router';
|
|
96
97
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
97
98
|
import { ref, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
|
|
98
|
-
import type {
|
|
99
|
+
import type { AdminForthActionFront, AdminForthBulkActionFront, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
99
100
|
import type { AdminForthActionInput } from '@/types/Back';
|
|
100
101
|
|
|
101
102
|
const { list, alert} = useAdminforth();
|
|
@@ -109,8 +110,8 @@ const buttonTriggerRef = ref<HTMLElement | null>(null);
|
|
|
109
110
|
|
|
110
111
|
const props = defineProps({
|
|
111
112
|
threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
|
|
112
|
-
customActions: Array<
|
|
113
|
-
bulkActions: Array<
|
|
113
|
+
customActions: Array<AdminForthActionFront>,
|
|
114
|
+
bulkActions: Array<AdminForthBulkActionFront>,
|
|
114
115
|
checkboxes: Array,
|
|
115
116
|
updateList: {
|
|
116
117
|
type: Function,
|
|
@@ -130,55 +131,32 @@ function setComponentRef(el: ComponentPublicInstance | null, index: number) {
|
|
|
130
131
|
|
|
131
132
|
async function handleActionClick(action: AdminForthActionInput, payload: any) {
|
|
132
133
|
list.closeThreeDotsDropdown();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
134
|
+
await executeCustomAction({
|
|
135
|
+
actionId: action.id,
|
|
136
|
+
resourceId: route.params.resourceId as string,
|
|
137
|
+
recordId: route.params.primaryKey as string,
|
|
138
|
+
extra: payload || {},
|
|
139
|
+
onSuccess: async (data: any) => {
|
|
140
|
+
await coreStore.fetchRecord({
|
|
141
|
+
resourceId: route.params.resourceId as string,
|
|
142
|
+
primaryKey: route.params.primaryKey as string,
|
|
143
|
+
source: 'show',
|
|
144
|
+
});
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Navigate within the app
|
|
152
|
-
if (data.redirectUrl.startsWith('http')) {
|
|
153
|
-
window.location.href = data.redirectUrl;
|
|
154
|
-
} else {
|
|
155
|
-
router.push(data.redirectUrl);
|
|
146
|
+
if (data.successMessage) {
|
|
147
|
+
alert({
|
|
148
|
+
message: data.successMessage,
|
|
149
|
+
variant: 'success'
|
|
150
|
+
});
|
|
156
151
|
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (data?.ok) {
|
|
162
|
-
await coreStore.fetchRecord({
|
|
163
|
-
resourceId: route.params.resourceId as string,
|
|
164
|
-
primaryKey: route.params.primaryKey as string,
|
|
165
|
-
source: 'show',
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (data.successMessage) {
|
|
152
|
+
},
|
|
153
|
+
onError: (error: string) => {
|
|
169
154
|
alert({
|
|
170
|
-
message:
|
|
171
|
-
variant: '
|
|
155
|
+
message: error,
|
|
156
|
+
variant: 'danger'
|
|
172
157
|
});
|
|
173
158
|
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (data?.error) {
|
|
177
|
-
alert({
|
|
178
|
-
message: data.error,
|
|
179
|
-
variant: 'danger'
|
|
180
|
-
});
|
|
181
|
-
}
|
|
159
|
+
});
|
|
182
160
|
}
|
|
183
161
|
|
|
184
162
|
function startBulkAction(actionId: string) {
|
package/dist/spa/src/i18n.ts
CHANGED
|
@@ -4,9 +4,11 @@ import { callAdminForthApi } from '@/utils';
|
|
|
4
4
|
import websocket from '@/websocket';
|
|
5
5
|
import { useAdminforth } from '@/adminforth';
|
|
6
6
|
|
|
7
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend } from '@/types/Common';
|
|
7
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, GetBaseConfigResponse, ResourceVeryShort, AdminUser, UserData, AdminForthConfigMenuItem, AdminForthConfigForFrontend, AdminForthResourceFrontend } from '@/types/Common';
|
|
8
8
|
import type { Ref } from 'vue'
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
|
|
10
12
|
export const useCoreStore = defineStore('core', () => {
|
|
11
13
|
const { alert } = useAdminforth();
|
|
12
14
|
const resourceById: Ref<Record<string, ResourceVeryShort>> = ref({});
|
|
@@ -15,7 +17,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
15
17
|
const menu: Ref<AdminForthConfigMenuItem[]> = ref([]);
|
|
16
18
|
const config: Ref<AdminForthConfigForFrontend | null> = ref(null);
|
|
17
19
|
const record: Ref<any | null> = ref({});
|
|
18
|
-
const resource: Ref<
|
|
20
|
+
const resource: Ref<AdminForthResourceFrontend | null> = ref(null);
|
|
19
21
|
const userData: Ref<UserData | null> = ref(null);
|
|
20
22
|
const isResourceFetching = ref(false);
|
|
21
23
|
const isInternetError = ref(false);
|