@drax/crud-vue 0.5.2 → 0.5.4

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": "0.5.2",
6
+ "version": "0.5.4",
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": "^0.5.1",
28
- "@drax/crud-front": "^0.5.1",
29
- "@drax/crud-share": "^0.5.2"
27
+ "@drax/common-front": "^0.5.4",
28
+ "@drax/crud-front": "^0.5.3",
29
+ "@drax/crud-share": "^0.5.4"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "pinia": "^2.2.2",
@@ -63,5 +63,5 @@
63
63
  "vue-tsc": "^2.0.11",
64
64
  "vuetify": "^3.7.1"
65
65
  },
66
- "gitHead": "6f854dbeb2af7bca32ed35bcec2f8e48790a73b9"
66
+ "gitHead": "6db0bebb2df9edecd2c3caa7288fb5c7001c4243"
67
67
  }
package/src/EntityCrud.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  IEntityCrud, IEntityCrudForm, IEntityCrudHeader, IEntityCrudRefs,
3
3
  IEntityCrudRules, IEntityCrudField, IEntityCrudPermissions,
4
- IDraxCrudProvider
4
+ IDraxCrudProvider, IEntityCrudFilter, IEntityCrudFormFilter
5
5
  } from "@drax/crud-share";
6
6
 
7
7
 
