@drax/dashboard-vue 2.8.0 → 2.11.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 +5 -5
- package/src/components/DashboardConfig/DashboardCardEditor.vue +20 -91
- package/src/components/DashboardConfig/DashboardConfig.vue +1 -1
- package/src/components/GroupByCard/renders/GroupByTableRender.vue +7 -1
- package/src/components/GroupByCard/renders/GroupByGalleryRenderbkp.vue +0 -181
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.11.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"format": "prettier --write src/"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@drax/crud-front": "^2.
|
|
26
|
-
"@drax/crud-share": "^2.
|
|
27
|
-
"@drax/dashboard-front": "^2.
|
|
25
|
+
"@drax/crud-front": "^2.11.0",
|
|
26
|
+
"@drax/crud-share": "^2.11.0",
|
|
27
|
+
"@drax/dashboard-front": "^2.11.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": "8919d31d4d9512e48ac461b0876dc85a5849daea"
|
|
50
50
|
}
|
|
@@ -12,12 +12,13 @@ import type {
|
|
|
12
12
|
import {useI18n} from "vue-i18n"
|
|
13
13
|
import CrudFormField from "@drax/crud-vue/src/components/CrudFormField.vue";
|
|
14
14
|
import {useFilterIcon} from "@drax/crud-vue";
|
|
15
|
+
import {useDynamicFilters} from "@drax/crud-vue/src/composables/UseDynamicFilters.ts";
|
|
15
16
|
|
|
16
17
|
const props = defineProps({
|
|
17
18
|
modelValue: {type: Object as PropType<IDashboardCard>, required: true}
|
|
18
19
|
});
|
|
19
20
|
const {filterIcon} = useFilterIcon()
|
|
20
|
-
const {t
|
|
21
|
+
const {t} = useI18n()
|
|
21
22
|
|
|
22
23
|
const emit = defineEmits(['update:modelValue', 'save', 'cancel']);
|
|
23
24
|
|
|
@@ -122,92 +123,19 @@ function onEntityChange() {
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
126
|
+
const {
|
|
127
|
+
dynamicFilter,
|
|
128
|
+
selectableFields,
|
|
129
|
+
getOperations,
|
|
130
|
+
isValueRequired,
|
|
131
|
+
onUpdateField,
|
|
132
|
+
addFilter,
|
|
133
|
+
removeFilter
|
|
134
|
+
} = useDynamicFilters(
|
|
135
|
+
computed(() => form.value.entity),
|
|
136
|
+
computed(() => entitySelected.value.fields || []),
|
|
137
|
+
filters
|
|
138
|
+
)
|
|
211
139
|
|
|
212
140
|
</script>
|
|
213
141
|
|
|
@@ -273,22 +201,23 @@ function onUpdateField(index: number, val: string) {
|
|
|
273
201
|
density="compact"
|
|
274
202
|
variant="outlined"
|
|
275
203
|
hide-details
|
|
276
|
-
@update:modelValue="(
|
|
204
|
+
@update:modelValue="() => onUpdateField(index, true)"
|
|
277
205
|
/>
|
|
278
206
|
</v-col>
|
|
279
207
|
<v-col cols="12" sm="3">
|
|
280
208
|
<v-select
|
|
281
|
-
:items="
|
|
209
|
+
:items="getOperations(index)"
|
|
282
210
|
v-model="dynamicFilter(index)!.operator"
|
|
283
211
|
:label="t('crud.operator')"
|
|
284
212
|
density="compact"
|
|
285
213
|
variant="outlined"
|
|
286
214
|
hide-details
|
|
215
|
+
@update:modelValue="() => onUpdateField(index)"
|
|
287
216
|
/>
|
|
288
217
|
</v-col>
|
|
289
218
|
<v-col cols="12" sm="4">
|
|
290
219
|
<crud-form-field
|
|
291
|
-
v-if="entitySelected"
|
|
220
|
+
v-if="entitySelected && isValueRequired(index)"
|
|
292
221
|
:field="filter"
|
|
293
222
|
:entity="entitySelected"
|
|
294
223
|
v-model="dynamicFilter(index)!.value"
|
|
@@ -314,7 +243,7 @@ function onUpdateField(index: number, val: string) {
|
|
|
314
243
|
</v-col>
|
|
315
244
|
|
|
316
245
|
<v-col cols="12">
|
|
317
|
-
<v-btn small variant="outlined" color="primary" @click="addFilter">+ {{ t('action.addFilter') }}</v-btn>
|
|
246
|
+
<v-btn size="small" variant="outlined" color="primary" @click="addFilter">+ {{ t('action.addFilter') }}</v-btn>
|
|
318
247
|
</v-col>
|
|
319
248
|
|
|
320
249
|
</v-row>
|
|
@@ -46,11 +46,17 @@ const getPercentage = (count: number) => {
|
|
|
46
46
|
v-slot:[`item.${field.name}`]="{ value }"
|
|
47
47
|
>
|
|
48
48
|
<template v-if="['ref','object'].includes(field.type) && field.refDisplay">
|
|
49
|
-
{{value[field.refDisplay]}}
|
|
49
|
+
{{value ? value[field.refDisplay] : '-' }}
|
|
50
50
|
</template>
|
|
51
|
+
|
|
51
52
|
<template v-else-if="field.type === 'date'">
|
|
52
53
|
{{ formatDateByUnit(value, dateFormat) }}
|
|
53
54
|
</template>
|
|
55
|
+
|
|
56
|
+
<template v-else-if="field.type === 'number'">
|
|
57
|
+
{{ value.toLocaleString('es-ar') }}
|
|
58
|
+
</template>
|
|
59
|
+
|
|
54
60
|
<template v-else>
|
|
55
61
|
{{ value }}
|
|
56
62
|
</template>
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type {PropType} from "vue";
|
|
3
|
-
import {computed} from "vue";
|
|
4
|
-
import {useDateFormat} from "@drax/common-vue"
|
|
5
|
-
import type {IDraxDateFormatUnit} from "@drax/common-share";
|
|
6
|
-
import type {IEntityCrudField} from "@drax/crud-share";
|
|
7
|
-
|
|
8
|
-
const {formatDateByUnit} = useDateFormat()
|
|
9
|
-
|
|
10
|
-
const {data, fields, dateFormat} = defineProps({
|
|
11
|
-
data: {type: Array as PropType<any[]>, required: false},
|
|
12
|
-
headers: {type: Array as PropType<any[]>, required: false},
|
|
13
|
-
fields: {type: Array as PropType<IEntityCrudField[]>, required: false},
|
|
14
|
-
dateFormat: {type: String as PropType<IDraxDateFormatUnit>, required: false, default:'day'},
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
// Paleta de colores para las tarjetas
|
|
18
|
-
const colors = [
|
|
19
|
-
'purple', 'indigo', 'teal', 'orange', 'pink',
|
|
20
|
-
'cyan', 'lime', 'amber', 'deep-purple', 'light-blue',
|
|
21
|
-
'deep-orange', 'blue-grey', 'brown', 'red', 'green'
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
// Calcular el total de todos los counts
|
|
25
|
-
const totalCount = computed(() => {
|
|
26
|
-
if (!data || data.length === 0) return 0
|
|
27
|
-
return data.reduce((sum, item) => sum + (item.count || 0), 0)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// Preparar datos para las tarjetas
|
|
31
|
-
const cardData = computed(() => {
|
|
32
|
-
if (!data || data.length === 0) return []
|
|
33
|
-
|
|
34
|
-
return data.map((item, index) => {
|
|
35
|
-
const percentage = totalCount.value > 0 ? (item.count / totalCount.value) * 100 : 0
|
|
36
|
-
|
|
37
|
-
// Obtener el label combinando todos los campos excepto count
|
|
38
|
-
const labelParts: string[] = []
|
|
39
|
-
|
|
40
|
-
// Iterar sobre las claves del item excepto count
|
|
41
|
-
Object.keys(item).forEach(key => {
|
|
42
|
-
if (key === 'count') return
|
|
43
|
-
|
|
44
|
-
// Buscar el campo correspondiente en fields
|
|
45
|
-
const field = fields?.find(f => f.name === key)
|
|
46
|
-
const value = item[key]
|
|
47
|
-
|
|
48
|
-
if (!field || value === null || value === undefined) return
|
|
49
|
-
|
|
50
|
-
let formattedValue = ''
|
|
51
|
-
|
|
52
|
-
if (['ref','object'].includes(field.type) && field.refDisplay && value) {
|
|
53
|
-
formattedValue = value[field.refDisplay]
|
|
54
|
-
} else if (field.type === 'date' && value) {
|
|
55
|
-
formattedValue = formatDateByUnit(value, dateFormat)
|
|
56
|
-
} else if (field.type === 'enum' && value) {
|
|
57
|
-
formattedValue = value.toString()
|
|
58
|
-
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
59
|
-
formattedValue = JSON.stringify(value)
|
|
60
|
-
} else {
|
|
61
|
-
formattedValue = value?.toString() || ''
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (formattedValue) {
|
|
65
|
-
labelParts.push(formattedValue)
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const label = labelParts.length > 0 ? labelParts.join(' - ') : 'N/A'
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
label,
|
|
73
|
-
value: item.count || 0,
|
|
74
|
-
percentage,
|
|
75
|
-
color: colors[index % colors.length]
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
</script>
|
|
80
|
-
|
|
81
|
-
<template>
|
|
82
|
-
<div class="gallery-container">
|
|
83
|
-
<div v-if="!data || data.length === 0" class="empty-state">
|
|
84
|
-
<v-icon size="64" color="grey-lighten-1">mdi-view-grid</v-icon>
|
|
85
|
-
<p class="text-grey-lighten-1 mt-4">No hay datos para mostrar</p>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<template v-else>
|
|
89
|
-
<v-row dense class="ma-0">
|
|
90
|
-
<v-col
|
|
91
|
-
v-for="(card, index) in cardData"
|
|
92
|
-
:key="index"
|
|
93
|
-
cols="6"
|
|
94
|
-
sm="4"
|
|
95
|
-
md="3"
|
|
96
|
-
lg="2"
|
|
97
|
-
class="pa-1"
|
|
98
|
-
>
|
|
99
|
-
<v-card
|
|
100
|
-
:color="card.color"
|
|
101
|
-
variant="tonal"
|
|
102
|
-
class="gallery-card"
|
|
103
|
-
hover
|
|
104
|
-
>
|
|
105
|
-
<v-card-text class="pa-2">
|
|
106
|
-
<div class="d-flex flex-column align-center text-center">
|
|
107
|
-
<div class="card-value text-h5 font-weight-bold mb-1">
|
|
108
|
-
{{ card.value }}
|
|
109
|
-
</div>
|
|
110
|
-
<div class="card-label text-caption text-truncate" :title="card.label">
|
|
111
|
-
{{ card.label }}
|
|
112
|
-
</div>
|
|
113
|
-
<v-chip
|
|
114
|
-
:color="card.color"
|
|
115
|
-
size="x-small"
|
|
116
|
-
variant="flat"
|
|
117
|
-
class="mt-1"
|
|
118
|
-
>
|
|
119
|
-
{{ card.percentage.toFixed(1) }}%
|
|
120
|
-
</v-chip>
|
|
121
|
-
</div>
|
|
122
|
-
</v-card-text>
|
|
123
|
-
</v-card>
|
|
124
|
-
</v-col>
|
|
125
|
-
</v-row>
|
|
126
|
-
|
|
127
|
-
<v-divider class="my-2"></v-divider>
|
|
128
|
-
|
|
129
|
-
<div class="total-section pa-2">
|
|
130
|
-
<v-card variant="flat" color="grey-lighten-4">
|
|
131
|
-
<v-card-text class="pa-2 d-flex align-center justify-space-between">
|
|
132
|
-
<span class="text-subtitle-2 font-weight-bold">Total</span>
|
|
133
|
-
<v-chip color="primary" size="small" variant="flat">
|
|
134
|
-
{{ totalCount }}
|
|
135
|
-
</v-chip>
|
|
136
|
-
</v-card-text>
|
|
137
|
-
</v-card>
|
|
138
|
-
</div>
|
|
139
|
-
</template>
|
|
140
|
-
</div>
|
|
141
|
-
</template>
|
|
142
|
-
|
|
143
|
-
<style scoped>
|
|
144
|
-
.gallery-container {
|
|
145
|
-
width: 100%;
|
|
146
|
-
padding: 4px;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.empty-state {
|
|
150
|
-
display: flex;
|
|
151
|
-
flex-direction: column;
|
|
152
|
-
align-items: center;
|
|
153
|
-
justify-content: center;
|
|
154
|
-
padding: 32px 16px;
|
|
155
|
-
min-height: 200px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.gallery-card {
|
|
159
|
-
height: 100%;
|
|
160
|
-
transition: transform 0.2s, box-shadow 0.2s;
|
|
161
|
-
cursor: pointer;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.gallery-card:hover {
|
|
165
|
-
transform: translateY(-2px);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.card-value {
|
|
169
|
-
line-height: 1.2;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.card-label {
|
|
173
|
-
width: 100%;
|
|
174
|
-
line-height: 1.2;
|
|
175
|
-
max-width: 100%;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.total-section {
|
|
179
|
-
padding: 0 4px;
|
|
180
|
-
}
|
|
181
|
-
</style>
|