@drax/crud-vue 0.4.0 → 0.5.2

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.4.0",
6
+ "version": "0.5.2",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "module": "./src/index.ts",
@@ -24,8 +24,9 @@
24
24
  "format": "prettier --write src/"
25
25
  },
26
26
  "dependencies": {
27
- "@drax/common-front": "^0.4.0",
28
- "@drax/common-share": "^0.4.0"
27
+ "@drax/common-front": "^0.5.1",
28
+ "@drax/crud-front": "^0.5.1",
29
+ "@drax/crud-share": "^0.5.2"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "pinia": "^2.2.2",
@@ -62,5 +63,5 @@
62
63
  "vue-tsc": "^2.0.11",
63
64
  "vuetify": "^3.7.1"
64
65
  },
65
- "gitHead": "481b302fe72f403abf092806ceca540dd2765dfa"
66
+ "gitHead": "6f854dbeb2af7bca32ed35bcec2f8e48790a73b9"
66
67
  }
package/src/EntityCrud.ts CHANGED
@@ -1,102 +1,98 @@
1
- import type {IDraxCrud} from "@drax/common-share";
2
1
  import type {
3
- IFields,
4
- ICrudForm,
5
- ICrudHeaders,
6
- ICrudPermissions,
7
- ICrudRules,
8
- ICrudField
9
- } from "./interfaces/IEntityCrud";
2
+ IEntityCrud, IEntityCrudForm, IEntityCrudHeader, IEntityCrudRefs,
3
+ IEntityCrudRules, IEntityCrudField, IEntityCrudPermissions,
4
+ IDraxCrudProvider
5
+ } from "@drax/crud-share";
10
6
 
11
7
 
12
8
 
