@drax/crud-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/EntityCrud.ts +13 -4
- package/src/components/Crud.vue +28 -4
- package/src/components/CrudActiveFilters.vue +2 -2
- package/src/components/CrudAutocomplete.vue +4 -4
- package/src/components/CrudFilters.vue +7 -15
- package/src/components/CrudFiltersAction.vue +37 -0
- package/src/components/CrudFiltersDynamic.vue +204 -0
- package/src/components/CrudList.vue +40 -18
- package/src/components/CrudListGallery.vue +300 -0
- package/src/components/CrudListTable.vue +222 -0
- package/src/composables/UseCrud.ts +61 -4
- package/src/index.ts +2 -0
- package/src/stores/UseCrudStore.ts +16 -0
- /package/src/composables/{useFilterIcon.ts → UseFilterIcon.ts} +0 -0
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",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"format": "prettier --write src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drax/common-front": "^2.
|
|
27
|
+
"@drax/common-front": "^2.8.0",
|
|
28
28
|
"@drax/crud-front": "^2.0.0",
|
|
29
|
-
"@drax/crud-share": "^2.
|
|
29
|
+
"@drax/crud-share": "^2.8.0",
|
|
30
30
|
"@drax/media-vue": "^2.2.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"vue-tsc": "^3.2.4",
|
|
51
51
|
"vuetify": "^3.11.8"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "3adeb31ee60eb83c92137dc28162f9226cab06c1"
|
|
54
54
|
}
|
package/src/EntityCrud.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
IEntityCrud, IEntityCrudForm, IEntityCrudHeader, IEntityCrudRefs,
|
|
3
3
|
IEntityCrudRules, IEntityCrudField, IEntityCrudPermissions,
|
|
4
|
-
IDraxCrudProvider, IEntityCrudFilter,
|
|
4
|
+
IDraxCrudProvider, IEntityCrudFilter, IEntityCrudFieldVariant, IDraxFieldFilter
|
|
5
5
|
} from "@drax/crud-share";
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
class EntityCrud implements IEntityCrud {
|
|
9
10
|
|
|
10
11
|
name: string = ''
|
|
@@ -125,11 +126,11 @@ class EntityCrud implements IEntityCrud {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
get formFilters():
|
|
129
|
+
get formFilters(): IDraxFieldFilter[] {
|
|
129
130
|
return this.filters.map(
|
|
130
131
|
(filter: IEntityCrudFilter) =>
|
|
131
132
|
({field: filter.name, value: filter.default ? filter.default : null, operator: (filter.operator ? filter.operator : 'eq')})
|
|
132
|
-
) as
|
|
133
|
+
) as IDraxFieldFilter[]
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
get refs(): IEntityCrudRefs {
|
|
@@ -138,7 +139,7 @@ class EntityCrud implements IEntityCrud {
|
|
|
138
139
|
|
|
139
140
|
getRef(ref: string): IEntityCrud {
|
|
140
141
|
if (!this.refs.hasOwnProperty(ref)) {
|
|
141
|
-
throw new Error("Ref not found: " + ref)
|
|
142
|
+
throw new Error("Ref not found: " + ref + " Refs Available: " + Object.getOwnPropertyNames(this.refs).join(", "))
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
return this.refs[ref] as IEntityCrud
|
|
@@ -238,6 +239,14 @@ class EntityCrud implements IEntityCrud {
|
|
|
238
239
|
return true
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
get filtersEnable(){
|
|
243
|
+
return true
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
get dynamicFiltersEnable(){
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
|
|
241
250
|
get filterButtons() {
|
|
242
251
|
return true
|
|
243
252
|
}
|
package/src/components/Crud.vue
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {onBeforeMount, type PropType} from "vue";
|
|
2
|
+
import {computed, onBeforeMount, type PropType} from "vue";
|
|
3
3
|
import type {IEntityCrud} from "@drax/crud-share";
|
|
4
|
-
import
|
|
4
|
+
import CrudListTable from "./CrudListTable.vue";
|
|
5
|
+
import CrudListGallery from "./CrudListGallery.vue";
|
|
5
6
|
import CrudForm from "./CrudForm.vue";
|
|
6
7
|
import CrudNotify from "./CrudNotify.vue";
|
|
7
8
|
import CrudDialog from "./CrudDialog.vue";
|
|
8
9
|
import {useCrud} from "../composables/UseCrud";
|
|
10
|
+
import {useDisplay} from 'vuetify'
|
|
9
11
|
|
|
10
12
|
const {entity} = defineProps({
|
|
11
13
|
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
@@ -24,6 +26,22 @@ onBeforeMount(() => {
|
|
|
24
26
|
|
|
25
27
|
const emit = defineEmits(['created', 'updated', 'deleted', 'viewed', 'canceled'])
|
|
26
28
|
|
|
29
|
+
const listComponent = computed(() => {
|
|
30
|
+
const listMode = entity?.listMode
|
|
31
|
+
switch (listMode){
|
|
32
|
+
case 'responsive':
|
|
33
|
+
return xs.value ? CrudListGallery : CrudListTable
|
|
34
|
+
case 'gallery':
|
|
35
|
+
return CrudListGallery
|
|
36
|
+
case 'table':
|
|
37
|
+
return CrudListTable
|
|
38
|
+
default:
|
|
39
|
+
return xs.value ? CrudListGallery : CrudListTable
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const {xs} = useDisplay()
|
|
44
|
+
|
|
27
45
|
|
|
28
46
|
</script>
|
|
29
47
|
|
|
@@ -31,7 +49,8 @@ const emit = defineEmits(['created', 'updated', 'deleted', 'viewed', 'canceled']
|
|
|
31
49
|
<v-container :fluid="entity.containerFluid" class="mt-5">
|
|
32
50
|
<v-card :class="entity.cardClass" :density="entity.cardDensity">
|
|
33
51
|
|
|
34
|
-
<
|
|
52
|
+
<component
|
|
53
|
+
:is="listComponent"
|
|
35
54
|
:entity="entity"
|
|
36
55
|
@create="onCreate"
|
|
37
56
|
@edit="onEdit"
|
|
@@ -66,13 +85,18 @@ const emit = defineEmits(['created', 'updated', 'deleted', 'viewed', 'canceled']
|
|
|
66
85
|
</slot>
|
|
67
86
|
</template>
|
|
68
87
|
|
|
88
|
+
<template v-slot:item="{item}">
|
|
89
|
+
<slot name="item" v-bind="{item}">
|
|
90
|
+
</slot>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
69
93
|
|
|
70
94
|
<template v-slot:item.actions="{item}">
|
|
71
95
|
<slot name="item.actions" v-bind="{item}">
|
|
72
96
|
</slot>
|
|
73
97
|
</template>
|
|
74
98
|
|
|
75
|
-
</
|
|
99
|
+
</component>
|
|
76
100
|
</v-card>
|
|
77
101
|
|
|
78
102
|
<crud-dialog
|
|
@@ -3,7 +3,7 @@ import { computed, type PropType } from 'vue'
|
|
|
3
3
|
import type { IEntityCrud } from '@drax/crud-share'
|
|
4
4
|
import { useCrudStore } from '../stores/UseCrudStore'
|
|
5
5
|
import { useI18n } from 'vue-i18n'
|
|
6
|
-
import { useFilterIcon } from '../composables/
|
|
6
|
+
import { useFilterIcon } from '../composables/UseFilterIcon'
|
|
7
7
|
import CrudRefDisplay from "./CrudRefDisplay.vue";
|
|
8
8
|
import {formatDate} from "@drax/common-front"
|
|
9
9
|
|
|
@@ -92,7 +92,7 @@ const removeFilter = (index: number) => {
|
|
|
92
92
|
const filterDef = props.entity.filters[index]
|
|
93
93
|
|
|
94
94
|
// Resetear al valor por defecto
|
|
95
|
-
filter.value = filterDef
|
|
95
|
+
filter.value = filterDef?.default
|
|
96
96
|
|
|
97
97
|
// Emitir evento para aplicar filtros
|
|
98
98
|
emit('filterRemoved')
|
|
@@ -146,14 +146,14 @@ defineEmits(['updateValue'])
|
|
|
146
146
|
:title="item.raw[itemTitle]"
|
|
147
147
|
:color="item.raw?.color"
|
|
148
148
|
:base-color="item.raw?.color"
|
|
149
|
-
:prepend-icon="item.raw?.icon"
|
|
149
|
+
:prepend-icon="typeof item.raw?.icon=== 'string' ? item.raw?.icon : null"
|
|
150
150
|
/>
|
|
151
151
|
</template>
|
|
152
152
|
|
|
153
153
|
<template v-slot:selection="{item}">
|
|
154
154
|
<v-chip tile density="compact"
|
|
155
155
|
:color="item.raw?.color"
|
|
156
|
-
:prepend-icon="item.raw?.icon"
|
|
156
|
+
:prepend-icon="typeof item.raw?.icon=== 'string' ? item.raw?.icon : null"
|
|
157
157
|
>
|
|
158
158
|
{{ item.raw[itemTitle] }}
|
|
159
159
|
</v-chip>
|
|
@@ -203,14 +203,14 @@ defineEmits(['updateValue'])
|
|
|
203
203
|
:title="item.raw[itemTitle]"
|
|
204
204
|
:color="item.raw?.color"
|
|
205
205
|
:base-color="item.raw?.color"
|
|
206
|
-
:prepend-icon="item.raw?.icon"
|
|
206
|
+
:prepend-icon="typeof item.raw?.icon=== 'string' ? item.raw?.icon : null"
|
|
207
207
|
/>
|
|
208
208
|
</template>
|
|
209
209
|
|
|
210
210
|
<template v-slot:selection="{item}">
|
|
211
211
|
<v-chip tile density="compact"
|
|
212
212
|
:color="item.raw?.color"
|
|
213
|
-
:prepend-icon="item.raw?.icon"
|
|
213
|
+
:prepend-icon="typeof item.raw?.icon=== 'string' ? item.raw?.icon : null"
|
|
214
214
|
>
|
|
215
215
|
{{ item.raw[itemTitle] }}
|
|
216
216
|
</v-chip>
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
import {computed, type PropType} from "vue";
|
|
3
3
|
import CrudFormField from "./CrudFormField.vue";
|
|
4
4
|
import type {IEntityCrud, IEntityCrudFilter} from "@drax/crud-share";
|
|
5
|
-
import {useI18n} from "vue-i18n";
|
|
6
5
|
import {useAuth} from "@drax/identity-vue";
|
|
7
|
-
import {useFilterIcon} from "../composables/
|
|
6
|
+
import {useFilterIcon} from "../composables/UseFilterIcon";
|
|
8
7
|
|
|
9
|
-
const {t} = useI18n()
|
|
10
8
|
const valueModel = defineModel({type: [Object]})
|
|
11
9
|
const {hasPermission} = useAuth()
|
|
12
10
|
const {filterIcon} = useFilterIcon()
|
|
13
11
|
|
|
14
|
-
const {entity,
|
|
12
|
+
const {entity, autoFilter} = defineProps({
|
|
15
13
|
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
16
|
-
|
|
14
|
+
autoFilter: {type: Boolean, default: false}
|
|
17
15
|
})
|
|
18
16
|
|
|
19
17
|
const aFields = computed(() => {
|
|
@@ -24,13 +22,15 @@ function filter() {
|
|
|
24
22
|
emit('applyFilter')
|
|
25
23
|
}
|
|
26
24
|
|
|
25
|
+
/*
|
|
27
26
|
function clear() {
|
|
28
27
|
emit('clearFilter')
|
|
29
28
|
}
|
|
29
|
+
*/
|
|
30
30
|
|
|
31
31
|
function onUpdateValue(){
|
|
32
|
-
if(
|
|
33
|
-
|
|
32
|
+
if(autoFilter){
|
|
33
|
+
filter()
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -69,14 +69,6 @@ const emit = defineEmits(['applyFilter', 'clearFilter'])
|
|
|
69
69
|
|
|
70
70
|
</v-row>
|
|
71
71
|
|
|
72
|
-
<v-card-actions v-if="actionButtons" class="pb-0">
|
|
73
|
-
<v-spacer />
|
|
74
|
-
<v-btn variant="text" density="compact" :class="entity.cleanFilterClass" @click="clear">{{ t('action.clear') }}</v-btn>
|
|
75
|
-
<v-btn variant="flat" density="compact" :class="entity.applyFilterClass" @click="filter">
|
|
76
|
-
{{ t('action.filter') }}
|
|
77
|
-
</v-btn>
|
|
78
|
-
</v-card-actions>
|
|
79
|
-
|
|
80
72
|
</v-card>
|
|
81
73
|
</template>
|
|
82
74
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {useI18n} from "vue-i18n";
|
|
3
|
+
import type {PropType} from "vue";
|
|
4
|
+
import type {IEntityCrud} from "@drax/crud-share";
|
|
5
|
+
const {t} = useI18n()
|
|
6
|
+
|
|
7
|
+
const {entity} = defineProps({
|
|
8
|
+
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
function filter() {
|
|
12
|
+
emit('applyFilter')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function clear() {
|
|
16
|
+
emit('clearFilter')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['applyFilter', 'clearFilter'])
|
|
20
|
+
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
|
|
25
|
+
<v-card-actions class="pb-0">
|
|
26
|
+
<v-spacer />
|
|
27
|
+
<v-btn variant="text" density="compact" :class="entity.cleanFilterClass" @click="clear">{{ t('action.clear') }}</v-btn>
|
|
28
|
+
<v-btn variant="flat" density="compact" :class="entity.applyFilterClass" @click="filter">
|
|
29
|
+
{{ t('action.filter') }}
|
|
30
|
+
</v-btn>
|
|
31
|
+
</v-card-actions>
|
|
32
|
+
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, type PropType} from "vue";
|
|
3
|
+
import CrudFormField from "./CrudFormField.vue";
|
|
4
|
+
import type {IEntityCrud, IEntityCrudFilter} from "@drax/crud-share";
|
|
5
|
+
import {useI18n} from "vue-i18n";
|
|
6
|
+
import {useAuth} from "@drax/identity-vue";
|
|
7
|
+
import {useFilterIcon} from "../composables/UseFilterIcon";
|
|
8
|
+
import {useCrudStore} from "../stores/UseCrudStore";
|
|
9
|
+
import {useEntityStore} from "../stores/UseEntityStore";
|
|
10
|
+
|
|
11
|
+
const {t, te} = useI18n()
|
|
12
|
+
const valueModel = defineModel({type: [Object]})
|
|
13
|
+
const {hasPermission} = useAuth()
|
|
14
|
+
const {filterIcon} = useFilterIcon()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const {entity, autoFilter} = defineProps({
|
|
18
|
+
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
19
|
+
autoFilter: {type: Boolean, default: false}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const store = useCrudStore(entity?.name)
|
|
23
|
+
const entityStore = useEntityStore();
|
|
24
|
+
const storeEntity = entityStore.entities.find((e: any) => e.name === entity.name)
|
|
25
|
+
|
|
26
|
+
const aFields = computed(() => {
|
|
27
|
+
return store.dynamicFilters
|
|
28
|
+
.filter((field: IEntityCrudFilter) => !field.permission || hasPermission(field.permission))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const dynamicFilter = computed(() => {
|
|
32
|
+
return (index: string | number) => {
|
|
33
|
+
return store.dynamicFilters[index]
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
function filter() {
|
|
38
|
+
emit('applyFilter')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
function clear() {
|
|
43
|
+
emit('clearFilter')
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
function onUpdateValue() {
|
|
48
|
+
if (autoFilter) {
|
|
49
|
+
filter()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fieldI18n = computed(() => {
|
|
54
|
+
return (field: IEntityCrudFilter) => {
|
|
55
|
+
return te(entity.name.toLowerCase() + ".field." + field.name) ? t(entity.name.toLowerCase() + ".field." + field.name) : field.label
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const selectableFields = computed(() => {
|
|
60
|
+
return storeEntity ? storeEntity.fields
|
|
61
|
+
.filter((f: any) => !['fullFile', 'object', 'array.object'].includes(f.type))
|
|
62
|
+
.map((f: any) => ({title: fieldI18n.value(f), value: f.name})) : []
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
function normalizeFieldType(type: string) :string{
|
|
66
|
+
if (type === 'array.ref') return 'ref';
|
|
67
|
+
if (type === 'array.string') return 'string';
|
|
68
|
+
if (type === 'longString') return 'string';
|
|
69
|
+
if (type === 'array.number') return 'number';
|
|
70
|
+
if (type === 'array.enum') return 'enum';
|
|
71
|
+
return type;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function onUpdateField(index: string | number, val: string){
|
|
75
|
+
|
|
76
|
+
const field = storeEntity.fields.find((e: any) => e.name === val)
|
|
77
|
+
dynamicFilter.value(index).value = null
|
|
78
|
+
|
|
79
|
+
if (!field) return
|
|
80
|
+
|
|
81
|
+
if(field.ref){
|
|
82
|
+
dynamicFilter.value(index).ref = field.ref
|
|
83
|
+
}
|
|
84
|
+
if(field.refDisplay){
|
|
85
|
+
dynamicFilter.value(index).refDisplay = field.refDisplay
|
|
86
|
+
}
|
|
87
|
+
if(field.enum){
|
|
88
|
+
dynamicFilter.value(index).enum = field.enum
|
|
89
|
+
}
|
|
90
|
+
if(field.type){
|
|
91
|
+
dynamicFilter.value(index).type = normalizeFieldType(field.type)
|
|
92
|
+
|
|
93
|
+
if(field.type === 'boolean'){
|
|
94
|
+
dynamicFilter.value(index).value = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const operations = [
|
|
101
|
+
{title: t('operation.equals'), value: 'eq'},
|
|
102
|
+
{title: t('operation.notEquals'), value: 'ne'},
|
|
103
|
+
{title: t('operation.contains'), value: 'like'},
|
|
104
|
+
{title: t('operation.greaterThan'), value: 'gt'},
|
|
105
|
+
{title: t('operation.lessThan'), value: 'lt'},
|
|
106
|
+
{title: t('operation.greaterThanOrEqual'), value: 'gte'},
|
|
107
|
+
{title: t('operation.lessThanOrEqual'), value: 'lte'},
|
|
108
|
+
// {title: t('operation.in'), value: 'in'},
|
|
109
|
+
// {title: t('operation.notIn'), value: 'nin'},
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
function removeFilter(index: string | number){
|
|
113
|
+
store.removeDynamicFilter(index)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function addFilter() {
|
|
117
|
+
const filter: IEntityCrudFilter = {
|
|
118
|
+
default: undefined,
|
|
119
|
+
label: "",
|
|
120
|
+
name: '',
|
|
121
|
+
operator: 'eq',
|
|
122
|
+
type: 'string',
|
|
123
|
+
permission: '',
|
|
124
|
+
value: ''
|
|
125
|
+
}
|
|
126
|
+
store.addDynamicFilter(filter)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
const emit = defineEmits(['applyFilter', 'clearFilter'])
|
|
131
|
+
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<v-row dense class="mt-4">
|
|
136
|
+
<v-col v-for="(filter,index) in aFields"
|
|
137
|
+
:key="filter.name"
|
|
138
|
+
cols="12" class="tdash"
|
|
139
|
+
>
|
|
140
|
+
<v-row >
|
|
141
|
+
<v-col cols="12" sm="4">
|
|
142
|
+
<v-select
|
|
143
|
+
:items="selectableFields"
|
|
144
|
+
v-model="dynamicFilter(index).name"
|
|
145
|
+
:label="t('crud.field')"
|
|
146
|
+
density="compact"
|
|
147
|
+
variant="outlined"
|
|
148
|
+
hide-details
|
|
149
|
+
@update:modelValue="(v:string) => onUpdateField(index, v)"
|
|
150
|
+
/>
|
|
151
|
+
</v-col>
|
|
152
|
+
<v-col cols="12" sm="3">
|
|
153
|
+
<v-select
|
|
154
|
+
:items="operations"
|
|
155
|
+
v-model="dynamicFilter(index).operator"
|
|
156
|
+
:label="t('crud.operator')"
|
|
157
|
+
density="compact"
|
|
158
|
+
variant="outlined"
|
|
159
|
+
hide-details
|
|
160
|
+
/>
|
|
161
|
+
</v-col>
|
|
162
|
+
<v-col cols="10" sm="4">
|
|
163
|
+
<crud-form-field
|
|
164
|
+
:field="filter"
|
|
165
|
+
:entity="entity"
|
|
166
|
+
v-model="dynamicFilter(index).value"
|
|
167
|
+
:clearable="true"
|
|
168
|
+
density="compact"
|
|
169
|
+
variant="outlined"
|
|
170
|
+
:prepend-inner-icon="filterIcon(filter)"
|
|
171
|
+
hide-details
|
|
172
|
+
@updateValue="onUpdateValue"
|
|
173
|
+
/>
|
|
174
|
+
</v-col>
|
|
175
|
+
<v-col cols="2" sm="1">
|
|
176
|
+
<v-btn @click="removeFilter(index)"
|
|
177
|
+
icon="mdi-delete"
|
|
178
|
+
class="mr-1"
|
|
179
|
+
variant="text"
|
|
180
|
+
color="red"
|
|
181
|
+
>
|
|
182
|
+
</v-btn>
|
|
183
|
+
</v-col>
|
|
184
|
+
</v-row>
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
</v-col>
|
|
188
|
+
|
|
189
|
+
<v-col cols="12">
|
|
190
|
+
<v-btn size="small" variant="outlined" color="primary" @click="addFilter">+ {{ t('action.addFilter') }}</v-btn>
|
|
191
|
+
</v-col>
|
|
192
|
+
|
|
193
|
+
</v-row>
|
|
194
|
+
|
|
195
|
+
</template>
|
|
196
|
+
|
|
197
|
+
<style scoped>
|
|
198
|
+
.tdash{
|
|
199
|
+
border-bottom-width: 0.5px;
|
|
200
|
+
border-bottom-color: lightgray;
|
|
201
|
+
border-bottom-style: dashed;
|
|
202
|
+
margin-bottom: 10px;
|
|
203
|
+
}
|
|
204
|
+
</style>
|
|
@@ -16,6 +16,8 @@ import type {IEntityCrud} from "@drax/crud-share";
|
|
|
16
16
|
import {useI18n} from "vue-i18n";
|
|
17
17
|
import CrudFilters from "./CrudFilters.vue";
|
|
18
18
|
import { useCrudColumns } from "../composables/UseCrudColumns";
|
|
19
|
+
import CrudFiltersDynamic from "./CrudFiltersDynamic.vue";
|
|
20
|
+
import CrudFiltersAction from "./CrudFiltersAction.vue";
|
|
19
21
|
|
|
20
22
|
const {t, te} = useI18n()
|
|
21
23
|
const {hasPermission} = useAuth()
|
|
@@ -133,25 +135,45 @@ defineEmits(['import', 'export', 'create', 'update', 'delete', 'view', 'edit'])
|
|
|
133
135
|
<v-card-text class="pt-0">
|
|
134
136
|
<slot name="filters" v-bind="{filters}"></slot>
|
|
135
137
|
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
138
|
+
<v-card variant="flat" v-if="!$slots.filters">
|
|
139
|
+
|
|
140
|
+
<crud-filters
|
|
141
|
+
v-if="entity.filtersEnable"
|
|
142
|
+
:entity="entity"
|
|
143
|
+
v-model="filters"
|
|
144
|
+
:auto-filter="!entity.filterButtons"
|
|
145
|
+
@clearFilter="clearFilters()"
|
|
146
|
+
@applyFilter="applyFilters()"
|
|
147
|
+
>
|
|
148
|
+
|
|
149
|
+
<template v-for="iFilter in entity.filters"
|
|
150
|
+
:key="iFilter.name"
|
|
151
|
+
v-slot:[`filter.${iFilter.name}`]="{filter, filterIndex}"
|
|
152
|
+
>
|
|
153
|
+
<slot v-if="$slots[`filter.${iFilter.name}`]"
|
|
154
|
+
:name="`filter.${iFilter.name}`"
|
|
155
|
+
v-bind="{filter, filterIndex}"
|
|
156
|
+
/>
|
|
157
|
+
</template>
|
|
158
|
+
</crud-filters>
|
|
159
|
+
|
|
160
|
+
<crud-filters-dynamic
|
|
161
|
+
v-if="entity.dynamicFiltersEnable"
|
|
162
|
+
:entity="entity"
|
|
163
|
+
v-model="filters"
|
|
164
|
+
:auto-filter="!entity.filterButtons"
|
|
165
|
+
@clearFilter="clearFilters()"
|
|
166
|
+
@applyFilter="applyFilters()"
|
|
148
167
|
>
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
168
|
+
</crud-filters-dynamic>
|
|
169
|
+
|
|
170
|
+
<crud-filters-action v-if="entity.filterButtons"
|
|
171
|
+
:entity="entity"
|
|
172
|
+
@clearFilter="clearFilters()"
|
|
173
|
+
@applyFilter="applyFilters()"
|
|
174
|
+
></crud-filters-action>
|
|
175
|
+
</v-card>
|
|
176
|
+
|
|
155
177
|
</v-card-text>
|
|
156
178
|
|
|
157
179
|
</v-card>
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {PropType} from 'vue'
|
|
3
|
+
import {onMounted} from 'vue'
|
|
4
|
+
import {useAuth} from '@drax/identity-vue'
|
|
5
|
+
import CrudSearch from "./CrudSearch.vue";
|
|
6
|
+
import {useCrud} from "../composables/UseCrud";
|
|
7
|
+
import CrudExportButton from "./buttons/CrudExportButton.vue";
|
|
8
|
+
import CrudImportButton from "./buttons/CrudImportButton.vue";
|
|
9
|
+
import CrudCreateButton from "./buttons/CrudCreateButton.vue";
|
|
10
|
+
import CrudUpdateButton from "./buttons/CrudUpdateButton.vue";
|
|
11
|
+
import CrudDeleteButton from "./buttons/CrudDeleteButton.vue";
|
|
12
|
+
import CrudViewButton from "./buttons/CrudViewButton.vue";
|
|
13
|
+
import CrudGroupByButton from "./buttons/CrudGroupByButton.vue";
|
|
14
|
+
import CrudColumnsButton from "./buttons/CrudColumnsButton.vue";
|
|
15
|
+
import CrudExportList from "./CrudExportList.vue";
|
|
16
|
+
import type {IEntityCrud} from "@drax/crud-share";
|
|
17
|
+
import {useI18n} from "vue-i18n";
|
|
18
|
+
import CrudFilters from "./CrudFilters.vue";
|
|
19
|
+
import {useCrudColumns} from "../composables/UseCrudColumns";
|
|
20
|
+
import CrudFiltersDynamic from "./CrudFiltersDynamic.vue";
|
|
21
|
+
import CrudFiltersAction from "./CrudFiltersAction.vue";
|
|
22
|
+
|
|
23
|
+
const {t, te} = useI18n()
|
|
24
|
+
const {hasPermission} = useAuth()
|
|
25
|
+
|
|
26
|
+
const {entity} = defineProps({
|
|
27
|
+
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
loading, itemsPerPage, page, search, totalItems, items,
|
|
32
|
+
doPaginate, filters, applyFilters, clearFilters, paginationError
|
|
33
|
+
} = useCrud(entity)
|
|
34
|
+
|
|
35
|
+
// Usar el composable de columnas
|
|
36
|
+
const {filteredHeaders} = useCrudColumns(entity)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
defineExpose({
|
|
40
|
+
doPaginate
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
defineEmits(['import', 'export', 'create', 'update', 'delete', 'view', 'edit'])
|
|
44
|
+
|
|
45
|
+
onMounted(() => {
|
|
46
|
+
doPaginate()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div v-if="hasPermission(entity.permissions.view)" class="d-flex flex-column h-100 pb-4">
|
|
53
|
+
<!-- Toolbar -->
|
|
54
|
+
<v-toolbar :class="entity.toolbarClass" :density="entity.toolbarDensity">
|
|
55
|
+
<v-toolbar-title>
|
|
56
|
+
{{ te(`${entity.name.toLowerCase()}.crud`) ? t(`${entity.name.toLowerCase()}.crud`) : entity.name }}
|
|
57
|
+
</v-toolbar-title>
|
|
58
|
+
<v-spacer></v-spacer>
|
|
59
|
+
|
|
60
|
+
<slot name="toolbar">
|
|
61
|
+
</slot>
|
|
62
|
+
|
|
63
|
+
<crud-import-button
|
|
64
|
+
:entity="entity"
|
|
65
|
+
@import="(v:any) => $emit('import', v)"
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
<crud-export-button
|
|
69
|
+
:entity="entity"
|
|
70
|
+
@export="(v:any) => $emit('export',v)"
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
<crud-group-by-button
|
|
74
|
+
v-if="entity.isGroupable"
|
|
75
|
+
:entity="entity"
|
|
76
|
+
/>
|
|
77
|
+
|
|
78
|
+
<crud-columns-button
|
|
79
|
+
v-if="entity.isColumnSelectable"
|
|
80
|
+
:entity="entity"
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<crud-create-button
|
|
84
|
+
v-if="entity.isCreatable"
|
|
85
|
+
:entity="entity"
|
|
86
|
+
@click="$emit('create')"
|
|
87
|
+
/>
|
|
88
|
+
</v-toolbar>
|
|
89
|
+
|
|
90
|
+
<crud-export-list
|
|
91
|
+
:entity="entity"
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
<v-card variant="flat">
|
|
95
|
+
<v-card-text v-if="entity.searchEnable">
|
|
96
|
+
<crud-search
|
|
97
|
+
v-model="search"
|
|
98
|
+
/>
|
|
99
|
+
</v-card-text>
|
|
100
|
+
|
|
101
|
+
<v-card-text class="pt-0">
|
|
102
|
+
<slot name="filters" v-bind="{filters}"></slot>
|
|
103
|
+
|
|
104
|
+
<v-card variant="flat" v-if="!$slots.filters">
|
|
105
|
+
|
|
106
|
+
<crud-filters
|
|
107
|
+
v-if="entity.filtersEnable"
|
|
108
|
+
:entity="entity"
|
|
109
|
+
v-model="filters"
|
|
110
|
+
:auto-filter="!entity.filterButtons"
|
|
111
|
+
@clearFilter="clearFilters()"
|
|
112
|
+
@applyFilter="applyFilters()"
|
|
113
|
+
>
|
|
114
|
+
|
|
115
|
+
<template v-for="iFilter in entity.filters"
|
|
116
|
+
:key="iFilter.name"
|
|
117
|
+
v-slot:[`filter.${iFilter.name}`]="{filter, filterIndex}"
|
|
118
|
+
>
|
|
119
|
+
<slot v-if="$slots[`filter.${iFilter.name}`]"
|
|
120
|
+
:name="`filter.${iFilter.name}`"
|
|
121
|
+
v-bind="{filter, filterIndex}"
|
|
122
|
+
/>
|
|
123
|
+
</template>
|
|
124
|
+
</crud-filters>
|
|
125
|
+
|
|
126
|
+
<crud-filters-dynamic
|
|
127
|
+
v-if="entity.dynamicFiltersEnable"
|
|
128
|
+
:entity="entity"
|
|
129
|
+
v-model="filters"
|
|
130
|
+
:auto-filter="!entity.filterButtons"
|
|
131
|
+
@clearFilter="clearFilters()"
|
|
132
|
+
@applyFilter="applyFilters()"
|
|
133
|
+
>
|
|
134
|
+
</crud-filters-dynamic>
|
|
135
|
+
|
|
136
|
+
<crud-filters-action v-if="entity.filterButtons"
|
|
137
|
+
:entity="entity"
|
|
138
|
+
@clearFilter="clearFilters()"
|
|
139
|
+
@applyFilter="applyFilters()"
|
|
140
|
+
></crud-filters-action>
|
|
141
|
+
</v-card>
|
|
142
|
+
|
|
143
|
+
</v-card-text>
|
|
144
|
+
|
|
145
|
+
</v-card>
|
|
146
|
+
|
|
147
|
+
<v-divider></v-divider>
|
|
148
|
+
|
|
149
|
+
<!-- CONTENT GALLERY -->
|
|
150
|
+
<v-container fluid class="flex-grow-1 position-relative pa-4">
|
|
151
|
+
<v-overlay :model-value="loading" contained class="align-center justify-center bg-transparent" scrim="transparent"
|
|
152
|
+
persistent z-index="4">
|
|
153
|
+
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
|
154
|
+
</v-overlay>
|
|
155
|
+
|
|
156
|
+
<v-alert
|
|
157
|
+
v-if="paginationError"
|
|
158
|
+
variant="tonal"
|
|
159
|
+
class="w-100 mb-4"
|
|
160
|
+
prominent
|
|
161
|
+
type="error"
|
|
162
|
+
:text="te(paginationError) ? t(paginationError) : paginationError"
|
|
163
|
+
/>
|
|
164
|
+
<v-alert v-else-if="!loading && items.length === 0" variant="tonal" class="w-100 mb-4" type="info"
|
|
165
|
+
:text="te('crud.noData') ? t('crud.noData') : 'No data'"/>
|
|
166
|
+
|
|
167
|
+
<!-- GALLERY GRIDS -->
|
|
168
|
+
<v-row v-if="items.length > 0">
|
|
169
|
+
<v-col v-for="item in items" :key="item.id || item.uuid || item.name || Math.random()" cols="12" sm="6" md="4"
|
|
170
|
+
xl="3">
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
<v-card class="h-100 d-flex flex-column hover-card" elevation="2" border>
|
|
174
|
+
<slot name="item" v-bind="{item}">
|
|
175
|
+
<v-card-text class="field-grid">
|
|
176
|
+
<template v-for="header in filteredHeaders.filter(h => h.key !=='actions')" :key="header.key">
|
|
177
|
+
|
|
178
|
+
<div class="field-label font-weight-regular text-grey-darken-2">
|
|
179
|
+
{{
|
|
180
|
+
te(`${entity.name.toLowerCase()}.${header.key}`) ? t(`${entity.name.toLowerCase()}.${header.key}`) : (header.title || header.key)
|
|
181
|
+
}}
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div class="field-value font-weight-medium">
|
|
185
|
+
<slot v-if="$slots[`item.${header.key}`]" :name="`item.${header.key}`"
|
|
186
|
+
v-bind="{item, value: item[header.key]}">
|
|
187
|
+
{{ item[header.key] }}
|
|
188
|
+
</slot>
|
|
189
|
+
<template v-else>
|
|
190
|
+
{{ item[header.key] }}
|
|
191
|
+
</template>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
</template>
|
|
195
|
+
</v-card-text>
|
|
196
|
+
</slot>
|
|
197
|
+
|
|
198
|
+
<v-divider></v-divider>
|
|
199
|
+
|
|
200
|
+
<v-card-actions class="bg-grey-lighten-4 py-2 px-4 d-flex justify-end flex-wrap gap-2">
|
|
201
|
+
<slot name="item.actions" v-bind="{item}">
|
|
202
|
+
</slot>
|
|
203
|
+
|
|
204
|
+
<crud-view-button
|
|
205
|
+
v-if="entity.isViewable && hasPermission(entity.permissions.view)"
|
|
206
|
+
@click="$emit('view', item)"
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
<crud-update-button
|
|
210
|
+
v-if="entity.isEditable && entity.isItemEditable(item) && hasPermission(entity.permissions?.update)"
|
|
211
|
+
@click="$emit('edit', item)"
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
<crud-delete-button
|
|
215
|
+
v-if="entity.isDeletable && hasPermission(entity.permissions?.delete)"
|
|
216
|
+
@click="$emit('delete', item)"
|
|
217
|
+
/>
|
|
218
|
+
</v-card-actions>
|
|
219
|
+
</v-card>
|
|
220
|
+
</v-col>
|
|
221
|
+
</v-row>
|
|
222
|
+
</v-container>
|
|
223
|
+
|
|
224
|
+
<!-- FOOTER WITH ALIGNED PAGINATION -->
|
|
225
|
+
<v-divider></v-divider>
|
|
226
|
+
<div :class="['d-flex align-center justify-space-between flex-wrap px-4 py-3 bg-surface', entity.footerClass]">
|
|
227
|
+
<div class="d-flex align-center mb-2 mb-sm-0">
|
|
228
|
+
<span class="text-body-2 mr-2 text-white">{{
|
|
229
|
+
te('crud.itemsPerPage') ? t('crud.itemsPerPage') : 'Items per page:'
|
|
230
|
+
}}</span>
|
|
231
|
+
<v-select
|
|
232
|
+
v-model="itemsPerPage"
|
|
233
|
+
:items="[5, 10, 20, 50]"
|
|
234
|
+
variant="outlined"
|
|
235
|
+
density="compact"
|
|
236
|
+
hide-details
|
|
237
|
+
class="pagination-select"
|
|
238
|
+
@update:model-value="doPaginate"
|
|
239
|
+
></v-select>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<v-pagination
|
|
243
|
+
v-model="page"
|
|
244
|
+
:length="Math.ceil(totalItems / itemsPerPage) || 1"
|
|
245
|
+
:total-visible="5"
|
|
246
|
+
density="comfortable"
|
|
247
|
+
active-color="primary"
|
|
248
|
+
@update:model-value="doPaginate"
|
|
249
|
+
></v-pagination>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</template>
|
|
253
|
+
|
|
254
|
+
<style scoped>
|
|
255
|
+
.hover-card {
|
|
256
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.hover-card:hover {
|
|
260
|
+
transform: translateY(-4px);
|
|
261
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12) !important;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.gap-2 {
|
|
265
|
+
gap: 8px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.pagination-select {
|
|
269
|
+
width: 90px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.truncate-label {
|
|
273
|
+
white-space: nowrap;
|
|
274
|
+
min-width: fit-content;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.field-grid {
|
|
278
|
+
display: grid;
|
|
279
|
+
grid-template-columns: max-content 1fr;
|
|
280
|
+
column-gap: 14px;
|
|
281
|
+
row-gap: 8px;
|
|
282
|
+
align-items: start;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.field-label {
|
|
286
|
+
text-align: right;
|
|
287
|
+
white-space: nowrap;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.field-value {
|
|
291
|
+
text-align: left;
|
|
292
|
+
word-break: break-word;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.field-label::after {
|
|
296
|
+
content: ":";
|
|
297
|
+
margin-left: 6px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
</style>
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {PropType} from 'vue'
|
|
3
|
+
import {useAuth} from '@drax/identity-vue'
|
|
4
|
+
import CrudSearch from "./CrudSearch.vue";
|
|
5
|
+
import {useCrud} from "../composables/UseCrud";
|
|
6
|
+
import CrudExportButton from "./buttons/CrudExportButton.vue";
|
|
7
|
+
import CrudImportButton from "./buttons/CrudImportButton.vue";
|
|
8
|
+
import CrudCreateButton from "./buttons/CrudCreateButton.vue";
|
|
9
|
+
import CrudUpdateButton from "./buttons/CrudUpdateButton.vue";
|
|
10
|
+
import CrudDeleteButton from "./buttons/CrudDeleteButton.vue";
|
|
11
|
+
import CrudViewButton from "./buttons/CrudViewButton.vue";
|
|
12
|
+
import CrudGroupByButton from "./buttons/CrudGroupByButton.vue";
|
|
13
|
+
import CrudColumnsButton from "./buttons/CrudColumnsButton.vue";
|
|
14
|
+
import CrudExportList from "./CrudExportList.vue";
|
|
15
|
+
import type {IEntityCrud} from "@drax/crud-share";
|
|
16
|
+
import {useI18n} from "vue-i18n";
|
|
17
|
+
import CrudFilters from "./CrudFilters.vue";
|
|
18
|
+
import { useCrudColumns } from "../composables/UseCrudColumns";
|
|
19
|
+
import CrudFiltersDynamic from "./CrudFiltersDynamic.vue";
|
|
20
|
+
import CrudFiltersAction from "./CrudFiltersAction.vue";
|
|
21
|
+
|
|
22
|
+
const {t, te} = useI18n()
|
|
23
|
+
const {hasPermission} = useAuth()
|
|
24
|
+
|
|
25
|
+
const {entity} = defineProps({
|
|
26
|
+
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
loading, itemsPerPage, page, sortBy, search, totalItems, items,
|
|
31
|
+
doPaginate, filters, applyFilters, clearFilters, paginationError
|
|
32
|
+
} = useCrud(entity)
|
|
33
|
+
|
|
34
|
+
// Usar el composable de columnas
|
|
35
|
+
const { filteredHeaders } = useCrudColumns(entity)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
defineExpose({
|
|
39
|
+
doPaginate
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
defineEmits(['import', 'export', 'create', 'update', 'delete', 'view', 'edit'])
|
|
43
|
+
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<v-data-table-server
|
|
48
|
+
:density="entity.tableDensity"
|
|
49
|
+
:striped="entity.tableStriped"
|
|
50
|
+
:header-props="entity.headerProps"
|
|
51
|
+
v-if="hasPermission(entity.permissions.view)"
|
|
52
|
+
v-model:items-per-page="itemsPerPage"
|
|
53
|
+
:items-per-page-options="[5, 10, 20, 50]"
|
|
54
|
+
v-model:page="page"
|
|
55
|
+
v-model:sort-by="sortBy"
|
|
56
|
+
:headers="filteredHeaders"
|
|
57
|
+
:items="items"
|
|
58
|
+
:items-length="totalItems"
|
|
59
|
+
:loading="loading"
|
|
60
|
+
:search="search"
|
|
61
|
+
:multi-sort="false"
|
|
62
|
+
item-value="name"
|
|
63
|
+
@update:options="doPaginate"
|
|
64
|
+
>
|
|
65
|
+
|
|
66
|
+
<template v-slot:no-data>
|
|
67
|
+
<v-alert
|
|
68
|
+
v-if="paginationError"
|
|
69
|
+
variant="tonal"
|
|
70
|
+
class="w-100 ma-2"
|
|
71
|
+
style="width: 100%; min-width: 100%"
|
|
72
|
+
prominent
|
|
73
|
+
type="error"
|
|
74
|
+
:text="te(paginationError) ? t(paginationError) : paginationError"
|
|
75
|
+
/>
|
|
76
|
+
<v-alert v-else variant="tonal" class="w-100 ma-2 " type="info" :text="te('crud.noData') ? t('crud.noData') : 'No data' " />
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<template v-slot:bottom>
|
|
80
|
+
<v-data-table-footer :class="entity.footerClass"
|
|
81
|
+
:items-per-page-options="[5, 10, 20, 50]"
|
|
82
|
+
></v-data-table-footer>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<template v-slot:top>
|
|
86
|
+
<v-toolbar :class="entity.toolbarClass" :density="entity.toolbarDensity">
|
|
87
|
+
<v-toolbar-title>
|
|
88
|
+
{{ te(`${entity.name.toLowerCase()}.crud`) ? t(`${entity.name.toLowerCase()}.crud`) : entity.name }}
|
|
89
|
+
</v-toolbar-title>
|
|
90
|
+
<v-spacer></v-spacer>
|
|
91
|
+
|
|
92
|
+
<slot name="toolbar">
|
|
93
|
+
|
|
94
|
+
</slot>
|
|
95
|
+
|
|
96
|
+
<crud-import-button
|
|
97
|
+
:entity="entity"
|
|
98
|
+
@import="(v:any) => $emit('import', v)"
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<crud-export-button
|
|
102
|
+
:entity="entity"
|
|
103
|
+
@export="(v:any) => $emit('export',v)"
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<crud-group-by-button
|
|
107
|
+
v-if="entity.isGroupable"
|
|
108
|
+
:entity="entity"
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<crud-columns-button
|
|
112
|
+
v-if="entity.isColumnSelectable"
|
|
113
|
+
:entity="entity"
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
<crud-create-button
|
|
117
|
+
v-if="entity.isCreatable"
|
|
118
|
+
:entity="entity"
|
|
119
|
+
@click="$emit('create')"
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
</v-toolbar>
|
|
123
|
+
|
|
124
|
+
<crud-export-list
|
|
125
|
+
:entity="entity"
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<v-card variant="flat">
|
|
129
|
+
<v-card-text v-if="entity.searchEnable">
|
|
130
|
+
<crud-search
|
|
131
|
+
v-model="search"
|
|
132
|
+
/>
|
|
133
|
+
</v-card-text>
|
|
134
|
+
|
|
135
|
+
<v-card-text class="pt-0">
|
|
136
|
+
<slot name="filters" v-bind="{filters}"></slot>
|
|
137
|
+
|
|
138
|
+
<v-card variant="flat" v-if="!$slots.filters">
|
|
139
|
+
|
|
140
|
+
<crud-filters
|
|
141
|
+
v-if="entity.filtersEnable"
|
|
142
|
+
:entity="entity"
|
|
143
|
+
v-model="filters"
|
|
144
|
+
:auto-filter="!entity.filterButtons"
|
|
145
|
+
@clearFilter="clearFilters()"
|
|
146
|
+
@applyFilter="applyFilters()"
|
|
147
|
+
>
|
|
148
|
+
|
|
149
|
+
<template v-for="iFilter in entity.filters"
|
|
150
|
+
:key="iFilter.name"
|
|
151
|
+
v-slot:[`filter.${iFilter.name}`]="{filter, filterIndex}"
|
|
152
|
+
>
|
|
153
|
+
<slot v-if="$slots[`filter.${iFilter.name}`]"
|
|
154
|
+
:name="`filter.${iFilter.name}`"
|
|
155
|
+
v-bind="{filter, filterIndex}"
|
|
156
|
+
/>
|
|
157
|
+
</template>
|
|
158
|
+
</crud-filters>
|
|
159
|
+
|
|
160
|
+
<crud-filters-dynamic
|
|
161
|
+
v-if="entity.dynamicFiltersEnable"
|
|
162
|
+
:entity="entity"
|
|
163
|
+
v-model="filters"
|
|
164
|
+
:auto-filter="!entity.filterButtons"
|
|
165
|
+
@clearFilter="clearFilters()"
|
|
166
|
+
@applyFilter="applyFilters()"
|
|
167
|
+
>
|
|
168
|
+
</crud-filters-dynamic>
|
|
169
|
+
|
|
170
|
+
<crud-filters-action v-if="entity.filterButtons"
|
|
171
|
+
:entity="entity"
|
|
172
|
+
@clearFilter="clearFilters()"
|
|
173
|
+
@applyFilter="applyFilters()"
|
|
174
|
+
></crud-filters-action>
|
|
175
|
+
</v-card>
|
|
176
|
+
|
|
177
|
+
</v-card-text>
|
|
178
|
+
|
|
179
|
+
</v-card>
|
|
180
|
+
|
|
181
|
+
<v-divider></v-divider>
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
<template v-for="header in entity.headers" :key="header.key" v-slot:[`item.${header.key}`]="{item, value}">
|
|
189
|
+
<slot v-if="$slots[`item.${header.key}`]" :name="`item.${header.key}`" v-bind="{item, value}">
|
|
190
|
+
{{ value }}
|
|
191
|
+
</slot>
|
|
192
|
+
</template>
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
<template v-slot:item.actions="{item}">
|
|
196
|
+
|
|
197
|
+
<slot name="item.actions" v-bind="{item}">
|
|
198
|
+
</slot>
|
|
199
|
+
|
|
200
|
+
<crud-view-button
|
|
201
|
+
v-if="entity.isViewable && hasPermission(entity.permissions.view)"
|
|
202
|
+
@click="$emit('view', item)"
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
<crud-update-button
|
|
206
|
+
v-if="entity.isEditable && entity.isItemEditable(item) && hasPermission(entity.permissions?.update)"
|
|
207
|
+
@click="$emit('edit', item)"
|
|
208
|
+
/>
|
|
209
|
+
|
|
210
|
+
<crud-delete-button
|
|
211
|
+
v-if="entity.isDeletable && hasPermission(entity.permissions?.delete)"
|
|
212
|
+
@click="$emit('delete', item)"
|
|
213
|
+
/>
|
|
214
|
+
|
|
215
|
+
</template>
|
|
216
|
+
|
|
217
|
+
</v-data-table-server>
|
|
218
|
+
</template>
|
|
219
|
+
|
|
220
|
+
<style scoped>
|
|
221
|
+
|
|
222
|
+
</style>
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
IDraxFieldFilter,
|
|
3
|
+
IDraxPaginateResult,
|
|
4
|
+
IEntityCrud,
|
|
5
|
+
IEntityCrudFilter,
|
|
6
|
+
} from "@drax/crud-share";
|
|
2
7
|
import {useCrudStore} from "../stores/UseCrudStore";
|
|
3
8
|
import {computed, nextTick, toRaw} from "vue";
|
|
4
9
|
import getItemId from "../helpers/getItemId";
|
|
5
10
|
import {useI18n} from "vue-i18n";
|
|
11
|
+
import {useRouter} from "vue-router";
|
|
12
|
+
import type {IEntityCrudFilterDynamic} from "@drax/crud-share/types/interfaces/IEntityCrudFilterDynamic";
|
|
6
13
|
|
|
7
14
|
export function useCrud(entity: IEntityCrud) {
|
|
8
15
|
|
|
9
16
|
const store = useCrudStore(entity?.name)
|
|
10
17
|
|
|
18
|
+
const router = useRouter();
|
|
19
|
+
|
|
11
20
|
const {t: $t, te: $te} = useI18n()
|
|
12
21
|
|
|
13
22
|
const exportError = computed({
|
|
@@ -33,6 +42,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
33
42
|
store.setDialog(value)
|
|
34
43
|
}
|
|
35
44
|
})
|
|
45
|
+
|
|
36
46
|
const operation = computed({
|
|
37
47
|
get() {
|
|
38
48
|
return store.operation
|
|
@@ -40,6 +50,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
40
50
|
store.setOperation(value)
|
|
41
51
|
}
|
|
42
52
|
})
|
|
53
|
+
|
|
43
54
|
const form = computed({
|
|
44
55
|
get() {
|
|
45
56
|
return store.form
|
|
@@ -47,6 +58,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
47
58
|
store.setForm(value)
|
|
48
59
|
}
|
|
49
60
|
})
|
|
61
|
+
|
|
50
62
|
const formValid = computed({
|
|
51
63
|
get() {
|
|
52
64
|
return store.formValid
|
|
@@ -54,6 +66,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
54
66
|
store.setFormValid(value)
|
|
55
67
|
}
|
|
56
68
|
})
|
|
69
|
+
|
|
57
70
|
const notify = computed({
|
|
58
71
|
get() {
|
|
59
72
|
return store.notify
|
|
@@ -61,6 +74,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
61
74
|
store.setNotify(value)
|
|
62
75
|
}
|
|
63
76
|
})
|
|
77
|
+
|
|
64
78
|
const error = computed({
|
|
65
79
|
get() {
|
|
66
80
|
return store.error
|
|
@@ -68,6 +82,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
68
82
|
store.setError(value)
|
|
69
83
|
}
|
|
70
84
|
})
|
|
85
|
+
|
|
71
86
|
const message = computed({
|
|
72
87
|
get() {
|
|
73
88
|
return store.message
|
|
@@ -75,6 +90,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
75
90
|
store.setMessage(value)
|
|
76
91
|
}
|
|
77
92
|
})
|
|
93
|
+
|
|
78
94
|
const loading = computed({
|
|
79
95
|
get() {
|
|
80
96
|
return store.loading
|
|
@@ -82,6 +98,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
82
98
|
store.setLoading(value)
|
|
83
99
|
}
|
|
84
100
|
})
|
|
101
|
+
|
|
85
102
|
const itemsPerPage = computed({
|
|
86
103
|
get() {
|
|
87
104
|
return store.itemsPerPage
|
|
@@ -89,6 +106,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
89
106
|
store.setItemsPerPage(value)
|
|
90
107
|
}
|
|
91
108
|
})
|
|
109
|
+
|
|
92
110
|
const page = computed({
|
|
93
111
|
get() {
|
|
94
112
|
return store.page
|
|
@@ -96,6 +114,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
96
114
|
store.setPage(value)
|
|
97
115
|
}
|
|
98
116
|
})
|
|
117
|
+
|
|
99
118
|
const sortBy = computed({
|
|
100
119
|
get() {
|
|
101
120
|
return store.sortBy
|
|
@@ -103,6 +122,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
103
122
|
store.setSortBy(value)
|
|
104
123
|
}
|
|
105
124
|
})
|
|
125
|
+
|
|
106
126
|
const search = computed({
|
|
107
127
|
get() {
|
|
108
128
|
return store.search
|
|
@@ -110,6 +130,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
110
130
|
store.setSearch(value)
|
|
111
131
|
}
|
|
112
132
|
})
|
|
133
|
+
|
|
113
134
|
const totalItems = computed({
|
|
114
135
|
get() {
|
|
115
136
|
return store.totalItems
|
|
@@ -117,6 +138,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
117
138
|
store.setTotalItems(value)
|
|
118
139
|
}
|
|
119
140
|
})
|
|
141
|
+
|
|
120
142
|
const items = computed({
|
|
121
143
|
get() {
|
|
122
144
|
return store.items
|
|
@@ -124,6 +146,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
124
146
|
store.setItems(value)
|
|
125
147
|
}
|
|
126
148
|
})
|
|
149
|
+
|
|
127
150
|
const exportFiles = computed({
|
|
128
151
|
get() {
|
|
129
152
|
return store.exportFiles
|
|
@@ -131,6 +154,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
131
154
|
store.setExportFiles(value)
|
|
132
155
|
}
|
|
133
156
|
})
|
|
157
|
+
|
|
134
158
|
const exportLoading = computed({
|
|
135
159
|
get() {
|
|
136
160
|
return store.exportLoading
|
|
@@ -138,6 +162,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
138
162
|
store.setExportLoading(value)
|
|
139
163
|
}
|
|
140
164
|
})
|
|
165
|
+
|
|
141
166
|
const exportListVisible = computed({
|
|
142
167
|
get() {
|
|
143
168
|
return store.exportListVisible
|
|
@@ -145,6 +170,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
145
170
|
store.setExportListVisible(value)
|
|
146
171
|
}
|
|
147
172
|
})
|
|
173
|
+
|
|
148
174
|
const filters = computed({
|
|
149
175
|
get() {
|
|
150
176
|
return store.filters
|
|
@@ -153,6 +179,24 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
153
179
|
}
|
|
154
180
|
})
|
|
155
181
|
|
|
182
|
+
const prepareDynamicFilters = computed(() => {
|
|
183
|
+
return store.dynamicFilters.map((filter: IEntityCrudFilterDynamic) => {
|
|
184
|
+
return {
|
|
185
|
+
field: filter.name,
|
|
186
|
+
operator: filter.operator,
|
|
187
|
+
value: filter.value
|
|
188
|
+
}
|
|
189
|
+
}) as IDraxFieldFilter[]
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
const getAllFilters = computed(() =>{
|
|
194
|
+
return [
|
|
195
|
+
...store.filters,
|
|
196
|
+
...prepareDynamicFilters.value
|
|
197
|
+
] as IDraxFieldFilter[]
|
|
198
|
+
})
|
|
199
|
+
|
|
156
200
|
|
|
157
201
|
async function doPaginate() {
|
|
158
202
|
store.setLoading(true)
|
|
@@ -165,7 +209,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
165
209
|
orderBy: store.sortBy[0]?.key,
|
|
166
210
|
order: store.sortBy[0]?.order,
|
|
167
211
|
search: store.search,
|
|
168
|
-
filters:
|
|
212
|
+
filters: getAllFilters.value
|
|
169
213
|
})
|
|
170
214
|
store.setItems(r.items)
|
|
171
215
|
store.setTotalItems(r.total)
|
|
@@ -204,7 +248,7 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
204
248
|
orderBy: store.sortBy[0]?.key,
|
|
205
249
|
order: store.sortBy[0]?.order,
|
|
206
250
|
search: store.search,
|
|
207
|
-
filters:
|
|
251
|
+
filters: getAllFilters.value
|
|
208
252
|
})
|
|
209
253
|
|
|
210
254
|
if (r && r.url) {
|
|
@@ -325,6 +369,9 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
325
369
|
await doPaginate()
|
|
326
370
|
closeDialog()
|
|
327
371
|
store.showMessage("Entity created successfully!")
|
|
372
|
+
if(entity.redirectOnCreate){
|
|
373
|
+
router.push(entity.redirectOnCreate(item))
|
|
374
|
+
}
|
|
328
375
|
return {status: 'created', item: item}
|
|
329
376
|
}
|
|
330
377
|
throw new Error("provider.create not implemented")
|
|
@@ -395,7 +442,17 @@ export function useCrud(entity: IEntityCrud) {
|
|
|
395
442
|
}
|
|
396
443
|
|
|
397
444
|
function prepareFilters() {
|
|
398
|
-
|
|
445
|
+
|
|
446
|
+
const staticFilters : IDraxFieldFilter[] = entity.filters.map(
|
|
447
|
+
(filter: IEntityCrudFilter) =>
|
|
448
|
+
({
|
|
449
|
+
field: filter.name,
|
|
450
|
+
value: filter.default ? filter.default : null,
|
|
451
|
+
operator: (filter.operator ? filter.operator : 'eq')
|
|
452
|
+
})
|
|
453
|
+
) as IDraxFieldFilter[]
|
|
454
|
+
|
|
455
|
+
store.setFilters(staticFilters)
|
|
399
456
|
}
|
|
400
457
|
|
|
401
458
|
async function clearFilters() {
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import EntityCombobox from "./components/combobox/EntityCombobox.vue";
|
|
|
12
12
|
import {useCrudStore} from "./stores/UseCrudStore";
|
|
13
13
|
import {useEntityStore} from "./stores/UseEntityStore";
|
|
14
14
|
import {useCrud} from "./composables/UseCrud";
|
|
15
|
+
import {useFilterIcon} from "./composables/UseFilterIcon";
|
|
15
16
|
import {useFormUtils} from "./composables/UseFormUtils";
|
|
16
17
|
import {useInputErrorI18n} from "./composables/UseInputErrorI18n";
|
|
17
18
|
import {EntityCrud} from "./EntityCrud";
|
|
@@ -32,6 +33,7 @@ export {
|
|
|
32
33
|
useFormUtils,
|
|
33
34
|
useCrudStore,
|
|
34
35
|
useInputErrorI18n,
|
|
36
|
+
useFilterIcon,
|
|
35
37
|
EntityCrud,
|
|
36
38
|
useEntityStore,
|
|
37
39
|
EntityCombobox
|
|
@@ -13,6 +13,7 @@ export const useCrudStore = (id: string = 'entity') => defineStore('CrudStore'+i
|
|
|
13
13
|
error: '' as string,
|
|
14
14
|
paginationError: '' as string,
|
|
15
15
|
filters: [] as IDraxFieldFilter[],
|
|
16
|
+
dynamicFilters: [] as IDraxFieldFilter[],
|
|
16
17
|
items: [] as any[],
|
|
17
18
|
totalItems: 0 as number,
|
|
18
19
|
itemsPerPage: 10 as number,
|
|
@@ -134,6 +135,21 @@ export const useCrudStore = (id: string = 'entity') => defineStore('CrudStore'+i
|
|
|
134
135
|
this.filters[index].value = value
|
|
135
136
|
}
|
|
136
137
|
},
|
|
138
|
+
addDynamicFilter(filter: IDraxFieldFilter) {
|
|
139
|
+
this.dynamicFilters.push(filter)
|
|
140
|
+
},
|
|
141
|
+
removeDynamicFilter(index: number) {
|
|
142
|
+
this.dynamicFilters.splice(index, 1)
|
|
143
|
+
},
|
|
144
|
+
setDynamicFilters(filters: IDraxFieldFilter[]) {
|
|
145
|
+
this.dynamicFilters = filters
|
|
146
|
+
},
|
|
147
|
+
setDynamicFilterValue(name:string, value:any) {
|
|
148
|
+
const index = this.getFilterIndex(name)
|
|
149
|
+
if (index >= 0) {
|
|
150
|
+
this.dynamicFilters[index].value = value
|
|
151
|
+
}
|
|
152
|
+
},
|
|
137
153
|
setVisibleColumns(columns: string[]) {
|
|
138
154
|
this.visibleColumns = columns
|
|
139
155
|
},
|
|
File without changes
|