@@ -36,7 +36,21 @@ class EntityCrud implements IEntityCrud{
36
36
 
37
37
  get fields():IEntityCrudField[]{
38
38
  return [
39
- {name: 'id', type: 'string', label: 'ID', default: '' },
39
+ {name: '_id', type: 'string', label: 'ID', default: '' },
40
+ ]
41
+ }
42
+
43
+ get createFields(){
44
+ return this.fields
45
+ }
46
+
47
+ get updateFields(){
48
+ return this.fields
49
+ }
50
+
51
+ get filters():IEntityCrudFilter[]{
52
+ return [
53
+ {name: '_id', type: 'string', label: 'ID', default: '', operator: 'eq' },
40
54
  ]
41
55
  }
42
56
 
@@ -57,7 +71,7 @@ class EntityCrud implements IEntityCrud{
57
71
 
58
72
  get form():IEntityCrudForm{
59
73
 
60
- const form = this.fields.reduce((acc, field) => {
74
+ return this.fields.reduce((acc, field) => {
61
75
  let value = null
62
76
  if(field.type === 'object'){
63
77
  value = this.objectFields(field)
@@ -68,10 +82,14 @@ class EntityCrud implements IEntityCrud{
68
82
  return {...acc, [field.name]: value }
69
83
  }, {})
70
84
 
71
- //console.log("Form: ", form)
85
+ }
72
86
 
73
- return form
74
87
 
88
+ get formFilters():IEntityCrudFormFilter[]{
89
+ return this.filters.map(
90
+ (filter:IEntityCrudFilter) =>
91
+ ({field:filter.name, value: null, operator: filter.operator })
92
+ )
75
93
  }
76
94
 
77
95
  get refs():IEntityCrudRefs{
@@ -91,10 +109,13 @@ class EntityCrud implements IEntityCrud{
91
109
  }
92
110
 
93
111
  getRule(field:string|undefined):Array<Function>|undefined {
94
- console.log("Getting rule for field: ", field, this.rules)
95
112
  return field && this.rules[field] && this.rules[field].length > 0 ? this.rules[field] : undefined
96
113
  }
97
114
 
115
+ get isViewable(){
116
+ return true
117
+ }
118
+
98
119
  get isEditable(){
99
120
  return true
100
121
  }
@@ -12,12 +12,14 @@ const {entity} = defineProps({
12
12
  })
13
13
 
14
14
  const {
15
- onCreate, onEdit, onDelete, onCancel, onSubmit,resetCrudStore,
16
- operation, dialog, form, notify, error, message, doExport
15
+ onView, onCreate, onEdit, onDelete, onCancel, onSubmit,resetCrudStore,
16
+ operation, dialog, form, notify, error, message, doExport,
17
+ prepareFilters
17
18
  } = useCrud(entity);
18
19
 
19
20
  onBeforeMount(() => {
20
21
  resetCrudStore()
22
+ prepareFilters()
21
23
  })
22
24
 
23
25
  </script>
@@ -32,6 +34,7 @@ onBeforeMount(() => {
32
34
  @edit="onEdit"
33
35
  @delete="onDelete"
34
36
  @export="doExport"
37
+ @view="onView"
35
38
  >
36
39
  <template v-for="header in entity.headers" :key="header.key" v-slot:[`item.${header.key}`]="{item, value}">
37
40
  <slot :name="`item.${header.key}`" v-bind="{item, value}">
@@ -9,15 +9,24 @@ const valueModel = defineModel<string | string[]>({type: [String, Array], requir
9
9
  const {entity, multiple} = defineProps({
10
10
  entity: {type: Object as PropType<IEntityCrud|undefined>, required: true},
11
11
  field: {type: Object as PropType<IEntityCrudField>, required: true},
12
+ prependIcon: {type: String},
13
+ prependInnerIcon: {type: String},
14
+ appendIcon: {type: String},
15
+ appendInnerIcon: {type: String},
12
16
  multiple: {type: Boolean, default: false},
13
17
  chips: {type: Boolean, default: false},
14
18
  closableChips: {type: Boolean, default: true},
19
+ readonly: {type: Boolean, default: false},
15
20
  clearable: {type: Boolean, default: true},
16
21
  label: {type: String},
17
22
  itemValue: {type: [String], default: '_id'},
18
23
  itemTitle: {type: [String], default: 'name'},
19
24
  rules: {type: Array as PropType<any>, default: []},
20
25
  errorMessages: {type: Array as PropType<string[]>, default: []},
26
+ hideDetails: {type: Boolean, default: false},
27
+ singleLine: {type: Boolean, default: false},
28
+ density: {type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default'},
29
+ variant: {type: String as PropType<'underlined' | 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'plain'>, default: 'filled'},
21
30
  })
22
31
 
23
32
  if(!entity){
@@ -29,63 +38,61 @@ const items: Ref<Array<any>> = ref([])
29
38
 
30
39
  const debouncedSearch = debounce(search, 300)
31
40
 
32
- onBeforeMount(async () => {
33
- if(valueModel.value && valueModel.value.length > 0){
34
-
35
- if(multiple && Array.isArray(valueModel.value) ){
36
- items.value = valueModel.value
37
-
38
- // valueModel.value = valueModel.value.map((item:any) => item._id)
39
- await findByIds(valueModel.value)
40
- }else if(!Array.isArray(valueModel.value)){
41
- // items.value = [valueModel.value]
42
- await findByIds([valueModel.value])
43
- }
44
-
45
-
46
-
47
- }
48
- })
49
-
50
- async function findByIds(ids: Array<string> = []) {
41
+ async function search(value: any) {
51
42
  try{
43
+ loading.value = true
52
44
  if(!entity){
53
45
  throw new Error('Entity is required')
54
46
  }
55
- if(!entity.provider){
56
- throw new Error('Provider is not defined')
57
- }
58
- if (typeof entity.provider.findByIds !== 'function') {
59
- throw new Error('Provider does not have a findByIds method');
47
+ if(!entity.provider.search){
48
+ throw new Error('Provider does not have a search method')
60
49
  }
61
- loading.value = true
62
- items.value = await entity.provider.findByIds(ids)
50
+ items.value = await entity.provider.search(value)
63
51
  }catch (e){
64
52
  console.error(e)
65
53
  }finally{
66
54
  loading.value = false
67
55
  }
56
+
68
57
  }
69
58
 
59
+ onBeforeMount(async () => {
70
60
 
71
- async function search(value: any) {
61
+ await search('')
62
+ await checkIds()
63
+ })
64
+
65
+ async function checkIds(ids: Array<string> = []) {
72
66
  try{
73
- loading.value = true
74
- if(!entity){
75
- throw new Error('Entity is required')
76
- }
77
- if(!entity.provider.search){
78
- throw new Error('Provider does not have a search method')
67
+
68
+ if(valueModel.value) {
69
+ let ids = Array.isArray(valueModel.value) ? valueModel.value : [valueModel.value]
70
+ for (let id of ids) {
71
+ if (!items.value.some((item: any) => item._id === id)) {
72
+ if (!entity) {
73
+ throw new Error('CrudAutocomplete Entity is required')
74
+ }
75
+ if (!entity.provider) {
76
+ throw new Error('CrudAutocomplete Provider is not defined')
77
+ }
78
+ if (typeof entity.provider.findById !== 'function') {
79
+ throw new Error('CrudAutocomplete Provider does not have a findById method');
80
+ }
81
+ let item = await entity.provider.findById(id)
82
+ items.value.push(item)
83
+ }
84
+ }
79
85
  }
80
- items.value = await entity.provider.search(value)
81
86
  }catch (e){
82
87
  console.error(e)
83
- }finally{
84
- loading.value = false
85
88
  }
86
-
87
89
  }
88
90
 
91
+
92
+
93
+
94
+ defineEmits(['updateValue'])
95
+
89
96
  </script>
90
97
 
91
98
  <template>
@@ -97,13 +104,23 @@ async function search(value: any) {
97
104
  :multiple="multiple"
98
105
  :chips="chips"
99
106
  :closable-chips="closableChips"
100
- :clearable="clearable"
101
107
  :item-value="itemValue"
102
108
  :item-title="itemTitle"
103
109
  :loading="loading"
104
110
  :rules="rules"
111
+ :readonly="readonly"
112
+ :density="density"
113
+ :variant="variant"
114
+ :hide-details="hideDetails"
115
+ :single-line="singleLine"
116
+ :clearable="clearable"
105
117
  :error-messages="errorMessages"
106
118
  @update:search="debouncedSearch"
119
+ @update:modelValue="$emit('updateValue')"
120
+ :prepend-icon="prependIcon"
121
+ :append-icon="appendIcon"
122
+ :prepend-inner-icon="prependInnerIcon"
123
+ :append-inner-icon="appendInnerIcon"
107
124
  ></v-autocomplete>
108
125
  </template>
109
126
 
@@ -1,14 +1,13 @@
1
1
  <script setup lang="ts">
2
- import type {TOperation} from "../interfaces/TOperation";
3
2
  import type {PropType} from "vue";
4
- import type {IEntityCrud} from "@drax/crud-share";
3
+ import type {IEntityCrud, IEntityCrudOperation} from "@drax/crud-share";
5
4
  const dialog = defineModel({type: Boolean, default: false})
6
5
  import {useI18n} from "vue-i18n";
7
6
  const {t,te} = useI18n()
8
7
 
9
8
  defineProps({
10
9
  entity: {type: Object as PropType<IEntityCrud>, required: true},
11
- operation: {type: String as PropType<TOperation>}
10
+ operation: {type: String as PropType<IEntityCrudOperation>}
12
11
  })
13
12
 
14
13
  defineEmits(
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ import type {PropType} from "vue";
3
+ import {ref} from "vue";
4
+ import CrudFormField from "./CrudFormField.vue";
5
+ import type {IEntityCrud} from "@drax/crud-share";
6
+ import {useI18n} from "vue-i18n";
7
+
8
+ const {t} = useI18n()
9
+ const valueModel = defineModel({type: [Object]})
10
+
11
+
12
+ const {entity} = defineProps({
13
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
14
+ actions: {type: Boolean, default: false},
15
+ })
16
+
17
+ const formRef = ref()
18
+
19
+ async function filter() {
20
+ emit('filter')
21
+ }
22
+
23
+ async function clear() {
24
+ emit('filter')
25
+ }
26
+
27
+
28
+ const emit = defineEmits(['filter', 'clear','updateValue'])
29
+
30
+ </script>
31
+
32
+ <template>
33
+ <v-card flat >
34
+ <v-row dense class="mt-1" justify="space-between">
35
+ <v-col v-for="(filter,index) in entity.filters" :key="filter.name"
36
+ cols="12" sm="6" md="4"
37
+ >
38
+ <crud-form-field
39
+ :field="filter"
40
+ :entity="entity"
41
+ v-model="valueModel[index].value"
42
+ :clearable="true"
43
+ density="compact"
44
+ variant="outlined"
45
+ hide-details single-line disable-rules
46
+ @updateValue="$emit('updateValue')"
47
+ />
48
+ </v-col>
49
+
50
+ </v-row>
51
+
52
+ <v-card-actions v-if="actions" class="pb-0">
53
+ <v-spacer />
54
+ <v-btn variant="text" density="compact" color="grey" @click="clear">{{ t('action.clear') }}</v-btn>
55
+ <v-btn variant="flat" density="compact" color="primary" @click="filter">
56
+ {{ t('action.filter') }}
57
+ </v-btn>
58
+ </v-card-actions>
59
+
60
+ </v-card>
61
+ </template>
62
+
63
+ <style scoped>
64
+
65
+ </style>
@@ -1,26 +1,43 @@
1
1
  <script setup lang="ts">
2
- import type {PropType} from "vue";
3
- import {ref} from "vue";
4
- import CrudFormField from "./CrudFormField.vue";
5
- import type {TOperation} from "../interfaces/TOperation";
6
- import type {IEntityCrud} from "@drax/crud-share";
7
2
  import {useI18n} from "vue-i18n";
3
+ import type {IEntityCrud, IEntityCrudOperation} from "@drax/crud-share";
4
+ import {useFormUtils} from "../composables/UseFormUtils";
5
+ import CrudFormField from "./CrudFormField.vue";
6
+ import {computed, defineEmits, defineModel, defineProps, ref} from "vue";
7
+ import type { PropType} from "vue";
8
8
  import {useCrudStore} from "../stores/UseCrudStore";
9
- const {t,te} = useI18n()
10
- const store = useCrudStore()
11
- const valueModel = defineModel({type: [Object]})
12
9
 
13
10
 
14
- const {entity} = defineProps({
11
+ const {t, te} = useI18n()
12
+
13
+ const valueModel = defineModel({type: [Object]})
14
+
15
+ const {entity, operation} = defineProps({
15
16
  entity: {type: Object as PropType<IEntityCrud>, required: true},
16
- operation: {type: String as PropType<TOperation>, required: true},
17
+ operation: {type: String as PropType<IEntityCrudOperation>, required: true},
17
18
  readonly: {type: Boolean, default: false},
18
19
  error: {type: String, required: false},
19
20
  })
20
21
 
22
+ const emit = defineEmits(['submit', 'cancel'])
23
+
24
+ const store = useCrudStore()
25
+
21
26
  const valid = ref()
22
27
  const formRef = ref()
23
28
 
29
+ const fields = computed(() => {
30
+ if(operation === 'create') {
31
+ return entity.createFields
32
+ }else if(operation === 'edit') {
33
+ return entity.updateFields
34
+ }else if(operation === 'delete') {
35
+ return entity.updateFields
36
+ }else if(operation === 'view') {
37
+ return entity.updateFields
38
+ }
39
+ })
40
+
24
41
  async function submit() {
25
42
  store.resetErrors()
26
43
  await formRef.value.validate()
@@ -35,7 +52,10 @@ function cancel() {
35
52
  emit('cancel')
36
53
  }
37
54
 
38
- const emit = defineEmits(['submit', 'cancel'])
55
+ const {
56
+ variant, submitColor, readonly
57
+ } = useFormUtils(operation)
58
+
39
59
 
40
60
  </script>
41
61
 
@@ -49,11 +69,18 @@ const emit = defineEmits(['submit', 'cancel'])
49
69
  <v-alert color="error">{{ te(error) ? t(error) : error }}</v-alert>
50
70
  </v-card-text>
51
71
  <v-card-text>
52
- <template v-for="field in entity.fields" :key="field.name">
72
+ <template v-for="field in fields" :key="field.name">
53
73
  <crud-form-field
54
74
  :field="field"
55
75
  :entity="entity"
56
76
  v-model="valueModel[field.name]"
77
+ :clearable="false"
78
+ :readonly="readonly"
79
+ :variant="variant"
80
+ :prepend-inner-icon="field?.prependInnerIcon"
81
+ :prepend-icon="field?.prependIcon"
82
+ :append-icon="field?.appendIcon"
83
+ :append-inner-icon="field?.appendInnerIcon"
57
84
  />
58
85
  </template>
59
86
  </v-card-text>
@@ -61,7 +88,7 @@ const emit = defineEmits(['submit', 'cancel'])
61
88
  <v-card-actions>
62
89
  <v-spacer></v-spacer>
63
90
  <v-btn variant="text" color="grey" @click="cancel">{{ t('action.cancel') }}</v-btn>
64
- <v-btn variant="flat" color="primary" @click="submit">
91
+ <v-btn variant="flat" v-if="operation != 'view'" :color="submitColor" @click="submit">
65
92
  {{ operation ? t('action.' + operation) : t('action.sent') }}
66
93
  </v-btn>
67
94
  </v-card-actions>
@@ -6,20 +6,32 @@ import CrudAutocomplete from "./CrudAutocomplete.vue";
6
6
  import {useI18n} from "vue-i18n";
7
7
  import {useCrudStore} from "../stores/UseCrudStore";
8
8
  import {VDateInput} from 'vuetify/labs/VDateInput'
9
- import type {IEntityCrud, IEntityCrudField} from "@drax/crud-share";
9
+ import type {IEntityCrud, IEntityCrudField, IEntityCrudFilter} from "@drax/crud-share";
10
10
  const {t, te} = useI18n()
11
11
 
12
12
  const store = useCrudStore()
13
13
 
14
14
  const valueModel = defineModel<any>({type: [String, Number, Boolean, Object, Array], default: false})
15
15
 
16
- const {index, entity, field} = defineProps({
16
+ const {index, entity, field, disableRules} = defineProps({
17
17
  entity: {type: Object as PropType<IEntityCrud>, required: true},
18
- field: {type: Object as PropType<IEntityCrudField|undefined>, required: true},
18
+ field: {type: Object as PropType<IEntityCrudField|IEntityCrudFilter|undefined>, required: true},
19
+ prependIcon: {type: String, default: ''},
20
+ prependInnerIcon: {type: String, default: ''},
21
+ appendIcon: {type: String, default: ''},
22
+ appendInnerIcon: {type: String, default: ''},
19
23
  readonly: {type: Boolean, default: false},
24
+ hideDetails: {type: Boolean, default: false},
25
+ singleLine: {type: Boolean, default: false},
26
+ clearable: {type: Boolean, default: false},
27
+ disableRules: {type: Boolean, default: false},
20
28
  index: {type: Number, default: 0},
29
+ density: {type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default'},
30
+ variant: {type: String as PropType<'underlined' | 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'plain'>, default: 'filled'},
21
31
  })
22
32
 
33
+
34
+
23
35
  if(!field){
24
36
  throw new Error("CrudFormField must be provided with a field object")
25
37
  }
@@ -32,6 +44,7 @@ const label = computed(() => {
32
44
  })
33
45
 
34
46
  const rules = computed(() => {
47
+ if(disableRules) return undefined
35
48
  return entity.getRule(field.name) as any
36
49
  })
37
50
 
@@ -39,6 +52,8 @@ const inputErrors = computed(() =>
39
52
  store.getInputErrors(field.name).map((error: string) => t(te(error) ? t(error) : error))
40
53
  )
41
54
 
55
+ defineEmits(['updateValue'])
56
+
42
57
  </script>
43
58
 
44
59
  <template>
@@ -53,8 +68,17 @@ const inputErrors = computed(() =>
53
68
  :readonly="readonly"
54
69
  :error-messages="inputErrors"
55
70
  :rules="rules"
56
- >
57
- </v-text-field>
71
+ :density="density"
72
+ :variant="variant"
73
+ :clearable="clearable"
74
+ :hide-details="hideDetails"
75
+ :single-line="singleLine"
76
+ :prepend-icon="prependIcon"
77
+ :append-icon="appendIcon"
78
+ :prepend-inner-icon="prependInnerIcon"
79
+ :append-inner-icon="appendInnerIcon"
80
+ @update:modelValue="$emit('updateValue')"
81
+ />
58
82
 
59
83
  <v-text-field
60
84
  v-if="field.type === 'number'"
@@ -65,10 +89,19 @@ const inputErrors = computed(() =>
65
89
  :readonly="readonly"
66
90
  :error-messages="inputErrors"
67
91
  :rules="rules"
68
- >
69
- </v-text-field>
92
+ :density="density"
93
+ :variant="variant"
94
+ :clearable="clearable"
95
+ :hide-details="hideDetails"
96
+ :single-line="singleLine"
97
+ @update:modelValue="$emit('updateValue')"
98
+ :prepend-icon="prependIcon"
99
+ :append-icon="appendIcon"
100
+ :prepend-inner-icon="prependInnerIcon"
101
+ :append-inner-icon="appendInnerIcon"
102
+ />
70
103
 
71
- <v-checkbox
104
+ <v-switch
72
105
  v-if="field.type === 'boolean'"
73
106
  :name="name"
74
107
  :label="label"
@@ -76,8 +109,18 @@ const inputErrors = computed(() =>
76
109
  :readonly="readonly"
77
110
  :error-messages="inputErrors"
78
111
  :rules="rules"
79
- >
80
- </v-checkbox>
112
+ :density="density"
113
+ :variant="variant"
114
+ :clearable="clearable"
115
+ :hide-details="hideDetails"
116
+ :single-line="singleLine"
117
+ @update:modelValue="$emit('updateValue')"
118
+ :prepend-icon="prependIcon"
119
+ :append-icon="appendIcon"
120
+ :prepend-inner-icon="prependInnerIcon"
121
+ :append-inner-icon="appendInnerIcon"
122
+ color="primary"
123
+ />
81
124
 
82
125
 
83
126
  <v-date-input
@@ -88,9 +131,17 @@ const inputErrors = computed(() =>
88
131
  v-model="valueModel"
89
132
  :readonly="readonly"
90
133
  :error-messages="inputErrors"
91
- prepend-inner-icon="mdi-calendar"
92
- prepend-icon=""
93
134
  :rules="rules"
135
+ :density="density"
136
+ :variant="variant"
137
+ :clearable="clearable"
138
+ :hide-details="hideDetails"
139
+ :single-line="singleLine"
140
+ @update:modelValue="$emit('updateValue')"
141
+ :prepend-icon="prependIcon"
142
+ :append-icon="appendIcon"
143
+ :prepend-inner-icon="prependInnerIcon"
144
+ :append-inner-icon="appendInnerIcon"
94
145
  />
95
146
 
96
147
  <crud-autocomplete
@@ -101,6 +152,17 @@ const inputErrors = computed(() =>
101
152
  :label="label"
102
153
  :error-messages="inputErrors"
103
154
  :rules="rules"
155
+ :density="density"
156
+ :variant="variant"
157
+ :readonly="readonly"
158
+ :clearable="clearable"
159
+ :hide-details="hideDetails"
160
+ :single-line="singleLine"
161
+ @updateValue="$emit('updateValue')"
162
+ :prepend-icon="prependIcon"
163
+ :append-icon="appendIcon"
164
+ :prepend-inner-icon="prependInnerIcon"
165
+ :append-inner-icon="appendInnerIcon"
104
166
  />
105
167
 
106
168
  <v-card v-if="field.type === 'object'" class="mt-3" variant="flat" border>
@@ -112,6 +174,16 @@ const inputErrors = computed(() =>
112
174
  :entity="entity"
113
175
  :field="oField"
114
176
  v-model="valueModel[oField.name]"
177
+ :density="density"
178
+ :variant="variant"
179
+ :clearable="clearable"
180
+ :hide-details="hideDetails"
181
+ :single-line="singleLine"
182
+ @updateValue="$emit('updateValue')"
183
+ :prepend-icon="prependIcon"
184
+ :append-icon="appendIcon"
185
+ :prepend-inner-icon="prependInnerIcon"
186
+ :append-inner-icon="appendInnerIcon"
115
187
  ></crud-form-field>
116
188
  </v-card-text>
117
189
 
@@ -126,9 +198,19 @@ const inputErrors = computed(() =>
126
198
  :multiple="true"
127
199
  :chips="true"
128
200
  :closable-chips="true"
129
- :clearable="true"
130
201
  :readonly="readonly"
131
202
  :error-messages="inputErrors"
203
+ :density="density"
204
+ :variant="variant"
205
+ :clearable="clearable"
206
+ :hide-details="hideDetails"
207
+ :single-line="singleLine"
208
+ :rules="rules"
209
+ @update:modelValue="$emit('updateValue')"
210
+ :prepend-icon="prependIcon"
211
+ :append-icon="appendIcon"
212
+ :prepend-inner-icon="prependInnerIcon"
213
+ :append-inner-icon="appendInnerIcon"
132
214
  >
133
215
  </v-combobox>
134
216
 
@@ -140,9 +222,19 @@ const inputErrors = computed(() =>
140
222
  v-model="valueModel"
141
223
  :multiple="true"
142
224
  :chips="true"
143
- :clearable="true"
144
225
  :label="label"
226
+ :rules="rules"
145
227
  :error-messages="inputErrors"
228
+ :density="density"
229
+ :variant="variant"
230
+ :clearable="clearable"
231
+ :hide-details="hideDetails"
232
+ :single-line="singleLine"
233
+ @updateValue="$emit('updateValue')"
234
+ :prepend-icon="prependIcon"
235
+ :append-icon="appendIcon"
236
+ :prepend-inner-icon="prependInnerIcon"
237
+ :append-inner-icon="appendInnerIcon"
146
238
  />
147
239
 
148
240
 
@@ -154,9 +246,19 @@ const inputErrors = computed(() =>
154
246
  v-model="valueModel"
155
247
  :multiple="true"
156
248
  :chips="true"
157
- :clearable="true"
158
249
  :readonly="readonly"
159
250
  :error-messages="inputErrors"
251
+ :density="density"
252
+ :variant="variant"
253
+ :clearable="clearable"
254
+ :hide-details="hideDetails"
255
+ :single-line="singleLine"
256
+ :rules="rules"
257
+ @update:modelValue="$emit('updateValue')"
258
+ :prepend-icon="prependIcon"
259
+ :append-icon="appendIcon"
260
+ :prepend-inner-icon="prependInnerIcon"
261
+ :append-inner-icon="appendInnerIcon"
160
262
  >
161
263
  </v-combobox>
162
264
 
@@ -167,6 +269,12 @@ const inputErrors = computed(() =>
167
269
  :field="field"
168
270
  v-model="valueModel"
169
271
  :readonly="readonly"
272
+ :density="density"
273
+ :variant="variant"
274
+ :clearable="clearable"
275
+ :hide-details="hideDetails"
276
+ :single-line="singleLine"
277
+ @updateValue="$emit('updateValue')"
170
278
  />
171
279
 
172
280
  </div>
@@ -9,6 +9,11 @@ const {field} = defineProps({
9
9
  entity: {type: Object as PropType<IEntityCrud>, required: true},
10
10
  field: {type: Object as PropType<IEntityCrudField>, required: true},
11
11
  readonly: {type: Boolean, default: false},
12
+ hideDetails: {type: Boolean, default: false},
13
+ singleLine: {type: Boolean, default: false},
14
+ clearable: {type: Boolean, default: true},
15
+ density: {type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default'},
16
+ variant: {type: String as PropType<'underlined' | 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'plain'>, default: 'filled'},
12
17
  })
13
18
 
14
19
  function newItem() {
@@ -31,6 +36,8 @@ function removeItem(index: number) {
31
36
  valueModel.value.splice(index, 1);
32
37
  }
33
38
 
39
+ defineEmits(['updateValue'])
40
+
34
41
  </script>
35
42
 
36
43
  <template>
@@ -50,6 +57,12 @@ function removeItem(index: number) {
50
57
  v-model="(valueModel[index] as any)[key]"
51
58
  :readonly="readonly"
52
59
  :index="index"
60
+ :density="density"
61
+ :variant="variant"
62
+ :clearable="clearable"
63
+ :hide-details="hideDetails"
64
+ :single-line="singleLine"
65
+ @updateValue="$emit('updateValue')"
53
66
  />
54
67
  </template>
55
68
 
@@ -8,24 +8,33 @@ import CrudImportButton from "./buttons/CrudImportButton.vue";
8
8
  import CrudCreateButton from "./buttons/CrudCreateButton.vue";
9
9
  import CrudUpdateButton from "./buttons/CrudUpdateButton.vue";
10
10
  import CrudDeleteButton from "./buttons/CrudDeleteButton.vue";
11
+ import CrudViewButton from "./buttons/CrudViewButton.vue";
11
12
  import CrudExportList from "./CrudExportList.vue";
12
13
  import type {IEntityCrud} from "@drax/crud-share";
13
14
  import {useI18n} from "vue-i18n";
14
15
  import type {IEntityCrudHeader} from "@drax/crud-share";
16
+ import CrudFilters from "./CrudFilters.vue";
15
17
 
16
- const {t,te} = useI18n()
18
+ const {t, te} = useI18n()
17
19
  const {hasPermission} = useAuth()
18
20
 
19
21
  const {entity} = defineProps({
20
22
  entity: {type: Object as PropType<IEntityCrud>, required: true},
23
+
21
24
  })
22
25
 
23
26
  const {
24
27
  loading, itemsPerPage, page, sortBy, search, totalItems, items,
25
- loadItems
28
+ doPaginate, filters
26
29
  } = useCrud(entity)
27
30
 
28
- const actions: IEntityCrudHeader[] = [{title: t('action.actions'), key: 'actions', sortable: false, align: 'end', minWidth: '140px'}]
31
+ const actions: IEntityCrudHeader[] = [{
32
+ title: t('action.actions'),
33
+ key: 'actions',
34
+ sortable: false,
35
+ align: 'center',
36
+ minWidth: '190px'
37
+ }]
29
38
  const tHeaders: IEntityCrudHeader[] = entity.headers.map(header => ({
30
39
  ...header,
31
40
  title: te(`${entity.name.toLowerCase()}.fields.${header.title}`) ? t(`${entity.name.toLowerCase()}.fields.${header.title}`) : header.title
@@ -35,9 +44,11 @@ const headers: IEntityCrudHeader[] = [...tHeaders, ...actions]
35
44
 
36
45
 
37
46
  defineExpose({
38
- loadItems
47
+ doPaginate
39
48
  });
40
49
 
50
+ defineEmits(['import', 'export', 'create', 'update', 'delete', 'view'])
51
+
41
52
  </script>
42
53
 
43
54
  <template>
@@ -55,7 +66,7 @@ defineExpose({
55
66
  :search="search"
56
67
  :multi-sort="false"
57
68
  item-value="name"
58
- @update:options="loadItems"
69
+ @update:options="doPaginate"
59
70
  >
60
71
  <template v-slot:top>
61
72
  <v-toolbar density="compact">
@@ -80,12 +91,25 @@ defineExpose({
80
91
 
81
92
  </v-toolbar>
82
93
 
83
- <crud-export-list :entity="entity"></crud-export-list>
94
+ <crud-export-list
95
+ :entity="entity"
96
+ />
84
97
 
85
98
  <v-card>
86
99
  <v-card-text>
87
- <crud-search v-model="search"></crud-search>
100
+ <crud-search
101
+ v-model="search"
102
+ />
103
+ </v-card-text>
104
+
105
+ <v-card-text class="pt-0">
106
+ <crud-filters
107
+ :entity="entity"
108
+ v-model="filters"
109
+ @updateValue="doPaginate()"
110
+ />
88
111
  </v-card-text>
112
+
89
113
  </v-card>
90
114
 
91
115
  </template>
@@ -99,6 +123,11 @@ defineExpose({
99
123
 
100
124
 
101
125
  <template v-slot:item.actions="{item}">
126
+ <crud-view-button
127
+ v-if="entity.isViewable && hasPermission(entity.permissions.view)"
128
+ @click="$emit('view', item)"
129
+ />
130
+
102
131
  <crud-update-button
103
132
  v-if="entity.isEditable && hasPermission(entity.permissions.update)"
104
133
  @click="$emit('edit', item)"
@@ -5,12 +5,14 @@ const model = defineModel<any>()
5
5
  </script>
6
6
 
7
7
  <template>
8
- <v-text-field v-model="model" hide-details
9
- density="compact" class="mr-2"
8
+ <v-text-field v-model="model"
9
+ density="compact"
10
+ class="mr-2"
10
11
  variant="outlined"
11
12
  append-inner-icon="mdi-magnify"
12
13
  :label="t('action.search')"
13
- single-line clearable @click:clear="() => model = ''"
14
+ single-line hide-details
15
+ clearable @click:clear="() => model = ''"
14
16
  />
15
17
  </template>
16
18
 
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import {useI18n} from "vue-i18n";
3
+ const {t} = useI18n()
4
+ </script>
5
+
6
+ <template>
7
+ <v-tooltip location="top">
8
+ <template v-slot:activator="{ props}">
9
+ <v-btn
10
+ v-bind="{ ...$attrs, ...props }"
11
+ icon="mdi-magnify"
12
+ class="mr-1"
13
+ variant="text"
14
+ color="secondary"
15
+ slim
16
+ >
17
+ </v-btn>
18
+ </template>
19
+ {{ t('action.view')}}
20
+ </v-tooltip>
21
+ </template>
22
+
23
+ <style scoped>
24
+
25
+ </style>
@@ -6,15 +6,37 @@ export function useCrud(entity: IEntityCrud) {
6
6
 
7
7
  const store = useCrudStore()
8
8
 
9
+ const dialog = computed({get(){return store.dialog} , set(value){store.setDialog(value)}})
10
+ const operation = computed({get(){return store.operation} , set(value){store.setOperation(value)}})
11
+ const form = computed({get(){return store.form} , set(value){store.setForm(value)}})
12
+ const formValid = computed({get(){return store.formValid} , set(value){store.setFormValid(value)}})
13
+ const notify = computed({get(){return store.notify} , set(value){store.setNotify(value)}})
14
+ const error = computed({get(){return store.error} , set(value){store.setError(value)}})
15
+ const message = computed({get(){return store.message} , set(value){store.setMessage(value)}})
16
+ const loading = computed({get(){return store.loading} , set(value){store.setLoading(value)}})
17
+ const itemsPerPage = computed({get(){return store.itemsPerPage} , set(value){store.setItemsPerPage(value)}})
18
+ const page = computed({get(){return store.page} , set(value){store.setPage(value)}})
19
+ const sortBy = computed({get(){return store.sortBy} , set(value){store.setSortBy(value)}})
20
+ const search = computed({get(){return store.search} , set(value){store.setSearch(value)}})
21
+ const totalItems = computed({get(){return store.totalItems} , set(value){store.setTotalItems(value)}})
22
+ const items = computed({get(){return store.items} , set(value){store.setItems(value)}})
23
+ const exportFiles = computed({get(){return store.exportFiles} , set(value){store.setExportFiles(value)}})
24
+ const exportLoading = computed({get(){return store.exportLoading} , set(value){store.setExportLoading(value)}})
25
+ const exportListVisible = computed({get(){return store.exportListVisible} , set(value){store.setExportListVisible(value)}})
26
+ const filters = computed({get(){return store.filters} , set(value){store.setFilters(value)}})
27
+
28
+
9
29
  async function doPaginate() {
10
30
  store.setLoading(true)
11
31
  try {
32
+
12
33
  const r: IDraxPaginateResult<any> = await entity?.provider.paginate({
13
34
  page: store.page,
14
35
  limit: store.itemsPerPage,
15
36
  orderBy: store.sortBy[0]?.key,
16
37
  order: store.sortBy[0]?.order,
17
- search: store.search
38
+ search: store.search,
39
+ filters: store.filters
18
40
  })
19
41
  store.setItems(r.items)
20
42
  store.setTotalItems(r.total)
@@ -61,13 +83,6 @@ export function useCrud(entity: IEntityCrud) {
61
83
 
62
84
 
63
85
 
64
-
65
- function onCreate() {
66
- store.setOperation("create")
67
- store.setForm(entity.form)
68
- store.setDialog(true)
69
- }
70
-
71
86
  function cast(item: any){
72
87
  entity.fields.filter(field => field.type === 'date')
73
88
  .forEach(field => {
@@ -76,31 +91,44 @@ export function useCrud(entity: IEntityCrud) {
76
91
 
77
92
  entity.fields.filter(field => field.type === 'ref')
78
93
  .forEach(field => {
79
- item[field.name] = item[field.name]._id
94
+ item[field.name] = item[field.name]?._id ? item[field.name]._id : item[field.name]
80
95
  })
81
96
 
82
97
  entity.fields.filter(field => field.type === 'array.ref')
83
98
  .forEach(field => {
84
- item[field.name] = item[field.name].map(((i:any) => i._id))
99
+ item[field.name] = item[field.name].map(((i:any) => i?._id ? i._id : i))
85
100
  })
86
101
 
87
102
  return item
88
103
  }
89
104
 
105
+ function onView(item: object) {
106
+ store.setOperation("view")
107
+ store.setForm(cast({...item}))
108
+ openDialog()
109
+ }
110
+
111
+
112
+ function onCreate() {
113
+ store.setOperation("create")
114
+ store.setForm(entity.form)
115
+ openDialog()
116
+ }
117
+
90
118
  function onEdit(item: object) {
91
119
  store.setOperation("edit")
92
120
  store.setForm(cast({...item}))
93
- store.setDialog(true)
121
+ openDialog()
94
122
  }
95
123
 
96
124
  function onDelete(item: object) {
97
125
  store.setOperation("delete")
98
126
  store.setForm(cast({...item}))
99
- store.setDialog(true)
127
+ openDialog()
100
128
  }
101
129
 
102
130
  function onCancel() {
103
- store.setDialog(false)
131
+ closeDialog()
104
132
  store.setError("")
105
133
  store.setInputErrors(null)
106
134
  }
@@ -108,6 +136,9 @@ export function useCrud(entity: IEntityCrud) {
108
136
  function onSubmit(formData: any) {
109
137
  store.setInputErrors(null)
110
138
  switch (store.operation) {
139
+ case "view":
140
+ closeDialog()
141
+ break
111
142
  case "create":
112
143
  doCreate(formData)
113
144
  break
@@ -120,11 +151,19 @@ export function useCrud(entity: IEntityCrud) {
120
151
  }
121
152
  }
122
153
 
154
+ function openDialog() {
155
+ store.setDialog(true)
156
+ }
157
+
158
+ function closeDialog() {
159
+ store.setDialog(false)
160
+ }
161
+
123
162
  async function doCreate(formData: any) {
124
163
  try {
125
164
  await entity?.provider.create(formData)
126
165
  await doPaginate()
127
- store.setDialog(false)
166
+ closeDialog()
128
167
  store.showMessage("Entity created successfully!")
129
168
  } catch (e: any) {
130
169
  if(e.inputErrors){
@@ -140,7 +179,7 @@ export function useCrud(entity: IEntityCrud) {
140
179
  try {
141
180
  await entity?.provider.update(formData._id, formData)
142
181
  await doPaginate()
143
- store.setDialog(false)
182
+ closeDialog()
144
183
  store.showMessage("Entity updated successfully!")
145
184
  } catch (e: any) {
146
185
  console.log("inputErrors", e.inputErrors)
@@ -157,7 +196,7 @@ export function useCrud(entity: IEntityCrud) {
157
196
  try {
158
197
  await entity?.provider.delete(formData._id)
159
198
  await doPaginate()
160
- store.setDialog(false)
199
+ closeDialog()
161
200
  store.showMessage("Entity deleted successfully!")
162
201
  } catch (e: any) {
163
202
  store.setError(e.message || "An error occurred while deleting the entity")
@@ -170,28 +209,16 @@ export function useCrud(entity: IEntityCrud) {
170
209
  store.$reset()
171
210
  }
172
211
 
173
- const dialog = computed({get(){return store.dialog} , set(value){store.setDialog(value)}})
174
- const operation = computed({get(){return store.operation} , set(value){store.setOperation(value)}})
175
- const form = computed({get(){return store.form} , set(value){store.setForm(value)}})
176
- const formValid = computed({get(){return store.formValid} , set(value){store.setFormValid(value)}})
177
- const notify = computed({get(){return store.notify} , set(value){store.setNotify(value)}})
178
- const error = computed({get(){return store.error} , set(value){store.setError(value)}})
179
- const message = computed({get(){return store.message} , set(value){store.setMessage(value)}})
180
- const loading = computed({get(){return store.loading} , set(value){store.setLoading(value)}})
181
- const itemsPerPage = computed({get(){return store.itemsPerPage} , set(value){store.setItemsPerPage(value)}})
182
- const page = computed({get(){return store.page} , set(value){store.setPage(value)}})
183
- const sortBy = computed({get(){return store.sortBy} , set(value){store.setSortBy(value)}})
184
- const search = computed({get(){return store.search} , set(value){store.setSearch(value)}})
185
- const totalItems = computed({get(){return store.totalItems} , set(value){store.setTotalItems(value)}})
186
- const items = computed({get(){return store.items} , set(value){store.setItems(value)}})
187
- const exportFiles = computed({get(){return store.exportFiles} , set(value){store.setExportFiles(value)}})
188
- const exportLoading = computed({get(){return store.exportLoading} , set(value){store.setExportLoading(value)}})
189
- const exportListVisible = computed({get(){return store.exportListVisible} , set(value){store.setExportListVisible(value)}})
212
+ function prepareFilters(){
213
+ store.setFilters(entity.formFilters)
214
+ }
215
+
190
216
 
191
217
  return {
192
- loadItems: doPaginate, doExport, onCreate, onEdit, onDelete, onCancel, onSubmit,resetCrudStore,
218
+ doPaginate, doExport, onView, onCreate, onEdit, onDelete, onCancel, onSubmit,resetCrudStore,
193
219
  operation, dialog, form, notify, error, message, formValid,
194
220
  loading, itemsPerPage, page, sortBy, search, totalItems, items,
221
+ prepareFilters,filters,
195
222
  exportFiles,exportLoading,exportListVisible
196
223
  }
197
224
 
@@ -0,0 +1,42 @@
1
+ import type {IEntityCrudOperation} from "@drax/crud-share";
2
+ import {computed} from "vue";
3
+
4
+
5
+ export function useFormUtils(operation:IEntityCrudOperation) {
6
+
7
+ const readonly = computed(() => {
8
+ return operation === 'delete' || operation === 'view';
9
+ })
10
+
11
+ const submitColor = computed(() => {
12
+ if(operation === 'create') {
13
+ return 'primary'
14
+ }else if(operation === 'edit') {
15
+ return 'primary'
16
+ }else if(operation === 'delete') {
17
+ return 'red'
18
+ }else if(operation === 'view') {
19
+ return 'secondary'
20
+ }
21
+ })
22
+
23
+ const variant = computed(() => {
24
+ if(operation === 'create') {
25
+ return 'filled'
26
+ }else if(operation === 'edit') {
27
+ return 'filled'
28
+ }else if(operation === 'delete') {
29
+ return 'underlined'
30
+ }else if(operation === 'view') {
31
+ return 'underlined'
32
+ }
33
+ })
34
+
35
+
36
+
37
+ return {
38
+ readonly,
39
+ variant,
40
+ submitColor,
41
+ }
42
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import CrudNotify from "./components/CrudNotify.vue";
8
8
  import CrudSearch from "./components/CrudSearch.vue";
9
9
  import {useCrudStore} from "./stores/UseCrudStore";
10
10
  import {useCrud} from "./composables/UseCrud";
11
+ import {useFormUtils} from "./composables/UseFormUtils";
11
12
  import {EntityCrud} from "./EntityCrud";
12
13
 
13
14
 
@@ -21,6 +22,7 @@ export {
21
22
  CrudNotify,
22
23
  CrudSearch,
23
24
  useCrud,
25
+ useFormUtils,
24
26
  useCrudStore,
25
27
  EntityCrud
26
28
 
@@ -1,16 +1,17 @@
1
1
  import {defineStore} from "pinia";
2
- import type {TOperation} from "../interfaces/TOperation";
2
+ import type {IEntityCrudOperation} from "@drax/crud-share";
3
3
 
4
4
  export const useCrudStore = defineStore('CrudStore', {
5
5
  state: () => (
6
6
  {
7
- operation: null as TOperation,
7
+ operation: null as IEntityCrudOperation,
8
8
  dialog: false as boolean,
9
9
  form: {} as any,
10
10
  formValid: {} as any,
11
11
  notify: false as boolean,
12
12
  message: '' as string,
13
13
  error: '' as string,
14
+ filters: [] as any[],
14
15
  items: [] as any[],
15
16
  totalItems: 0 as number,
16
17
  itemsPerPage: 5 as number,
@@ -35,7 +36,7 @@ export const useCrudStore = defineStore('CrudStore', {
35
36
  }
36
37
  },
37
38
  actions: {
38
- setOperation(operation: TOperation) {
39
+ setOperation(operation: IEntityCrudOperation) {
39
40
  this.operation = operation
40
41
  },
41
42
  setDialog(dialog: boolean) {
@@ -99,6 +100,9 @@ export const useCrudStore = defineStore('CrudStore', {
99
100
  },
100
101
  setExportListVisible(exportListVisible: boolean) {
101
102
  this.exportListVisible = exportListVisible
103
+ },
104
+ setFilters(filters: any[]) {
105
+ this.filters = filters
102
106
  }
103
107
  }
104
108
 
@@ -1,6 +0,0 @@
1
- type TOperation = "create" | "edit" | "delete" | null
2
-
3
-
4
- export type {
5
- TOperation
6
- }