13
- class EntityCrud{
9
+ class EntityCrud implements IEntityCrud{
14
10
 
15
11
  name: string = ''
16
12
 
17
13
  constructor() {
18
14
  }
19
15
 
20
- static get instance(){
16
+ static get instance():IEntityCrud{
21
17
  throw new Error('EntityCrud instance not found')
22
18
  }
23
19
 
24
20
 
25
- get headers():ICrudHeaders[]{
21
+ get headers():IEntityCrudHeader[]{
26
22
  return [
27
23
  {title: 'ID',key:'_id'},
28
24
  ]
29
25
  }
30
26
 
31
- get permissions(): ICrudPermissions {
27
+ get permissions(): IEntityCrudPermissions {
32
28
  return {
33
29
  manage: 'manage', view: 'view', create: 'create', update: 'update', delete: 'delete'
34
30
  }
35
31
  }
36
32
 
37
- get provider(): IDraxCrud<any, any, any>{
33
+ get provider(): IDraxCrudProvider<any, any, any>{
38
34
  throw new Error('provider not implemented')
39
35
  }
40
36
 
41
- get fields():IFields{
37
+ get fields():IEntityCrudField[]{
42
38
  return [
43
39
  {name: 'id', type: 'string', label: 'ID', default: '' },
44
40
  ]
45
41
  }
46
42
 
47
- get form():ICrudForm{
48
-
49
- function objectFields(field:ICrudField){
50
- let value:any = {}
51
- if(field.objectFields){
52
- field.objectFields.forEach(subField => {
53
- if(subField.type === 'object'){
54
- value[subField.name] = objectFields(subField)
55
- }else{
56
- value[subField.name] = subField.default
57
- }
58
-
59
- })
60
- }
61
- return value
43
+ objectFields(field:IEntityCrudField){
44
+ let value:any = {}
45
+ if(field.objectFields){
46
+ field.objectFields.forEach(subField => {
47
+ if(subField.type === 'object'){
48
+ value[subField.name] = this.objectFields(subField)
49
+ }else{
50
+ value[subField.name] = subField.default
51
+ }
52
+
53
+ })
62
54
  }
55
+ return value
56
+ }
63
57
 
64
- const form = this.fields.reduce((acc, field) => {
58
+ get form():IEntityCrudForm{
65
59
 
60
+ const form = this.fields.reduce((acc, field) => {
66
61
  let value = null
67
62
  if(field.type === 'object'){
68
- value = objectFields(field)
69
- } else if(field.default != undefined){
63
+ value = this.objectFields(field)
64
+ }else if(field.default != undefined){
70
65
  value = field.default
71
66
  }
72
67
 
73
68
  return {...acc, [field.name]: value }
74
69
  }, {})
75
70
 
76
- console.log("Form: ", form)
71
+ //console.log("Form: ", form)
77
72
 
78
73
  return form
79
74
 
80
75
  }
81
76
 
82
- get refs():{ [key: string]: EntityCrud }{
77
+ get refs():IEntityCrudRefs{
83
78
  return {}
84
79
  }
85
80
 
86
- getRef(ref: string):EntityCrud{
81
+ getRef(ref: string):IEntityCrud{
87
82
  if(!this.refs.hasOwnProperty(ref)) {
88
83
  throw new Error("Ref not found: " + ref)
89
84
  }
90
85
 
91
- return this.refs[ref] as EntityCrud
86
+ return this.refs[ref]
92
87
  }
93
88
 
94
- get rules(): ICrudRules{
89
+ get rules(): IEntityCrudRules{
95
90
  return {}
96
91
  }
97
92
 
98
- get rule() {
99
- return (field:string) => this.rules[field] || []
93
+ getRule(field:string|undefined):Array<Function>|undefined {
94
+ console.log("Getting rule for field: ", field, this.rules)
95
+ return field && this.rules[field] && this.rules[field].length > 0 ? this.rules[field] : undefined
100
96
  }
101
97
 
102
98
  get isEditable(){
@@ -111,11 +107,29 @@ class EntityCrud{
111
107
  return true
112
108
  }
113
109
 
114
- get dialogFullscreen(){
115
- return false
110
+ get isExportable(){
111
+ return true
112
+ }
113
+
114
+ get exportFormats(){
115
+ return ['CSV', 'JSON']
116
+ }
117
+
118
+ get exportHeaders(){
119
+ return ['_id']
120
+ }
121
+
122
+ get isImportable(){
123
+ return true
116
124
  }
117
125
 
126
+ get importFormats(){
127
+ return ['CSV', 'JSON']
128
+ }
118
129
 
130
+ get dialogFullscreen(){
131
+ return false
132
+ }
119
133
 
120
134
 
121
135
  }
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type {PropType} from "vue";
3
- import EntityCrud from "../EntityCrud";
2
+ import {onBeforeMount, type PropType} from "vue";
3
+ import type {IEntityCrud} from "@drax/crud-share";
4
4
  import CrudList from "./CrudList.vue";
5
5
  import CrudForm from "./CrudForm.vue";
6
6
  import CrudNotify from "./CrudNotify.vue";
@@ -8,14 +8,18 @@ import CrudDialog from "./CrudDialog.vue";
8
8
  import {useCrud} from "../composables/UseCrud";
9
9
 
10
10
  const {entity} = defineProps({
11
- entity: {type: Object as PropType<EntityCrud>, required: true},
11
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
12
12
  })
13
13
 
14
14
  const {
15
- onCreate, onEdit, onDelete, onCancel, onSubmit,
16
- operation, dialog, form, formValid, notify, error, message,
15
+ onCreate, onEdit, onDelete, onCancel, onSubmit,resetCrudStore,
16
+ operation, dialog, form, notify, error, message, doExport
17
17
  } = useCrud(entity);
18
18
 
19
+ onBeforeMount(() => {
20
+ resetCrudStore()
21
+ })
22
+
19
23
  </script>
20
24
 
21
25
  <template>
@@ -27,6 +31,7 @@ const {
27
31
  @create="onCreate"
28
32
  @edit="onEdit"
29
33
  @delete="onDelete"
34
+ @export="doExport"
30
35
  >
31
36
  <template v-for="header in entity.headers" :key="header.key" v-slot:[`item.${header.key}`]="{item, value}">
32
37
  <slot :name="`item.${header.key}`" v-bind="{item, value}">
@@ -1,15 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import {debounce} from "@drax/common-front"
3
- import type { PropType, Ref} from "vue";
3
+ import { type PropType, type Ref} from "vue";
4
4
  import {ref, onBeforeMount} from "vue";
5
- import EntityCrud from "../EntityCrud";
6
- import type {ICrudField} from "@/interfaces/IEntityCrud";
5
+ import type {IEntityCrud, IEntityCrudField} from "@drax/crud-share";
7
6
 
8
- const valueModel = defineModel({type: [String, Array], required: false})
7
+ const valueModel = defineModel<string | string[]>({type: [String, Array], required: false})
9
8
 
10
9
  const {entity, multiple} = defineProps({
11
- entity: {type: Object as PropType<EntityCrud>, required: true},
12
- field: {type: Object as PropType<ICrudField>, required: true},
10
+ entity: {type: Object as PropType<IEntityCrud|undefined>, required: true},
11
+ field: {type: Object as PropType<IEntityCrudField>, required: true},
13
12
  multiple: {type: Boolean, default: false},
14
13
  chips: {type: Boolean, default: false},
15
14
  closableChips: {type: Boolean, default: true},
@@ -17,10 +16,14 @@ const {entity, multiple} = defineProps({
17
16
  label: {type: String},
18
17
  itemValue: {type: [String], default: '_id'},
19
18
  itemTitle: {type: [String], default: 'name'},
20
- rules: {type: Array<Function>, default: []},
19
+ rules: {type: Array as PropType<any>, default: []},
21
20
  errorMessages: {type: Array as PropType<string[]>, default: []},
22
21
  })
23
22
 
23
+ if(!entity){
24
+ throw new Error('entity is required')
25
+ }
26
+
24
27
  const loading: Ref<boolean> = ref(false)
25
28
  const items: Ref<Array<any>> = ref([])
26
29
 
@@ -28,19 +31,33 @@ const debouncedSearch = debounce(search, 300)
28
31
 
29
32
  onBeforeMount(async () => {
30
33
  if(valueModel.value && valueModel.value.length > 0){
34
+
31
35
  if(multiple && Array.isArray(valueModel.value) ){
32
36
  items.value = valueModel.value
33
- //await findByIds(valueModel.value)
34
- }else{
35
- items.value = [valueModel.value]
36
- //await findByIds([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])
37
43
  }
38
44
 
45
+
46
+
39
47
  }
40
48
  })
41
49
 
42
- async function findByIds(ids: Array<string>) {
50
+ async function findByIds(ids: Array<string> = []) {
43
51
  try{
52
+ if(!entity){
53
+ throw new Error('Entity is required')
54
+ }
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');
60
+ }
44
61
  loading.value = true
45
62
  items.value = await entity.provider.findByIds(ids)
46
63
  }catch (e){
@@ -51,11 +68,12 @@ async function findByIds(ids: Array<string>) {
51
68
  }
52
69
 
53
70
 
54
-
55
-
56
71
  async function search(value: any) {
57
72
  try{
58
73
  loading.value = true
74
+ if(!entity){
75
+ throw new Error('Entity is required')
76
+ }
59
77
  if(!entity.provider.search){
60
78
  throw new Error('Provider does not have a search method')
61
79
  }
@@ -1,11 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import type {TOperation} from "../interfaces/TOperation";
3
3
  import type {PropType} from "vue";
4
- import EntityCrud from "../EntityCrud";
4
+ import type {IEntityCrud} from "@drax/crud-share";
5
5
  const dialog = defineModel({type: Boolean, default: false})
6
+ import {useI18n} from "vue-i18n";
7
+ const {t,te} = useI18n()
6
8
 
7
9
  defineProps({
8
- entity: {type: Object as PropType<EntityCrud>, required: true},
10
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
9
11
  operation: {type: String as PropType<TOperation>}
10
12
  })
11
13
 
@@ -19,7 +21,7 @@ defineEmits(
19
21
  <v-dialog v-model="dialog" :fullscreen="entity.dialogFullscreen">
20
22
  <v-card>
21
23
  <v-toolbar>
22
- <v-toolbar-title>{{entity.name}} {{$te('action.'+operation) ? $t('action.'+operation) : operation}}</v-toolbar-title>
24
+ <v-toolbar-title>{{entity.name}} {{te('action.'+operation) ? t('action.'+operation) : operation}}</v-toolbar-title>
23
25
  <v-spacer></v-spacer>
24
26
  <v-btn icon @click="dialog = false"><v-icon>mdi-close</v-icon></v-btn>
25
27
  </v-toolbar>
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ import {useCrud} from "../composables/UseCrud";
3
+ import type {PropType} from "vue";
4
+ import type {IEntityCrud} from "@drax/crud-share";
5
+ import {useI18n} from "vue-i18n";
6
+ const {t} = useI18n()
7
+ const {entity} = defineProps({
8
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
9
+ })
10
+
11
+ const {
12
+ exportFiles, exportListVisible, exportLoading
13
+ } = useCrud(entity);
14
+
15
+ </script>
16
+
17
+ <template>
18
+ <v-card
19
+ v-if="exportListVisible"
20
+ :loading="exportLoading"
21
+ class="ma-3" density="compact" variant="outlined" color="secondary"
22
+ >
23
+ <v-card-title>
24
+ {{ t('action.exports') }}
25
+
26
+ </v-card-title>
27
+ <v-card-text>
28
+ <v-table density="compact">
29
+ <thead>
30
+ <tr>
31
+ <th>Link</th><th>Rows</th><th>Time</th>
32
+ </tr>
33
+ </thead>
34
+ <tbody>
35
+ <tr v-for="exportFile in exportFiles">
36
+ <td><a :href="exportFile.url" target="_blank">{{ exportFile.url }}</a></td>
37
+ <td>{{ exportFile.rowCount }}</td>
38
+ <td>{{ exportFile.time }}</td>
39
+ </tr>
40
+ </tbody>
41
+ </v-table>
42
+
43
+ </v-card-text>
44
+
45
+ <v-card-actions>
46
+ <v-spacer></v-spacer>
47
+ <v-btn @click="exportFiles = []" :loading="exportLoading">{{ t('action.clear') }}</v-btn>
48
+ <v-btn @click="exportListVisible=false">{{ t('action.close') }}</v-btn>
49
+ </v-card-actions>
50
+ </v-card>
51
+ </template>
52
+
53
+ <style scoped>
54
+
55
+ </style>
@@ -1,16 +1,18 @@
1
1
  <script setup lang="ts">
2
2
  import type {PropType} from "vue";
3
3
  import {ref} from "vue";
4
- import EntityCrud from "../EntityCrud";
5
4
  import CrudFormField from "./CrudFormField.vue";
6
5
  import type {TOperation} from "../interfaces/TOperation";
7
-
8
-
6
+ import type {IEntityCrud} from "@drax/crud-share";
7
+ import {useI18n} from "vue-i18n";
8
+ import {useCrudStore} from "../stores/UseCrudStore";
9
+ const {t,te} = useI18n()
10
+ const store = useCrudStore()
9
11
  const valueModel = defineModel({type: [Object]})
10
12
 
11
13
 
12
14
  const {entity} = defineProps({
13
- entity: {type: Object as PropType<EntityCrud>, required: true},
15
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
14
16
  operation: {type: String as PropType<TOperation>, required: true},
15
17
  readonly: {type: Boolean, default: false},
16
18
  error: {type: String, required: false},
@@ -19,10 +21,13 @@ const {entity} = defineProps({
19
21
  const valid = ref()
20
22
  const formRef = ref()
21
23
 
22
- function submit() {
23
- formRef.value.validate()
24
+ async function submit() {
25
+ store.resetErrors()
26
+ await formRef.value.validate()
24
27
  if(valid.value) {
25
28
  emit('submit',valueModel.value)
29
+ }else{
30
+ console.log('Invalid form')
26
31
  }
27
32
  }
28
33
 
@@ -37,8 +42,11 @@ const emit = defineEmits(['submit', 'cancel'])
37
42
  <template>
38
43
  <v-form v-model="valid" ref="formRef" @submit.prevent >
39
44
  <v-card flat>
45
+
46
+ <v-card-subtitle v-if="valueModel._id">ID: {{valueModel._id}}</v-card-subtitle>
47
+
40
48
  <v-card-text v-if="error">
41
- <v-alert color="error">{{ $te(error) ? $t(error) : error }}</v-alert>
49
+ <v-alert color="error">{{ te(error) ? t(error) : error }}</v-alert>
42
50
  </v-card-text>
43
51
  <v-card-text>
44
52
  <template v-for="field in entity.fields" :key="field.name">
@@ -52,9 +60,9 @@ const emit = defineEmits(['submit', 'cancel'])
52
60
 
53
61
  <v-card-actions>
54
62
  <v-spacer></v-spacer>
55
- <v-btn variant="text" color="grey" @click="cancel">{{ $t('action.cancel') }}</v-btn>
63
+ <v-btn variant="text" color="grey" @click="cancel">{{ t('action.cancel') }}</v-btn>
56
64
  <v-btn variant="flat" color="primary" @click="submit">
57
- {{ operation ? $t('action.' + operation) : $t('action.sent') }}
65
+ {{ operation ? t('action.' + operation) : t('action.sent') }}
58
66
  </v-btn>
59
67
  </v-card-actions>
60
68
  </v-card>
@@ -1,26 +1,29 @@
1
1
  <script setup lang="ts">
2
2
  import {computed} from "vue";
3
3
  import type {PropType} from "vue";
4
- import type {ICrudField} from "../interfaces/IEntityCrud";
5
4
  import CrudFormList from "./CrudFormList.vue";
6
5
  import CrudAutocomplete from "./CrudAutocomplete.vue";
7
- import EntityCrud from "@/EntityCrud";
8
6
  import {useI18n} from "vue-i18n";
9
7
  import {useCrudStore} from "../stores/UseCrudStore";
10
8
  import {VDateInput} from 'vuetify/labs/VDateInput'
9
+ import type {IEntityCrud, IEntityCrudField} from "@drax/crud-share";
11
10
  const {t, te} = useI18n()
12
11
 
13
12
  const store = useCrudStore()
14
13
 
15
- const valueModel = defineModel({type: [String, Number, Boolean, Object, Array], default: false})
14
+ const valueModel = defineModel<any>({type: [String, Number, Boolean, Object, Array], default: false})
16
15
 
17
16
  const {index, entity, field} = defineProps({
18
- entity: {type: Object as PropType<EntityCrud>, required: true},
19
- field: {type: Object as PropType<ICrudField>, required: true},
17
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
18
+ field: {type: Object as PropType<IEntityCrudField|undefined>, required: true},
20
19
  readonly: {type: Boolean, default: false},
21
20
  index: {type: Number, default: 0},
22
21
  })
23
22
 
23
+ if(!field){
24
+ throw new Error("CrudFormField must be provided with a field object")
25
+ }
26
+
24
27
  const name = computed(() => index > 0 ? `${field.name}_${index}` : field.name)
25
28
 
26
29
  const label = computed(() => {
@@ -29,7 +32,7 @@ const label = computed(() => {
29
32
  })
30
33
 
31
34
  const rules = computed(() => {
32
- return entity.rule(field.name) as any
35
+ return entity.getRule(field.name) as any
33
36
  })
34
37
 
35
38
  const inputErrors = computed(() =>
@@ -41,7 +44,6 @@ const inputErrors = computed(() =>
41
44
  <template>
42
45
 
43
46
  <div v-if="field && field.type">
44
-
45
47
  <v-text-field
46
48
  v-if="field.type === 'string'"
47
49
  type="text"
@@ -93,7 +95,7 @@ const inputErrors = computed(() =>
93
95
 
94
96
  <crud-autocomplete
95
97
  v-if="field.type === 'ref'"
96
- :entity="entity.getRef(field.ref).instance"
98
+ :entity="entity.getRef(field.ref)"
97
99
  :field="field"
98
100
  v-model="valueModel"
99
101
  :label="label"
@@ -133,7 +135,7 @@ const inputErrors = computed(() =>
133
135
 
134
136
  <crud-autocomplete
135
137
  v-if="field.type === 'array.ref'"
136
- :entity="entity.getRef(field.ref).instance"
138
+ :entity="entity.getRef(field.ref)"
137
139
  :field="field"
138
140
  v-model="valueModel"
139
141
  :multiple="true"
@@ -1,14 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import type {PropType} from "vue";
3
- import type {ICrudField} from "../interfaces/IEntityCrud";
4
3
  import CrudFormField from "./CrudFormField.vue";
5
- import EntityCrud from "@/EntityCrud";
4
+ import type {IEntityCrud, IEntityCrudField} from "@drax/crud-share";
6
5
 
7
6
  const valueModel = defineModel({type: Array, default: () => []});
8
7
 
9
8
  const {field} = defineProps({
10
- entity: {type: Object as PropType<EntityCrud>, required: true},
11
- field: {type: Object as PropType<ICrudField>, required: true},
9
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
10
+ field: {type: Object as PropType<IEntityCrudField>, required: true},
12
11
  readonly: {type: Boolean, default: false},
13
12
  })
14
13
 
@@ -16,7 +15,7 @@ function newItem() {
16
15
  return field.objectFields ? field.objectFields.reduce((acc, field) => ({...acc, [field.name]: field.default }), {}) : []
17
16
  }
18
17
 
19
- function getField(key: string):ICrudField|undefined {
18
+ function getField(key: string):IEntityCrudField|undefined {
20
19
  return field.objectFields ? field.objectFields.find(field => field.name === key) : undefined;
21
20
  }
22
21
 
@@ -43,12 +42,12 @@ function removeItem(index: number) {
43
42
  <v-col cols="12" v-for="(item,index) in valueModel" :key="index" class="text-right">
44
43
  <v-row dense align="center">
45
44
  <v-col cols="11">
46
- <template v-for="key in Object.keys(item)" :key="key">
45
+ <template v-for="key in Object.keys(item as Record<string, any>)" :key="key">
47
46
  <crud-form-field
48
47
  v-if="hasField(key)"
49
48
  :entity="entity"
50
49
  :field="getField(key)"
51
- v-model="valueModel[index][key]"
50
+ v-model="(valueModel[index] as any)[key]"
52
51
  :readonly="readonly"
53
52
  :index="index"
54
53
  />
@@ -1,24 +1,37 @@
1
1
  <script setup lang="ts">
2
2
  import type {PropType} from 'vue'
3
3
  import {useAuth} from '@drax/identity-vue'
4
- import EntityCrud from "../EntityCrud";
5
4
  import CrudSearch from "./CrudSearch.vue";
6
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 CrudExportList from "./CrudExportList.vue";
12
+ import type {IEntityCrud} from "@drax/crud-share";
7
13
  import {useI18n} from "vue-i18n";
8
- const {t, te} = useI18n()
14
+ import type {IEntityCrudHeader} from "@drax/crud-share";
15
+
16
+ const {t,te} = useI18n()
9
17
  const {hasPermission} = useAuth()
10
18
 
11
19
  const {entity} = defineProps({
12
- entity: {type: Object as PropType<EntityCrud>, required: true},
20
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
13
21
  })
14
22
 
15
- const {loading, itemsPerPage, page, sortBy, search, totalItems, items,
16
- loadItems} = useCrud(entity)
23
+ const {
24
+ loading, itemsPerPage, page, sortBy, search, totalItems, items,
25
+ loadItems
26
+ } = useCrud(entity)
17
27
 
18
- const actions = [{title: t('action.actions'),key:'actions', sortable: false, align: 'right'}]
19
- const tHeaders = entity.headers.map(header => ({...header, title: t(`${entity.name}.fields.${header.title}`)}))
28
+ const actions: IEntityCrudHeader[] = [{title: t('action.actions'), key: 'actions', sortable: false, align: 'end', minWidth: '140px'}]
29
+ const tHeaders: IEntityCrudHeader[] = entity.headers.map(header => ({
30
+ ...header,
31
+ title: te(`${entity.name.toLowerCase()}.fields.${header.title}`) ? t(`${entity.name.toLowerCase()}.fields.${header.title}`) : header.title
32
+ }))
20
33
 
21
- const headers = [...tHeaders, ...actions]
34
+ const headers: IEntityCrudHeader[] = [...tHeaders, ...actions]
22
35
 
23
36
 
24
37
  defineExpose({
@@ -29,29 +42,46 @@ defineExpose({
29
42
 
30
43
  <template>
31
44
  <v-data-table-server
32
- class="border"
33
- v-if="hasPermission(entity.permissions.view)"
34
- v-model:items-per-page="itemsPerPage"
35
- :items-per-page-options="[5, 10, 20, 50]"
36
- v-model:page="page"
37
- v-model:sort-by="sortBy"
38
- :headers="headers"
39
- :items="items"
40
- :items-length="totalItems"
41
- :loading="loading"
42
- :search="search"
43
- :multi-sort="false"
44
- item-value="name"
45
- @update:options="loadItems"
45
+ class="border"
46
+ v-if="hasPermission(entity.permissions.view)"
47
+ v-model:items-per-page="itemsPerPage"
48
+ :items-per-page-options="[5, 10, 20, 50]"
49
+ v-model:page="page"
50
+ v-model:sort-by="sortBy"
51
+ :headers="headers"
52
+ :items="items"
53
+ :items-length="totalItems"
54
+ :loading="loading"
55
+ :search="search"
56
+ :multi-sort="false"
57
+ item-value="name"
58
+ @update:options="loadItems"
46
59
  >
47
60
  <template v-slot:top>
48
- <v-toolbar density="compact" >
61
+ <v-toolbar density="compact">
49
62
  <v-toolbar-title>{{ entity.name }}</v-toolbar-title>
50
63
  <v-spacer></v-spacer>
51
- <v-btn v-if="entity.isCreatable" icon="mdi-plus" class="mr-1" variant="text" color="primary" @click="$emit('create')">
52
- </v-btn>
64
+
65
+ <crud-import-button
66
+ :entity="entity"
67
+ @import="v => $emit('import', v)"
68
+ />
69
+
70
+ <crud-export-button
71
+ :entity="entity"
72
+ @export="v => $emit('export',v)"
73
+ />
74
+
75
+ <crud-create-button
76
+ v-if="entity.isCreatable"
77
+ :entity="entity"
78
+ @click="$emit('create')"
79
+ />
80
+
53
81
  </v-toolbar>
54
82
 
83
+ <crud-export-list :entity="entity"></crud-export-list>
84
+
55
85
  <v-card>
56
86
  <v-card-text>
57
87
  <crud-search v-model="search"></crud-search>
@@ -62,21 +92,21 @@ defineExpose({
62
92
 
63
93
 
64
94
  <template v-for="header in entity.headers" :key="header.key" v-slot:[`item.${header.key}`]="{item, value}">
65
- <slot :name="`item.${header.key}`" v-bind="{item, value}" >
66
- {{value}}
67
- </slot>
95
+ <slot :name="`item.${header.key}`" v-bind="{item, value}">
96
+ {{ value }}
97
+ </slot>
68
98
  </template>
69
99
 
70
100
 
71
101
  <template v-slot:item.actions="{item}">
72
- <v-btn v-if="entity.isEditable && hasPermission(entity.permissions.update)"
73
- icon="mdi-pencil" variant="text" color="primary"
74
- @click="$emit('edit', item)">
75
- </v-btn>
76
- <v-btn v-if="entity.isDeletable && hasPermission(entity.permissions.delete)"
77
- icon="mdi-delete" class="mr-1" variant="text" color="red"
78
- @click="$emit('delete', item)">
79
- </v-btn>
102
+ <crud-update-button
103
+ v-if="entity.isEditable && hasPermission(entity.permissions.update)"
104
+ @click="$emit('edit', item)"
105
+ />
106
+ <crud-delete-button
107
+ v-if="entity.isDeletable && hasPermission(entity.permissions.delete)"
108
+ @click="$emit('delete', item)"
109
+ />
80
110
  </template>
81
111
 
82
112
  </v-data-table-server>
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import {useI18n} from "vue-i18n";
3
+ const {t} = useI18n()
2
4
  const model = defineModel<any>()
3
5
  </script>
4
6
 
@@ -7,7 +9,7 @@ const model = defineModel<any>()
7
9
  density="compact" class="mr-2"
8
10
  variant="outlined"
9
11
  append-inner-icon="mdi-magnify"
10
- :label="$t('action.search')"
12
+ :label="t('action.search')"
11
13
  single-line clearable @click:clear="() => model = ''"
12
14
  />
13
15
  </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import {useI18n} from "vue-i18n";
3
+
4
+ const {t} = useI18n()
5
+ </script>
6
+
7
+ <template>
8
+ <v-tooltip location="top">
9
+ <template v-slot:activator="{ props }">
10
+ <v-btn
11
+ v-bind="{ ...$attrs, ...props }"
12
+ icon="mdi-plus"
13
+ class="mr-1"
14
+ variant="text"
15
+ color="primary"
16
+ >
17
+ </v-btn>
18
+ </template>
19
+ {{ t('action.create')}}
20
+ </v-tooltip>
21
+ </template>
22
+
23
+ <style scoped>
24
+
25
+ </style>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import {useI18n} from "vue-i18n";
3
+
4
+ const {t} = useI18n()
5
+ </script>
6
+
7
+ <template>
8
+ <v-tooltip location="top">
9
+ <template v-slot:activator="{ props}">
10
+ <v-btn
11
+ v-bind="{ ...$attrs, ...props }"
12
+ icon="mdi-delete"
13
+ class="mr-1"
14
+ variant="text"
15
+ color="red"
16
+ >
17
+ </v-btn>
18
+ </template>
19
+ {{ t('action.delete')}}
20
+ </v-tooltip>
21
+ </template>
22
+
23
+ <style scoped>
24
+
25
+ </style>
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import type {PropType} from "vue";
3
+ import type {IEntityCrud} from "@drax/crud-share"
4
+ import {useCrud} from "../../composables/UseCrud";
5
+ import {useI18n} from "vue-i18n";
6
+
7
+ const {t} = useI18n()
8
+
9
+ const {entity} = defineProps({
10
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
11
+ })
12
+
13
+ const {
14
+ exportLoading
15
+ } = useCrud(entity)
16
+ </script>
17
+
18
+ <template>
19
+ <v-menu v-if="entity.isExportable">
20
+ <template v-slot:activator="{ props: mp }">
21
+ <v-tooltip location="top">
22
+ <template v-slot:activator="{ props: tp }">
23
+ <v-btn
24
+ v-bind="{...mp, ...tp}"
25
+ :disabled="exportLoading"
26
+ class="mr-1"
27
+ color="teal"
28
+ variant="text"
29
+ :loading="exportLoading"
30
+ icon="mdi-database-export-outline"
31
+ ></v-btn>
32
+ </template>
33
+ {{ t('action.export')}}
34
+ </v-tooltip>
35
+
36
+ </template>
37
+ <v-list>
38
+ <v-list-item v-for="format in entity.exportFormats" @click="$emit('export', format)">
39
+ <v-list-item-title>{{format}}</v-list-item-title>
40
+ </v-list-item>
41
+ </v-list>
42
+
43
+ </v-menu>
44
+ </template>
45
+
46
+ <style scoped>
47
+
48
+ </style>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import type {PropType} from "vue";
3
+ import type {IEntityCrud} from "@drax/crud-share"
4
+ import {useCrud} from "../../composables/UseCrud";
5
+ import {useI18n} from "vue-i18n";
6
+
7
+ const {t} = useI18n()
8
+ const {entity} = defineProps({
9
+ entity: {type: Object as PropType<IEntityCrud>, required: true},
10
+ })
11
+
12
+ const {
13
+ exportLoading
14
+ } = useCrud(entity)
15
+ </script>
16
+
17
+ <template>
18
+ <div v-if="entity.isImportable">
19
+ <v-tooltip location="top">
20
+ <template v-slot:activator="{ props }">
21
+ <v-btn
22
+ v-bind="props"
23
+ :disabled="exportLoading"
24
+ class="mr-1"
25
+ color="purple"
26
+ variant="text"
27
+ :loading="exportLoading"
28
+ icon="mdi-database-import-outline"
29
+ ></v-btn>
30
+ </template>
31
+ {{ t('action.import')}}
32
+ </v-tooltip>
33
+
34
+ </div>
35
+ </template>
36
+
37
+ <style scoped>
38
+
39
+ </style>
@@ -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-pencil"
12
+ class="mr-1"
13
+ variant="text"
14
+ color="primary"
15
+ slim
16
+ >
17
+ </v-btn>
18
+ </template>
19
+ {{ t('action.update')}}
20
+ </v-tooltip>
21
+ </template>
22
+
23
+ <style scoped>
24
+
25
+ </style>
@@ -1,14 +1,12 @@
1
- import EntityCrud from "../EntityCrud";
2
- import type {IDraxPaginateResult} from "@drax/common-share";
1
+ import type {IDraxPaginateResult, IEntityCrud} from "@drax/crud-share";
3
2
  import {useCrudStore} from "../stores/UseCrudStore";
4
3
  import {computed} from "vue";
5
- import type {ICrudField} from "@/interfaces/IEntityCrud";
6
4
 
7
- export function useCrud(entity: EntityCrud) {
5
+ export function useCrud(entity: IEntityCrud) {
8
6
 
9
7
  const store = useCrudStore()
10
8
 
11
- async function loadItems() {
9
+ async function doPaginate() {
12
10
  store.setLoading(true)
13
11
  try {
14
12
  const r: IDraxPaginateResult<any> = await entity?.provider.paginate({
@@ -27,25 +25,68 @@ export function useCrud(entity: EntityCrud) {
27
25
  }
28
26
  }
29
27
 
30
- function cast(item: any){
28
+ async function doExport(format: 'JSON') {
29
+ store.setExportLoading(true)
30
+ store.setExportListVisible(true)
31
+ try {
31
32
 
32
- entity.fields.filter(field => field.type === 'date')
33
- .forEach(field => {
34
- if(field.type === 'date'){
35
- item[field.name] = new Date(item[field.name])
36
- }
37
- })
33
+ if(!entity?.provider.export) {
34
+ throw new Error("provider.export not implemented")
35
+ }
38
36
 
39
- return item
37
+ const headers: string = entity.exportHeaders.join(',')
38
+
39
+ const r: any = await entity?.provider.export({
40
+ format: format,
41
+ headers: headers,
42
+ separator: ";",
43
+ orderBy: store.sortBy[0]?.key,
44
+ order: store.sortBy[0]?.order,
45
+ search: store.search
46
+ })
47
+
48
+ if(r && r.url) {
49
+ store.addExportFile(r)
50
+ store.showMessage("Export successful")
51
+ }
52
+
53
+ return r
54
+
55
+ } catch (e) {
56
+ console.error("Error exporting csv", e)
57
+ } finally {
58
+ store.setExportLoading(false)
59
+ }
40
60
  }
41
61
 
42
62
 
63
+
64
+
43
65
  function onCreate() {
44
66
  store.setOperation("create")
45
67
  store.setForm(entity.form)
46
68
  store.setDialog(true)
47
69
  }
48
70
 
71
+ function cast(item: any){
72
+ entity.fields.filter(field => field.type === 'date')
73
+ .forEach(field => {
74
+ item[field.name] = new Date(item[field.name])
75
+ })
76
+
77
+ entity.fields.filter(field => field.type === 'ref')
78
+ .forEach(field => {
79
+ item[field.name] = item[field.name]._id
80
+ })
81
+
82
+ entity.fields.filter(field => field.type === 'array.ref')
83
+ .forEach(field => {
84
+ item[field.name] = item[field.name].map(((i:any) => i._id))
85
+ })
86
+
87
+ return item
88
+ }
89
+
49
90
  function onEdit(item: object) {
50
91
  store.setOperation("edit")
51
92
  store.setForm(cast({...item}))
@@ -65,7 +106,6 @@ export function useCrud(entity: EntityCrud) {
65
106
  }
66
107
 
67
108
  function onSubmit(formData: any) {
68
- console.log("formData", formData)
69
109
  store.setInputErrors(null)
70
110
  switch (store.operation) {
71
111
  case "create":
@@ -83,7 +123,7 @@ export function useCrud(entity: EntityCrud) {
83
123
  async function doCreate(formData: any) {
84
124
  try {
85
125
  await entity?.provider.create(formData)
86
- await loadItems()
126
+ await doPaginate()
87
127
  store.setDialog(false)
88
128
  store.showMessage("Entity created successfully!")
89
129
  } catch (e: any) {
@@ -99,7 +139,7 @@ export function useCrud(entity: EntityCrud) {
99
139
  async function doUpdate(formData: any) {
100
140
  try {
101
141
  await entity?.provider.update(formData._id, formData)
102
- await loadItems()
142
+ await doPaginate()
103
143
  store.setDialog(false)
104
144
  store.showMessage("Entity updated successfully!")
105
145
  } catch (e: any) {
@@ -116,7 +156,7 @@ export function useCrud(entity: EntityCrud) {
116
156
  async function doDelete(formData: any) {
117
157
  try {
118
158
  await entity?.provider.delete(formData._id)
119
- await loadItems()
159
+ await doPaginate()
120
160
  store.setDialog(false)
121
161
  store.showMessage("Entity deleted successfully!")
122
162
  } catch (e: any) {
@@ -126,6 +166,10 @@ export function useCrud(entity: EntityCrud) {
126
166
 
127
167
  }
128
168
 
169
+ function resetCrudStore(){
170
+ store.$reset()
171
+ }
172
+
129
173
  const dialog = computed({get(){return store.dialog} , set(value){store.setDialog(value)}})
130
174
  const operation = computed({get(){return store.operation} , set(value){store.setOperation(value)}})
131
175
  const form = computed({get(){return store.form} , set(value){store.setForm(value)}})
@@ -140,11 +184,15 @@ export function useCrud(entity: EntityCrud) {
140
184
  const search = computed({get(){return store.search} , set(value){store.setSearch(value)}})
141
185
  const totalItems = computed({get(){return store.totalItems} , set(value){store.setTotalItems(value)}})
142
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)}})
143
190
 
144
191
  return {
145
- loadItems, onCreate, onEdit, onDelete, onCancel, onSubmit,
146
- operation, dialog, form, notify, error, message,
147
- loading, itemsPerPage, page, sortBy, search, totalItems, items
192
+ loadItems: doPaginate, doExport, onCreate, onEdit, onDelete, onCancel, onSubmit,resetCrudStore,
193
+ operation, dialog, form, notify, error, message, formValid,
194
+ loading, itemsPerPage, page, sortBy, search, totalItems, items,
195
+ exportFiles,exportLoading,exportListVisible
148
196
  }
149
197
 
150
198
  }
package/src/index.ts CHANGED
@@ -10,8 +10,6 @@ import {useCrudStore} from "./stores/UseCrudStore";
10
10
  import {useCrud} from "./composables/UseCrud";
11
11
  import {EntityCrud} from "./EntityCrud";
12
12
 
13
- import type {IFields, ICrudForm, ICrudHeaders, ICrudPermissions, ICrudRules} from "./interfaces/IEntityCrud";
14
- export type {IFields, ICrudForm, ICrudHeaders, ICrudPermissions, ICrudRules}
15
13
 
16
14
  export {
17
15
  Crud,
@@ -9,8 +9,8 @@ export const useCrudStore = defineStore('CrudStore', {
9
9
  form: {} as any,
10
10
  formValid: {} as any,
11
11
  notify: false as boolean,
12
- error: '' as string,
13
12
  message: '' as string,
13
+ error: '' as string,
14
14
  items: [] as any[],
15
15
  totalItems: 0 as number,
16
16
  itemsPerPage: 5 as number,
@@ -18,7 +18,10 @@ export const useCrudStore = defineStore('CrudStore', {
18
18
  search: '' as string,
19
19
  sortBy: [] as any[],
20
20
  loading: false,
21
- inputErrors: null
21
+ inputErrors: null,
22
+ exportLoading: false,
23
+ exportFiles: [] as string[],
24
+ exportListVisible: false,
22
25
  }
23
26
  ),
24
27
  getters:{
@@ -80,6 +83,22 @@ export const useCrudStore = defineStore('CrudStore', {
80
83
  },
81
84
  setInputErrors(inputErrors: any) {
82
85
  this.inputErrors = inputErrors
86
+ },
87
+ resetErrors(){
88
+ this.inputErrors = null
89
+ this.error = ''
90
+ },
91
+ setExportFiles(exportFiles: string[]) {
92
+ this.exportFiles = exportFiles
93
+ },
94
+ addExportFile(exportFile: string) {
95
+ this.exportFiles.push(exportFile)
96
+ },
97
+ setExportLoading(exportLoading: boolean) {
98
+ this.exportLoading = exportLoading
99
+ },
100
+ setExportListVisible(exportListVisible: boolean) {
101
+ this.exportListVisible = exportListVisible
83
102
  }
84
103
  }
85
104
 
@@ -1,33 +0,0 @@
1
- interface ICrudHeaders {
2
- title: string
3
- key: string
4
- }
5
-
6
- interface ICrudRules {
7
- [key: string]: Array<Function>
8
- }
9
-
10
- interface ICrudField {
11
- name: string
12
- type: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'ref' | 'array.string' | 'array.number' | 'array.object' | 'array.ref'
13
- ref?: string
14
- objectFields?: ICrudField[]
15
- label: string,
16
- default: any
17
- }
18
-
19
- interface ICrudForm {
20
- [key: string]: string | number | boolean | Date | null
21
- }
22
-
23
- type IFields = ICrudField[]
24
-
25
- interface ICrudPermissions {
26
- manage: string
27
- view: string
28
- create: string
29
- update: string
30
- delete: string
31
- }
32
-
33
- export type {ICrudHeaders, ICrudRules, ICrudField, IFields, ICrudForm, ICrudPermissions}