@drax/crud-vue 3.30.0 → 3.31.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 +3 -3
- package/src/components/Crud.vue +93 -1
- package/src/components/CrudForm.vue +27 -3
- package/src/components/CrudRouteForm.vue +188 -0
- package/src/index.ts +2 -6
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.31.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@drax/common-front": "^3.29.0",
|
|
28
28
|
"@drax/crud-front": "^3.21.0",
|
|
29
29
|
"@drax/crud-share": "^3.29.0",
|
|
30
|
-
"@drax/media-vue": "^3.
|
|
30
|
+
"@drax/media-vue": "^3.31.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"pinia": "^3.0.4",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"vue-tsc": "^3.2.4",
|
|
51
51
|
"vuetify": "^3.11.8"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "90e6f71d45f95f14df6b8334920f9af82a1c99a2"
|
|
54
54
|
}
|
package/src/components/Crud.vue
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {computed, onBeforeMount, ref, watch, type PropType} from "vue";
|
|
3
|
+
import {useRoute, useRouter} from "vue-router";
|
|
3
4
|
import type {IEntityCrud} from "@drax/crud-share";
|
|
4
5
|
import CrudListTable from "./CrudListTable.vue";
|
|
5
6
|
import CrudListGallery from "./CrudListGallery.vue";
|
|
6
7
|
import CrudForm from "./CrudForm.vue";
|
|
8
|
+
import CrudRouteForm from "./CrudRouteForm.vue";
|
|
7
9
|
import CrudNotify from "./CrudNotify.vue";
|
|
8
10
|
import CrudDialog from "./CrudDialog.vue";
|
|
9
11
|
import CrudAiButton from "./buttons/CrudAiButton.vue";
|
|
@@ -12,6 +14,7 @@ import {useCrud} from "../composables/UseCrud";
|
|
|
12
14
|
import {useDisplay} from 'vuetify'
|
|
13
15
|
import {useAuth} from "@drax/identity-vue";
|
|
14
16
|
import CrudRowValue from "./CrudRowValue.vue";
|
|
17
|
+
import getItemId from "../helpers/getItemId";
|
|
15
18
|
|
|
16
19
|
const {entity} = defineProps({
|
|
17
20
|
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
@@ -25,6 +28,8 @@ const {
|
|
|
25
28
|
} = useCrud(entity);
|
|
26
29
|
|
|
27
30
|
const {hasPermission} = useAuth()
|
|
31
|
+
const route = useRoute()
|
|
32
|
+
const router = useRouter()
|
|
28
33
|
|
|
29
34
|
onBeforeMount(() => {
|
|
30
35
|
resetCrudStore()
|
|
@@ -51,10 +56,75 @@ const listComponent = computed(() => {
|
|
|
51
56
|
|
|
52
57
|
const {xs} = useDisplay()
|
|
53
58
|
|
|
59
|
+
const routeCrudMode = computed(() => {
|
|
60
|
+
const mode = getQueryParam('mode')
|
|
61
|
+
return ['create', 'edit', 'view'].includes(mode || '') ? mode : null
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const routeCrudId = computed(() => getQueryParam('id'))
|
|
65
|
+
|
|
66
|
+
const isRouteCrudForm = computed(() => {
|
|
67
|
+
if (routeCrudMode.value === 'create') {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return ['edit', 'view'].includes(routeCrudMode.value || '') && !!routeCrudId.value
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const canOpenRouteCrudForm = computed(() => {
|
|
75
|
+
if (operation.value === 'create') {
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return ['edit', 'view'].includes(operation.value || '') && !!getFormId()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
function getQueryParam(name: string): string | null {
|
|
83
|
+
const value = route.query[name]
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
return value[0] ?? null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return value ?? null
|
|
90
|
+
}
|
|
91
|
+
|
|
54
92
|
function applyAiSuggestions(values: Record<string, any>) {
|
|
55
93
|
form.value = values
|
|
56
94
|
}
|
|
57
95
|
|
|
96
|
+
function getFormId() {
|
|
97
|
+
return entity.identifier ? form.value?.[entity.identifier] : getItemId(form.value)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function openRouteCrudForm() {
|
|
101
|
+
if (!operation.value || !['create', 'edit', 'view'].includes(operation.value)) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const query: Record<string, string> = {
|
|
106
|
+
mode: operation.value,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (operation.value !== 'create') {
|
|
110
|
+
const id = getFormId()
|
|
111
|
+
|
|
112
|
+
if (!id) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
query.id = String(id)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
router.push({
|
|
120
|
+
name: route.name ?? undefined,
|
|
121
|
+
path: route.name ? undefined : route.path,
|
|
122
|
+
params: route.params,
|
|
123
|
+
hash: route.hash,
|
|
124
|
+
query,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
58
128
|
watch(dialog, (value) => {
|
|
59
129
|
if (!value) {
|
|
60
130
|
aiExpanded.value = false
|
|
@@ -65,7 +135,22 @@ watch(dialog, (value) => {
|
|
|
65
135
|
</script>
|
|
66
136
|
|
|
67
137
|
<template>
|
|
68
|
-
<
|
|
138
|
+
<crud-route-form
|
|
139
|
+
v-if="isRouteCrudForm"
|
|
140
|
+
:entity="entity"
|
|
141
|
+
@created="item => emit('created', item)"
|
|
142
|
+
@updated="item => emit('updated', item)"
|
|
143
|
+
@deleted="emit('deleted')"
|
|
144
|
+
@viewed="emit('viewed')"
|
|
145
|
+
@canceled="emit('canceled')"
|
|
146
|
+
>
|
|
147
|
+
<template v-for="ifield in entity.fields" :key="ifield.name" v-slot:[`field.${ifield.name}`]="{field, form, modelValue, setValue}">
|
|
148
|
+
<slot :name="`field.${ifield.name}`" v-bind="{field, form, modelValue, setValue}">
|
|
149
|
+
</slot>
|
|
150
|
+
</template>
|
|
151
|
+
</crud-route-form>
|
|
152
|
+
|
|
153
|
+
<v-container v-else :fluid="entity.containerFluid" class="mt-5">
|
|
69
154
|
<v-card :class="entity.cardClass" :density="entity.cardDensity">
|
|
70
155
|
|
|
71
156
|
<component
|
|
@@ -149,6 +234,13 @@ watch(dialog, (value) => {
|
|
|
149
234
|
:operation="operation"
|
|
150
235
|
>
|
|
151
236
|
<template #toolbar-actions>
|
|
237
|
+
<v-btn
|
|
238
|
+
v-if="canOpenRouteCrudForm"
|
|
239
|
+
icon="mdi-open-in-new"
|
|
240
|
+
variant="text"
|
|
241
|
+
@click="openRouteCrudForm"
|
|
242
|
+
/>
|
|
243
|
+
|
|
152
244
|
<v-btn
|
|
153
245
|
v-if="canNavigateItems"
|
|
154
246
|
icon="mdi-chevron-left"
|
|
@@ -14,13 +14,14 @@ const {hasPermission} = useAuth()
|
|
|
14
14
|
const {t, te} = useI18n()
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
const {entity} = defineProps({
|
|
17
|
+
const {entity, showSubmitAndReturn} = defineProps({
|
|
18
18
|
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
19
|
+
showSubmitAndReturn: {type: Boolean, default: false},
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
const {onSubmit, onCancel, operation, error, form} = useCrud(entity)
|
|
22
23
|
|
|
23
|
-
const emit = defineEmits(['created', 'updated', 'deleted', 'viewed', 'canceled'])
|
|
24
|
+
const emit = defineEmits(['created', 'updated', 'deleted', 'viewed', 'canceled', 'saved-and-return'])
|
|
24
25
|
|
|
25
26
|
const store = useCrudStore(entity?.name)
|
|
26
27
|
|
|
@@ -71,7 +72,7 @@ function setFieldModelValue(fieldName: string, value: any) {
|
|
|
71
72
|
form.value[fieldName] = value
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
async function
|
|
75
|
+
async function submitForm(returnAfterSubmit = false) {
|
|
75
76
|
store.resetErrors()
|
|
76
77
|
|
|
77
78
|
if (['create', 'edit'].includes(operation.value)) {
|
|
@@ -87,9 +88,15 @@ async function submit() {
|
|
|
87
88
|
switch (result.status) {
|
|
88
89
|
case "created":
|
|
89
90
|
emit("created", result.item)
|
|
91
|
+
if (returnAfterSubmit) {
|
|
92
|
+
emit("saved-and-return", result.item)
|
|
93
|
+
}
|
|
90
94
|
break
|
|
91
95
|
case "updated":
|
|
92
96
|
emit("updated", result.item)
|
|
97
|
+
if (returnAfterSubmit) {
|
|
98
|
+
emit("saved-and-return", result.item)
|
|
99
|
+
}
|
|
93
100
|
break
|
|
94
101
|
case "deleted":
|
|
95
102
|
emit("deleted")
|
|
@@ -101,6 +108,14 @@ async function submit() {
|
|
|
101
108
|
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
async function submit() {
|
|
112
|
+
await submitForm()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function submitAndReturn() {
|
|
116
|
+
await submitForm(true)
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
function cancel() {
|
|
105
120
|
onCancel()
|
|
106
121
|
emit('canceled')
|
|
@@ -349,6 +364,15 @@ const onlyView = computed(()=> {
|
|
|
349
364
|
<v-btn variant="flat" v-if="operation != 'view'" :class="entity.submitBtnFormClass" @click="submit" :loading="store.loading">
|
|
350
365
|
{{ operation ? t('action.'+operation) : t('action.sent') }}
|
|
351
366
|
</v-btn>
|
|
367
|
+
<v-btn
|
|
368
|
+
variant="flat"
|
|
369
|
+
v-if="showSubmitAndReturn && ['create', 'edit'].includes(operation || '')"
|
|
370
|
+
:class="entity.submitBtnFormClass"
|
|
371
|
+
@click="submitAndReturn"
|
|
372
|
+
:loading="store.loading"
|
|
373
|
+
>
|
|
374
|
+
{{ te('action.saveAndReturn') ? t('action.saveAndReturn') : 'Guardar y volver' }}
|
|
375
|
+
</v-btn>
|
|
352
376
|
</v-card-actions>
|
|
353
377
|
</v-card>
|
|
354
378
|
</v-form>
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, onBeforeMount, watch, type PropType} from "vue";
|
|
3
|
+
import {useRoute, useRouter} from "vue-router";
|
|
4
|
+
import type {IEntityCrud} from "@drax/crud-share";
|
|
5
|
+
import CrudForm from "./CrudForm.vue";
|
|
6
|
+
import CrudNotify from "./CrudNotify.vue";
|
|
7
|
+
import {useCrud} from "../composables/UseCrud";
|
|
8
|
+
import {useCrudStore} from "../stores/UseCrudStore";
|
|
9
|
+
import getItemId from "../helpers/getItemId";
|
|
10
|
+
|
|
11
|
+
type RouteCrudMode = 'create' | 'edit' | 'view'
|
|
12
|
+
|
|
13
|
+
const {entity} = defineProps({
|
|
14
|
+
entity: {type: Object as PropType<IEntityCrud>, required: true},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits(['created', 'updated', 'deleted', 'viewed', 'canceled'])
|
|
18
|
+
|
|
19
|
+
const route = useRoute()
|
|
20
|
+
const router = useRouter()
|
|
21
|
+
const store = useCrudStore(entity?.name)
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
resetCrudStore,
|
|
25
|
+
prepareFilters,
|
|
26
|
+
prepareSort,
|
|
27
|
+
operation,
|
|
28
|
+
form,
|
|
29
|
+
notify,
|
|
30
|
+
message,
|
|
31
|
+
setForm,
|
|
32
|
+
setOperation,
|
|
33
|
+
cast,
|
|
34
|
+
cloneItem,
|
|
35
|
+
} = useCrud(entity)
|
|
36
|
+
|
|
37
|
+
const routeMode = computed<RouteCrudMode | null>(() => {
|
|
38
|
+
const mode = getQueryParam('mode')
|
|
39
|
+
return isRouteCrudMode(mode) ? mode : null
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const routeId = computed(() => getQueryParam('id'))
|
|
43
|
+
|
|
44
|
+
const formReady = computed(() => {
|
|
45
|
+
return operation.value === 'create' || Boolean(form.value && Object.keys(form.value).length > 0)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
onBeforeMount(() => {
|
|
49
|
+
resetCrudStore()
|
|
50
|
+
prepareFilters()
|
|
51
|
+
prepareSort()
|
|
52
|
+
prepareRouteForm()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
watch(
|
|
56
|
+
() => [route.query.mode, route.query.id],
|
|
57
|
+
() => prepareRouteForm()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
function getQueryParam(name: string): string | null {
|
|
61
|
+
const value = route.query[name]
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return value[0] ?? null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return value ?? null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isRouteCrudMode(mode: string | null): mode is RouteCrudMode {
|
|
71
|
+
return mode === 'create' || mode === 'edit' || mode === 'view'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cancel() {
|
|
75
|
+
emit('canceled')
|
|
76
|
+
goToList()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function goToList() {
|
|
80
|
+
router.push({
|
|
81
|
+
name: route.name ?? undefined,
|
|
82
|
+
path: route.name ? undefined : route.path,
|
|
83
|
+
params: route.params,
|
|
84
|
+
hash: route.hash,
|
|
85
|
+
query: {},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getCreatedItemId(item: any) {
|
|
90
|
+
return entity.identifier ? item?.[entity.identifier] : getItemId(item)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function goToEdit(item: any) {
|
|
94
|
+
const id = getCreatedItemId(item)
|
|
95
|
+
|
|
96
|
+
if (!id) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
router.push({
|
|
101
|
+
name: route.name ?? undefined,
|
|
102
|
+
path: route.name ? undefined : route.path,
|
|
103
|
+
params: route.params,
|
|
104
|
+
hash: route.hash,
|
|
105
|
+
query: {
|
|
106
|
+
mode: 'edit',
|
|
107
|
+
id: String(id),
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function created(item: any) {
|
|
113
|
+
emit('created', item)
|
|
114
|
+
goToEdit(item)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function prepareRouteForm() {
|
|
118
|
+
const mode = routeMode.value
|
|
119
|
+
|
|
120
|
+
if (!mode) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
store.resetErrors()
|
|
125
|
+
|
|
126
|
+
if (mode === 'create') {
|
|
127
|
+
setOperation('create')
|
|
128
|
+
setForm(entity.form)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!routeId.value) {
|
|
133
|
+
store.setError('Missing id query param')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
store.setLoading(true)
|
|
139
|
+
|
|
140
|
+
if (!entity.provider.findById) {
|
|
141
|
+
throw new Error('provider.findById not implemented')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const item = await entity.provider.findById(routeId.value)
|
|
145
|
+
|
|
146
|
+
if (!item) {
|
|
147
|
+
throw new Error('Item not found')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setOperation(mode)
|
|
151
|
+
setForm(cast(cloneItem(item)))
|
|
152
|
+
} catch (e: any) {
|
|
153
|
+
setOperation(mode)
|
|
154
|
+
setForm({})
|
|
155
|
+
store.setError(e.message || 'An error occurred while loading the entity')
|
|
156
|
+
console.error('Error loading entity by id', e)
|
|
157
|
+
} finally {
|
|
158
|
+
store.setLoading(false)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
<template>
|
|
164
|
+
<v-container :fluid="entity.containerFluid" class="mt-5">
|
|
165
|
+
<v-card :class="entity.cardClass" :density="entity.cardDensity">
|
|
166
|
+
<v-progress-linear v-if="store.loading" indeterminate/>
|
|
167
|
+
|
|
168
|
+
<crud-form
|
|
169
|
+
v-if="formReady || store.error"
|
|
170
|
+
:entity="entity"
|
|
171
|
+
show-submit-and-return
|
|
172
|
+
@created="created"
|
|
173
|
+
@updated="item => emit('updated', item)"
|
|
174
|
+
@deleted="emit('deleted')"
|
|
175
|
+
@viewed="emit('viewed')"
|
|
176
|
+
@canceled="cancel"
|
|
177
|
+
@saved-and-return="goToList"
|
|
178
|
+
>
|
|
179
|
+
<template v-for="ifield in entity.fields" :key="ifield.name" v-slot:[`field.${ifield.name}`]="{field, form, modelValue, setValue}">
|
|
180
|
+
<slot :name="`field.${ifield.name}`" v-bind="{field, form, modelValue, setValue}">
|
|
181
|
+
</slot>
|
|
182
|
+
</template>
|
|
183
|
+
</crud-form>
|
|
184
|
+
</v-card>
|
|
185
|
+
|
|
186
|
+
<crud-notify v-model="notify" :message="message"></crud-notify>
|
|
187
|
+
</v-container>
|
|
188
|
+
</template>
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Crud from "./components/Crud.vue";
|
|
2
2
|
import CrudDialog from "./components/CrudDialog.vue";
|
|
3
3
|
import CrudForm from "./components/CrudForm.vue";
|
|
4
|
+
import CrudRouteForm from "./components/CrudRouteForm.vue";
|
|
4
5
|
import CrudFormField from "./components/CrudFormField.vue";
|
|
5
6
|
import CrudFieldRange from "./components/CrudFieldRange.vue";
|
|
6
7
|
import CrudFormList from "./components/CrudFormList.vue";
|
|
@@ -20,10 +21,7 @@ import EntityCombobox from "./components/combobox/EntityCombobox.vue";
|
|
|
20
21
|
import {useCrudStore} from "./stores/UseCrudStore";
|
|
21
22
|
import {useEntityStore} from "./stores/UseEntityStore";
|
|
22
23
|
import {useCrud} from "./composables/UseCrud";
|
|
23
|
-
import {useCrudColumns} from "./composables/UseCrudColumns";
|
|
24
|
-
import {useCrudGroupBy} from "./composables/UseCrudGroupBy";
|
|
25
24
|
import {useDynamicFilters} from "./composables/UseDynamicFilters";
|
|
26
|
-
import {useCrudRefDisplay} from "./composables/useCrudRefDisplay";
|
|
27
25
|
import {useFilterIcon} from "./composables/UseFilterIcon";
|
|
28
26
|
import {useFormUtils} from "./composables/UseFormUtils";
|
|
29
27
|
import {useInputErrorI18n} from "./composables/UseInputErrorI18n";
|
|
@@ -34,6 +32,7 @@ export {
|
|
|
34
32
|
Crud,
|
|
35
33
|
CrudDialog,
|
|
36
34
|
CrudForm,
|
|
35
|
+
CrudRouteForm,
|
|
37
36
|
CrudFormField,
|
|
38
37
|
CrudFieldRange,
|
|
39
38
|
CrudFormList,
|
|
@@ -50,10 +49,7 @@ export {
|
|
|
50
49
|
CrudFiltersAction,
|
|
51
50
|
CrudFiltersDynamic,
|
|
52
51
|
useCrud,
|
|
53
|
-
useCrudColumns,
|
|
54
|
-
useCrudGroupBy,
|
|
55
52
|
useDynamicFilters,
|
|
56
|
-
useCrudRefDisplay,
|
|
57
53
|
useFormUtils,
|
|
58
54
|
useCrudStore,
|
|
59
55
|
useInputErrorI18n,
|