@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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.30.0",
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.0"
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": "4b41867751444a91332910fd154fa7510b09f02e"
53
+ "gitHead": "90e6f71d45f95f14df6b8334920f9af82a1c99a2"
54
54
  }
@@ -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
- <v-container :fluid="entity.containerFluid" class="mt-5">
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 submit() {
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,