@drax/dashboard-vue 2.7.0 → 2.8.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/package.json +4 -4
- package/src/components/DashboardConfig/DashboardCardEditor.vue +294 -58
- package/src/components/DashboardConfig/DashboardConfig.vue +15 -3
- package/src/components/DashboardView/DashboardView.vue +3 -3
- package/src/components/GroupByCard/renders/GroupByGalleryRender.vue +28 -13
- package/src/cruds/DashboardCrud.ts +90 -71
- package/src/pages/DashboardConfigPage.vue +6 -2
- package/src/pages/DashboardIdentifierPage.vue +2 -3
- package/src/pages/crud/DashboardCrudPage.vue +10 -2
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.8.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@drax/crud-front": "^2.0.0",
|
|
26
|
-
"@drax/crud-share": "^2.
|
|
27
|
-
"@drax/dashboard-front": "^2.
|
|
26
|
+
"@drax/crud-share": "^2.8.0",
|
|
27
|
+
"@drax/dashboard-front": "^2.8.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"pinia": "^3.0.4",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"vue-tsc": "^3.2.4",
|
|
47
47
|
"vuetify": "^3.11.8"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "3adeb31ee60eb83c92137dc28162f9226cab06c1"
|
|
50
50
|
}
|
|
@@ -1,31 +1,108 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import {ref, watch, computed, type PropType} from 'vue';
|
|
3
|
+
import type {IDashboardCard} from "@drax/dashboard-share";
|
|
4
4
|
import {useEntityStore} from "@drax/crud-vue";
|
|
5
|
+
import type {
|
|
6
|
+
IDraxFieldFilter,
|
|
7
|
+
IEntityCrud,
|
|
8
|
+
IEntityCrudFieldTypes,
|
|
9
|
+
IEntityCrudFilter,
|
|
10
|
+
IEntityCrudFilterOperators
|
|
11
|
+
} from "@drax/crud-share";
|
|
12
|
+
import {useI18n} from "vue-i18n"
|
|
13
|
+
import CrudFormField from "@drax/crud-vue/src/components/CrudFormField.vue";
|
|
14
|
+
import {useFilterIcon} from "@drax/crud-vue";
|
|
5
15
|
|
|
6
16
|
const props = defineProps({
|
|
7
|
-
modelValue: {
|
|
17
|
+
modelValue: {type: Object as PropType<IDashboardCard>, required: true}
|
|
8
18
|
});
|
|
19
|
+
const {filterIcon} = useFilterIcon()
|
|
20
|
+
const {t, te} = useI18n()
|
|
9
21
|
|
|
10
22
|
const emit = defineEmits(['update:modelValue', 'save', 'cancel']);
|
|
11
23
|
|
|
12
24
|
// Create a local reactive copy
|
|
13
25
|
const form = ref<IDashboardCard>(JSON.parse(JSON.stringify(props.modelValue)));
|
|
14
26
|
|
|
27
|
+
const entityStore = useEntityStore();
|
|
28
|
+
|
|
29
|
+
const entitySelected = computed<IEntityCrud>(() => {
|
|
30
|
+
return entityStore.entities.find((entity: any) => entity.name === form.value.entity)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const entityField = computed(() => {
|
|
34
|
+
return (fieldName: string) => {
|
|
35
|
+
if(!entitySelected.value){
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
return entitySelected.value.fields.find(f => f.name === fieldName)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const entities = computed(() => {
|
|
44
|
+
return entityStore.entities.map((entity: any) => entity.name)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const columns = computed(() => {
|
|
48
|
+
return entitySelected.value ? entitySelected.value.headers.map((h: any) => ({title: h.title, value: h.key})) : []
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const filters = ref<IEntityCrudFilter[]>(prepareFilters(form.value.filters));
|
|
53
|
+
|
|
54
|
+
function prepareFilters(filters: IDraxFieldFilter[] = []): IEntityCrudFilter[] {
|
|
55
|
+
|
|
56
|
+
return filters.map((filter: IDraxFieldFilter) => {
|
|
57
|
+
let theField = entityField.value(filter.field)
|
|
58
|
+
|
|
59
|
+
let value = filter.value
|
|
60
|
+
if(value === 'true'){
|
|
61
|
+
value = true
|
|
62
|
+
}
|
|
63
|
+
if(value === 'false'){
|
|
64
|
+
value = false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
name: filter.field,
|
|
69
|
+
operator: filter.operator as IEntityCrudFilterOperators,
|
|
70
|
+
value: value,
|
|
71
|
+
type: theField?.type || 'string',
|
|
72
|
+
label: theField?.label || '',
|
|
73
|
+
default: '',
|
|
74
|
+
ref: theField?.ref || '',
|
|
75
|
+
refDisplay: theField?.refDisplay || '',
|
|
76
|
+
enum: theField?.enum || []
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
15
82
|
watch(() => props.modelValue, (newVal) => {
|
|
16
83
|
form.value = JSON.parse(JSON.stringify(newVal));
|
|
17
|
-
|
|
84
|
+
ensureStructure();
|
|
85
|
+
}, {deep: true});
|
|
18
86
|
|
|
19
87
|
// Ensure nested objects exist to avoid v-model errors
|
|
20
88
|
const ensureStructure = () => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
89
|
+
if (!form.value.layout) form.value.layout = {cols: 12, sm: 12, md: 12, lg: 12, height: 450, cardVariant: 'elevated'};
|
|
90
|
+
if (!form.value.groupBy) form.value.groupBy = {fields: [], dateFormat: 'day', render: 'pie'};
|
|
91
|
+
if (!form.value.paginate) form.value.paginate = {columns: [], orderBy: '', order: ''};
|
|
92
|
+
if (!form.value.filters) form.value.filters = [];
|
|
24
93
|
};
|
|
25
94
|
ensureStructure();
|
|
26
95
|
|
|
27
96
|
const save = () => {
|
|
28
|
-
|
|
97
|
+
const payload = form.value
|
|
98
|
+
payload.filters = filters.value
|
|
99
|
+
.filter((filter: IEntityCrudFilter) => filter.name && filter.operator)
|
|
100
|
+
.map((filter: IEntityCrudFilter) => ({
|
|
101
|
+
field: filter.name,
|
|
102
|
+
operator: filter.operator as string,
|
|
103
|
+
value: filter.value == null ? '' : String(filter.value)
|
|
104
|
+
}))
|
|
105
|
+
emit('update:modelValue', payload);
|
|
29
106
|
emit('save');
|
|
30
107
|
};
|
|
31
108
|
|
|
@@ -33,29 +110,105 @@ const cancel = () => {
|
|
|
33
110
|
emit('cancel');
|
|
34
111
|
};
|
|
35
112
|
|
|
36
|
-
const entities = computed(() => {
|
|
37
|
-
const dashboardStore = useEntityStore();
|
|
38
|
-
return dashboardStore.entities.map((entity: any) => entity.name)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const columns = computed(() => {
|
|
42
|
-
const dashboardStore = useEntityStore();
|
|
43
|
-
const entity = dashboardStore.entities.find((entity: any) => entity.name === form.value.entity)
|
|
44
|
-
return entity ? entity.headers.map((h:any) => ({title: h.title, value: h.key}) ) : []
|
|
45
|
-
})
|
|
46
113
|
|
|
47
|
-
function onEntityChange(){
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if(form.value.paginate){
|
|
114
|
+
function onEntityChange() {
|
|
115
|
+
if (form.value) {
|
|
116
|
+
if (form.value.paginate) {
|
|
51
117
|
form.value.paginate.columns = []
|
|
52
118
|
}
|
|
53
|
-
if(form.value.groupBy){
|
|
119
|
+
if (form.value.groupBy) {
|
|
54
120
|
form.value.groupBy.fields = []
|
|
55
121
|
}
|
|
56
122
|
}
|
|
57
123
|
}
|
|
58
124
|
|
|
125
|
+
const fieldI18n = computed(() => {
|
|
126
|
+
return (field: IEntityCrudFilter) => {
|
|
127
|
+
return te(entitySelected.value?.name?.toLowerCase() + ".field." + field.name) ? t(entitySelected.value?.name?.toLowerCase() + ".field." + field.name) : field.label
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const selectableFields = computed(() => {
|
|
132
|
+
return entitySelected.value ?
|
|
133
|
+
entitySelected.value.fields
|
|
134
|
+
.filter((f: any) => !['fullFile', 'object', 'array.object'].includes(f.type))
|
|
135
|
+
.map((f: any) => ({title: fieldI18n.value(f), value: f.name}))
|
|
136
|
+
: []
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const dynamicFilter = computed(() => {
|
|
140
|
+
return (index: number) => {
|
|
141
|
+
return filters.value[index]
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const operations = [
|
|
146
|
+
{title: t('operation.equals'), value: 'eq'},
|
|
147
|
+
{title: t('operation.notEquals'), value: 'ne'},
|
|
148
|
+
{title: t('operation.contains'), value: 'like'},
|
|
149
|
+
{title: t('operation.greaterThan'), value: 'gt'},
|
|
150
|
+
{title: t('operation.lessThan'), value: 'lt'},
|
|
151
|
+
{title: t('operation.greaterThanOrEqual'), value: 'gte'},
|
|
152
|
+
{title: t('operation.lessThanOrEqual'), value: 'lte'},
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
function removeFilter(index: number) {
|
|
156
|
+
if (filters.value) {
|
|
157
|
+
filters.value.splice(index, 1)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function addFilter() {
|
|
162
|
+
const filter: IEntityCrudFilter = {
|
|
163
|
+
default: undefined,
|
|
164
|
+
label: "",
|
|
165
|
+
name: '',
|
|
166
|
+
operator: 'eq',
|
|
167
|
+
type: 'string',
|
|
168
|
+
permission: '',
|
|
169
|
+
value: ''
|
|
170
|
+
}
|
|
171
|
+
if (filters.value) {
|
|
172
|
+
filters.value.push(filter)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeFieldType(type: string): IEntityCrudFieldTypes {
|
|
177
|
+
if (type === 'array.ref') return 'ref';
|
|
178
|
+
if (type === 'array.string') return 'string';
|
|
179
|
+
if (type === 'longString') return 'string';
|
|
180
|
+
if (type === 'array.number') return 'number';
|
|
181
|
+
if (type === 'array.enum') return 'enum';
|
|
182
|
+
return type as IEntityCrudFieldTypes;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onUpdateField(index: number, val: string) {
|
|
186
|
+
const field = entitySelected.value.fields.find((e: any) => e.name === val)
|
|
187
|
+
let filter = dynamicFilter.value(index)
|
|
188
|
+
if (!filter) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
filter.value = null
|
|
192
|
+
if (!field) return
|
|
193
|
+
|
|
194
|
+
if (field.ref) {
|
|
195
|
+
filter.ref = field.ref
|
|
196
|
+
}
|
|
197
|
+
if (field.refDisplay) {
|
|
198
|
+
filter.refDisplay = field.refDisplay
|
|
199
|
+
}
|
|
200
|
+
if (field.enum) {
|
|
201
|
+
filter.enum = field.enum
|
|
202
|
+
}
|
|
203
|
+
if (field.type) {
|
|
204
|
+
filter.type = normalizeFieldType(field.type)
|
|
205
|
+
if(field.type === 'boolean'){
|
|
206
|
+
filter.value = false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
59
212
|
</script>
|
|
60
213
|
|
|
61
214
|
<template>
|
|
@@ -69,12 +222,13 @@ function onEntityChange(){
|
|
|
69
222
|
|
|
70
223
|
<v-card-text class="pt-4 flex-grow-1 overflow-y-auto">
|
|
71
224
|
<v-form @submit.prevent>
|
|
72
|
-
<v-row dense>
|
|
225
|
+
<v-row v-if="form" dense>
|
|
73
226
|
<!-- Base Config -->
|
|
74
227
|
<v-col cols="12" md="12">
|
|
75
|
-
<v-text-field v-model="form.title" label="Título de la tarjeta" variant="outlined" density="compact"
|
|
228
|
+
<v-text-field v-model="form.title" label="Título de la tarjeta" variant="outlined" density="compact"
|
|
229
|
+
hide-details="auto" class="mb-3"></v-text-field>
|
|
76
230
|
</v-col>
|
|
77
|
-
<v-col cols="12" md="6" lg="
|
|
231
|
+
<v-col cols="12" md="6" lg="6">
|
|
78
232
|
<v-select :items="entities"
|
|
79
233
|
v-model="form.entity"
|
|
80
234
|
label="Entidad (ej. User, Country)"
|
|
@@ -86,56 +240,134 @@ function onEntityChange(){
|
|
|
86
240
|
@update:modelValue="onEntityChange"
|
|
87
241
|
></v-select>
|
|
88
242
|
</v-col>
|
|
89
|
-
<v-col cols="12" md="6" lg="
|
|
90
|
-
<v-select v-model="form.type" :items="['groupBy' , 'paginate']" label="Tipo de tarjeta" variant="outlined"
|
|
243
|
+
<v-col cols="12" md="6" lg="6">
|
|
244
|
+
<v-select v-model="form.type" :items="['groupBy' , 'paginate']" label="Tipo de tarjeta" variant="outlined"
|
|
245
|
+
density="compact" hide-details="auto" class="mb-3"></v-select>
|
|
246
|
+
</v-col>
|
|
247
|
+
|
|
248
|
+
<v-col cols="12" md="6" lg="6">
|
|
249
|
+
<v-select v-model="form.layout!.cardVariant" :items="['elevated', 'outlined', 'flat','text','tonal','plain']" label="Tipo de tarjeta" variant="outlined"
|
|
250
|
+
density="compact" hide-details="auto" class="mb-3"></v-select>
|
|
91
251
|
</v-col>
|
|
92
252
|
|
|
93
|
-
|
|
94
|
-
|
|
253
|
+
|
|
254
|
+
<v-col cols="12" md="6" lg="6">
|
|
255
|
+
<v-text-field v-if="form.layout" v-model="form.layout!.height" label="Altura" variant="outlined"
|
|
256
|
+
density="compact" hide-details="auto" class="mb-3"></v-text-field>
|
|
95
257
|
</v-col>
|
|
96
258
|
|
|
259
|
+
|
|
260
|
+
<v-divider class="my-2 w-100"></v-divider>
|
|
261
|
+
<!-- Filters HERE -->
|
|
262
|
+
<v-row dense class="mt-4">
|
|
263
|
+
<v-col v-for="(filter,index) in filters"
|
|
264
|
+
:key="filter.name"
|
|
265
|
+
cols="12"
|
|
266
|
+
>
|
|
267
|
+
<v-row>
|
|
268
|
+
<v-col cols="12" sm="4">
|
|
269
|
+
<v-select
|
|
270
|
+
:items="selectableFields"
|
|
271
|
+
v-model="dynamicFilter(index)!.name"
|
|
272
|
+
:label="t('crud.field')"
|
|
273
|
+
density="compact"
|
|
274
|
+
variant="outlined"
|
|
275
|
+
hide-details
|
|
276
|
+
@update:modelValue="(v:string) => onUpdateField(index, v)"
|
|
277
|
+
/>
|
|
278
|
+
</v-col>
|
|
279
|
+
<v-col cols="12" sm="3">
|
|
280
|
+
<v-select
|
|
281
|
+
:items="operations"
|
|
282
|
+
v-model="dynamicFilter(index)!.operator"
|
|
283
|
+
:label="t('crud.operator')"
|
|
284
|
+
density="compact"
|
|
285
|
+
variant="outlined"
|
|
286
|
+
hide-details
|
|
287
|
+
/>
|
|
288
|
+
</v-col>
|
|
289
|
+
<v-col cols="12" sm="4">
|
|
290
|
+
<crud-form-field
|
|
291
|
+
v-if="entitySelected"
|
|
292
|
+
:field="filter"
|
|
293
|
+
:entity="entitySelected"
|
|
294
|
+
v-model="dynamicFilter(index)!.value"
|
|
295
|
+
:clearable="true"
|
|
296
|
+
density="compact"
|
|
297
|
+
variant="outlined"
|
|
298
|
+
:prepend-inner-icon="filterIcon(filter)"
|
|
299
|
+
hide-details
|
|
300
|
+
/>
|
|
301
|
+
</v-col>
|
|
302
|
+
<v-col cols="12" sm="1">
|
|
303
|
+
<v-btn @click="removeFilter(index)"
|
|
304
|
+
icon="mdi-delete"
|
|
305
|
+
class="mr-1"
|
|
306
|
+
variant="text"
|
|
307
|
+
color="red"
|
|
308
|
+
>
|
|
309
|
+
</v-btn>
|
|
310
|
+
</v-col>
|
|
311
|
+
</v-row>
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
</v-col>
|
|
315
|
+
|
|
316
|
+
<v-col cols="12">
|
|
317
|
+
<v-btn small variant="outlined" color="primary" @click="addFilter">+ {{ t('action.addFilter') }}</v-btn>
|
|
318
|
+
</v-col>
|
|
319
|
+
|
|
320
|
+
</v-row>
|
|
321
|
+
|
|
97
322
|
<v-divider class="my-2 w-100" v-if="form.type"></v-divider>
|
|
98
323
|
|
|
99
324
|
<!-- Type specific config -->
|
|
100
325
|
<template v-if="form.type === 'paginate'">
|
|
101
326
|
<v-col cols="12">
|
|
102
|
-
|
|
327
|
+
<div class="text-subtitle-2 mb-2 text-primary d-flex align-center">
|
|
328
|
+
<v-icon icon="mdi-table" size="small" class="mr-1"></v-icon>
|
|
329
|
+
Paginate
|
|
330
|
+
</div>
|
|
103
331
|
</v-col>
|
|
104
332
|
<v-col cols="12">
|
|
105
|
-
<v-select
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
333
|
+
<v-select item-title="title"
|
|
334
|
+
item-value="value"
|
|
335
|
+
:items="columns"
|
|
336
|
+
v-model="form.paginate!.columns"
|
|
337
|
+
label="Columnas (presiona enter)"
|
|
338
|
+
multiple chips variant="outlined"
|
|
339
|
+
density="compact"
|
|
340
|
+
hide-details="auto"
|
|
341
|
+
class="mb-3">
|
|
114
342
|
|
|
115
343
|
</v-select>
|
|
116
344
|
</v-col>
|
|
117
345
|
<v-col cols="12" md="6">
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
346
|
+
<v-select
|
|
347
|
+
item-title="title"
|
|
348
|
+
item-value="value"
|
|
349
|
+
:items="columns"
|
|
350
|
+
v-model="form.paginate!.orderBy"
|
|
351
|
+
label="Ordenar por campo"
|
|
352
|
+
variant="outlined"
|
|
353
|
+
density="compact"
|
|
354
|
+
hide-details="auto"
|
|
355
|
+
class="mb-3"
|
|
356
|
+
clearable
|
|
357
|
+
></v-select>
|
|
130
358
|
</v-col>
|
|
131
359
|
<v-col cols="12" md="6">
|
|
132
|
-
|
|
360
|
+
<v-select v-model="form.paginate!.order" clearable :items="['asc', 'desc']" label="Dirección de orden"
|
|
361
|
+
variant="outlined" density="compact" hide-details="auto" class="mb-3"></v-select>
|
|
133
362
|
</v-col>
|
|
134
363
|
</template>
|
|
135
364
|
|
|
136
365
|
<template v-else-if="form.type === 'groupBy'">
|
|
137
366
|
<v-col cols="12">
|
|
138
|
-
|
|
367
|
+
<div class="text-subtitle-2 mb-2 text-primary d-flex align-center">
|
|
368
|
+
<v-icon icon="mdi-chart-pie" size="small" class="mr-1"></v-icon>
|
|
369
|
+
Group By
|
|
370
|
+
</div>
|
|
139
371
|
</v-col>
|
|
140
372
|
<v-col cols="12">
|
|
141
373
|
<v-select
|
|
@@ -149,10 +381,14 @@ function onEntityChange(){
|
|
|
149
381
|
class="mb-3"></v-select>
|
|
150
382
|
</v-col>
|
|
151
383
|
<v-col cols="12" md="6">
|
|
152
|
-
|
|
384
|
+
<v-select v-model="form.groupBy!.dateFormat" :items="['year', 'month', 'day', 'hour', 'minute', 'second']"
|
|
385
|
+
label="Formato de Fecha (opcional)" variant="outlined" density="compact" hide-details="auto"
|
|
386
|
+
clearable class="mb-3"></v-select>
|
|
153
387
|
</v-col>
|
|
154
388
|
<v-col cols="12" md="6">
|
|
155
|
-
|
|
389
|
+
<v-select v-model="form.groupBy!.render" :items="['pie', 'bars', 'table', 'gallery']"
|
|
390
|
+
label="Render visual" variant="outlined" density="compact" hide-details="auto"
|
|
391
|
+
class="mb-3"></v-select>
|
|
156
392
|
</v-col>
|
|
157
393
|
</template>
|
|
158
394
|
</v-row>
|
|
@@ -169,7 +405,7 @@ function onEntityChange(){
|
|
|
169
405
|
|
|
170
406
|
<style scoped>
|
|
171
407
|
.dashboard-card-editor {
|
|
172
|
-
|
|
173
|
-
|
|
408
|
+
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
409
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1) !important;
|
|
174
410
|
}
|
|
175
411
|
</style>
|
|
@@ -4,6 +4,7 @@ import type {IDashboardBase, IDashboardCard} from "@drax/dashboard-share";
|
|
|
4
4
|
import GroupByCard from "../GroupByCard/GroupByCard.vue";
|
|
5
5
|
import PaginateCard from "../PaginateCard/PaginateCard.vue";
|
|
6
6
|
import DashboardCardEditor from "./DashboardCardEditor.vue";
|
|
7
|
+
import {debounce} from "@drax/common-front"
|
|
7
8
|
|
|
8
9
|
const valueModel = defineModel<IDashboardBase>({required: true})
|
|
9
10
|
|
|
@@ -88,17 +89,22 @@ const addNewCard = () => {
|
|
|
88
89
|
valueModel.value.cards.push({
|
|
89
90
|
title: 'Nueva Tarjeta',
|
|
90
91
|
entity: '',
|
|
92
|
+
filters: [],
|
|
91
93
|
type: 'groupBy',
|
|
92
|
-
layout: {cols: 12, sm: 12, md: 6, lg: 6, height: 450, cardVariant: '
|
|
94
|
+
layout: {cols: 12, sm: 12, md: 6, lg: 6, height: 450, cardVariant: 'elevated'}
|
|
93
95
|
});
|
|
94
96
|
editingCardIndex.value = valueModel.value.cards.length - 1;
|
|
95
97
|
};
|
|
96
98
|
|
|
99
|
+
|
|
100
|
+
|
|
97
101
|
const onSaveCard = () => {
|
|
98
102
|
emit("dashboardUpdated")
|
|
99
103
|
editingCardIndex.value = null;
|
|
100
104
|
};
|
|
101
105
|
|
|
106
|
+
const debouncedSave = debounce(onSaveCard, 500)
|
|
107
|
+
|
|
102
108
|
const onCancelCard = () => {
|
|
103
109
|
editingCardIndex.value = null;
|
|
104
110
|
};
|
|
@@ -108,10 +114,16 @@ const emit = defineEmits(["dashboardUpdated"])
|
|
|
108
114
|
</script>
|
|
109
115
|
|
|
110
116
|
<template>
|
|
111
|
-
<v-card v-if="valueModel" class="mt-3 valueModel-config-wrapper" variant="flat"
|
|
117
|
+
<v-card v-if="valueModel" class="mt-3 valueModel-config-wrapper" variant="flat" >
|
|
112
118
|
<v-card-title class="px-0 d-flex align-center">
|
|
113
|
-
|
|
119
|
+
<v-text-field
|
|
120
|
+
variant="solo-filled"
|
|
121
|
+
class="font-weight-semibold"
|
|
122
|
+
v-model="valueModel.title"
|
|
123
|
+
@update:modelValue="debouncedSave"
|
|
124
|
+
></v-text-field>
|
|
114
125
|
<v-spacer></v-spacer>
|
|
126
|
+
<slot name="buttons"></slot>
|
|
115
127
|
<v-btn color="primary" prepend-icon="mdi-plus" @click="addNewCard" elevation="2">Añadir Tarjeta</v-btn>
|
|
116
128
|
</v-card-title>
|
|
117
129
|
|
|
@@ -11,8 +11,8 @@ const {dashboard} = defineProps({
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<template>
|
|
14
|
-
<v-card v-if="dashboard" class="mt-3" >
|
|
15
|
-
<v-card-title>{{dashboard.title}}</v-card-title>
|
|
14
|
+
<v-card v-if="dashboard" class="mt-3" variant="flat" >
|
|
15
|
+
<v-card-title style="font-size: 2rem">{{dashboard.title}}</v-card-title>
|
|
16
16
|
<v-card-text>
|
|
17
17
|
<v-row>
|
|
18
18
|
<v-col v-for="(card,i) in dashboard.cards" :key="i"
|
|
@@ -21,7 +21,7 @@ const {dashboard} = defineProps({
|
|
|
21
21
|
:md="card?.layout?.md || 12"
|
|
22
22
|
:lg="card?.layout?.lg || 12"
|
|
23
23
|
>
|
|
24
|
-
<v-card :variant="card?.layout?.cardVariant || '
|
|
24
|
+
<v-card :variant="card?.layout?.cardVariant || 'elevated' " hover :height="card?.layout?.height || 300" style="overflow-y: auto">
|
|
25
25
|
<v-card-title>{{card?.title}}</v-card-title>
|
|
26
26
|
<v-card-text >
|
|
27
27
|
<paginate-card v-if="card?.type === 'paginate'" :card="card" />
|
|
@@ -90,10 +90,10 @@ const cardData = computed(() => {
|
|
|
90
90
|
<v-col
|
|
91
91
|
v-for="(card, index) in cardData"
|
|
92
92
|
:key="index"
|
|
93
|
-
cols="
|
|
94
|
-
sm="
|
|
95
|
-
md="
|
|
96
|
-
lg="
|
|
93
|
+
cols="12"
|
|
94
|
+
sm="6"
|
|
95
|
+
md="4"
|
|
96
|
+
lg="4"
|
|
97
97
|
class="pa-1"
|
|
98
98
|
>
|
|
99
99
|
<v-card
|
|
@@ -112,7 +112,7 @@ const cardData = computed(() => {
|
|
|
112
112
|
</div>
|
|
113
113
|
<v-chip
|
|
114
114
|
:color="card.color"
|
|
115
|
-
size="
|
|
115
|
+
size="small"
|
|
116
116
|
variant="flat"
|
|
117
117
|
class="mt-1"
|
|
118
118
|
>
|
|
@@ -126,16 +126,31 @@ const cardData = computed(() => {
|
|
|
126
126
|
|
|
127
127
|
<v-divider class="my-2"></v-divider>
|
|
128
128
|
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
<v-card
|
|
130
|
+
color="black"
|
|
131
|
+
variant="tonal"
|
|
132
|
+
class="gallery-card"
|
|
133
|
+
hover
|
|
134
|
+
>
|
|
135
|
+
<v-card-text class="pa-2">
|
|
136
|
+
<div class="d-flex flex-column align-center text-center">
|
|
137
|
+
<div class="card-value text-h5 font-weight-bold mb-1">
|
|
134
138
|
{{ totalCount }}
|
|
139
|
+
</div>
|
|
140
|
+
<div class="card-label text-caption text-truncate" >
|
|
141
|
+
TOTAL
|
|
142
|
+
</div>
|
|
143
|
+
<v-chip
|
|
144
|
+
color="black"
|
|
145
|
+
size="small"
|
|
146
|
+
variant="flat"
|
|
147
|
+
class="mt-1"
|
|
148
|
+
>
|
|
149
|
+
100%
|
|
135
150
|
</v-chip>
|
|
136
|
-
</
|
|
137
|
-
</v-card>
|
|
138
|
-
</
|
|
151
|
+
</div>
|
|
152
|
+
</v-card-text>
|
|
153
|
+
</v-card>
|
|
139
154
|
</template>
|
|
140
155
|
</div>
|
|
141
156
|
</template>
|
|
@@ -89,75 +89,75 @@ class DashboardCrud extends EntityCrud implements IEntityCrud {
|
|
|
89
89
|
return [
|
|
90
90
|
{name: 'identifier', type: 'string', label: 'identifier', default: ''},
|
|
91
91
|
{name: 'title', type: 'string', label: 'title', default: ''},
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
}
|
|
92
|
+
// {
|
|
93
|
+
// name: 'cards',
|
|
94
|
+
// type: 'array.object',
|
|
95
|
+
// label: 'cards',
|
|
96
|
+
// default: [],
|
|
97
|
+
// objectFields: [
|
|
98
|
+
// {name: 'title', type: 'string', label: 'title', default: ''},
|
|
99
|
+
// {name: 'entity', type: 'enum', enum: this.entities, label: 'entity', default: ''},
|
|
100
|
+
// {name: 'type', type: 'enum', label: 'type', default: null, enum: ['paginate', 'groupBy']},
|
|
101
|
+
// {
|
|
102
|
+
// name: 'filters',
|
|
103
|
+
// type: 'array.object',
|
|
104
|
+
// label: 'filters',
|
|
105
|
+
// default: [],
|
|
106
|
+
// objectFields: [
|
|
107
|
+
// {name: 'field', type: 'string', label: 'field', default: ''},
|
|
108
|
+
// {name: 'operator', type: 'string', label: 'operator', default: ''},
|
|
109
|
+
// {name: 'value', type: 'string', label: 'value', default: ''}]
|
|
110
|
+
// },
|
|
111
|
+
// {
|
|
112
|
+
// name: 'layout',
|
|
113
|
+
// type: 'object',
|
|
114
|
+
// label: 'layout',
|
|
115
|
+
// default: {"cols": 12, "sm": 12, "md": 12, "lg": 12, "height": 350, "cardVariant": "elevated"},
|
|
116
|
+
// objectFields: [
|
|
117
|
+
// {name: 'cols', type: 'number', label: 'cols', default: 12, sm: 3, md: 3, lg: 3},
|
|
118
|
+
// {name: 'sm', type: 'number', label: 'sm', default: 12, sm: 3, md: 3, lg: 3},
|
|
119
|
+
// {name: 'md', type: 'number', label: 'md', default: 12, sm: 3, md: 3, lg: 3},
|
|
120
|
+
// {name: 'lg', type: 'number', label: 'lg', default: 12, sm: 3, md: 3, lg: 3},
|
|
121
|
+
// {name: 'height', type: 'number', label: 'height', default: 350},
|
|
122
|
+
// {
|
|
123
|
+
// name: 'cardVariant',
|
|
124
|
+
// type: 'enum',
|
|
125
|
+
// label: 'cardVariant',
|
|
126
|
+
// default: 'elevated',
|
|
127
|
+
// enum: ['text', 'flat', 'elevated', 'tonal', 'outlined', 'plain']
|
|
128
|
+
// }]
|
|
129
|
+
// },
|
|
130
|
+
// {
|
|
131
|
+
// name: 'groupBy',
|
|
132
|
+
// type: 'object',
|
|
133
|
+
// label: 'groupBy',
|
|
134
|
+
// default: {"fields": [], "dateFormat": "day", "render": "table"},
|
|
135
|
+
// objectFields: [{name: 'fields', type: 'array.string', label: 'fields', default: []},
|
|
136
|
+
// {
|
|
137
|
+
// name: 'dateFormat',
|
|
138
|
+
// type: 'enum',
|
|
139
|
+
// label: 'dateFormat',
|
|
140
|
+
// default: 'day',
|
|
141
|
+
// enum: ['year', 'month', 'day', 'hour', 'minute', 'second']
|
|
142
|
+
// },
|
|
143
|
+
// {
|
|
144
|
+
// name: 'render',
|
|
145
|
+
// type: 'enum',
|
|
146
|
+
// label: 'render',
|
|
147
|
+
// default: 'table',
|
|
148
|
+
// enum: ['table', 'gallery', 'pie', 'bars']
|
|
149
|
+
// }]
|
|
150
|
+
// },
|
|
151
|
+
// {
|
|
152
|
+
// name: 'paginate',
|
|
153
|
+
// type: 'object',
|
|
154
|
+
// label: 'paginate',
|
|
155
|
+
// default: {"columns": [], "orderBy": "", "order": null},
|
|
156
|
+
// objectFields: [{name: 'columns', type: 'array.string', label: 'columns', default: []},
|
|
157
|
+
// {name: 'orderBy', type: 'string', label: 'orderBy', default: ''},
|
|
158
|
+
// {name: 'order', type: 'enum', label: 'order', default: null, enum: ['asc', 'desc']}]
|
|
159
|
+
// }]
|
|
160
|
+
// }
|
|
161
161
|
]
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -168,11 +168,11 @@ class DashboardCrud extends EntityCrud implements IEntityCrud {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
get isViewable() {
|
|
171
|
-
return
|
|
171
|
+
return false
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
get isEditable() {
|
|
175
|
-
return
|
|
175
|
+
return false
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
get isCreatable() {
|
|
@@ -223,6 +223,25 @@ class DashboardCrud extends EntityCrud implements IEntityCrud {
|
|
|
223
223
|
return []
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
get listMode(): 'table' | 'gallery' {
|
|
227
|
+
return 'gallery'
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get searchEnable(){
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
get dynamicFiltersEnable(){
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
get filterButtons(){
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
redirectOnCreate(item: any){
|
|
243
|
+
return '/dashboard/config/'+item.identifier
|
|
244
|
+
}
|
|
226
245
|
|
|
227
246
|
}
|
|
228
247
|
|
|
@@ -46,13 +46,17 @@ onMounted(() => {
|
|
|
46
46
|
|
|
47
47
|
<template>
|
|
48
48
|
<v-container fluid>
|
|
49
|
-
<v-btn size="small" prepend-icon="mdi-view-dashboard" :href="'/dashboard/view/'+identifier">ver</v-btn>
|
|
50
49
|
|
|
51
50
|
<v-skeleton-loader :loading="loading"/>
|
|
52
51
|
<dashboard-config v-if="dashboardSelected"
|
|
53
52
|
v-model="dashboardSelected"
|
|
54
53
|
@dashboardUpdated="updateDashboard"
|
|
55
|
-
|
|
54
|
+
>
|
|
55
|
+
<template v-slot:buttons>
|
|
56
|
+
<v-btn class="mx-3" prepend-icon="mdi-view-dashboard" :href="'/dashboard/view/'+identifier">ver</v-btn>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
</dashboard-config>
|
|
56
60
|
|
|
57
61
|
</v-container>
|
|
58
62
|
|
|
@@ -31,9 +31,8 @@ onMounted(() => {
|
|
|
31
31
|
</script>
|
|
32
32
|
|
|
33
33
|
<template>
|
|
34
|
-
<v-container >
|
|
35
|
-
|
|
36
|
-
<v-skeleton-loader :loading="loading" />
|
|
34
|
+
<v-container fluid >
|
|
35
|
+
<v-skeleton-loader :loading="loading" />
|
|
37
36
|
<dashboard-view v-if="dashboardSelected" :dashboard="dashboardSelected"></dashboard-view>
|
|
38
37
|
|
|
39
38
|
</v-container>
|
|
@@ -7,10 +7,18 @@ import type {IDashboard} from "@drax/dashboard-share";
|
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
9
|
<crud :entity="DashboardCrud.instance">
|
|
10
|
+
|
|
11
|
+
<template v-slot:item="{item}">
|
|
12
|
+
<v-card class="h-100 pa-2">
|
|
13
|
+
<v-card-title>{{item.title}}</v-card-title>
|
|
14
|
+
<v-card-subtitle>{{item.identifier}}</v-card-subtitle>
|
|
15
|
+
</v-card>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
10
18
|
<template v-slot:item.actions="{ item }">
|
|
11
|
-
<v-btn class="mx-1" variant="text" color="purple"
|
|
19
|
+
<v-btn class="mx-1" variant="text" color="purple" icon="mdi-view-dashboard" :href="'/dashboard/view/'+ (item as IDashboard)?.identifier">
|
|
12
20
|
</v-btn>
|
|
13
|
-
<v-btn class="mx-1" variant="text" color="indigo"
|
|
21
|
+
<v-btn class="mx-1" variant="text" color="indigo" icon="mdi-table-edit" :href="'/dashboard/config/'+ (item as IDashboard)?.identifier">
|
|
14
22
|
</v-btn>
|
|
15
23
|
</template>
|
|
16
24
|
</crud>
|