@ramathibodi/nuxt-commons 0.0.10 → 0.1.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.
Files changed (30) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +14 -9
  3. package/dist/runtime/components/ExportCSV.vue +4 -4
  4. package/dist/runtime/components/FileBtn.vue +10 -4
  5. package/dist/runtime/components/ImportCSV.vue +7 -4
  6. package/dist/runtime/components/document/TemplateBuilder.vue +217 -0
  7. package/dist/runtime/components/form/Dialog.vue +138 -0
  8. package/dist/runtime/components/form/Hidden.vue +32 -0
  9. package/dist/runtime/components/form/Pad.vue +18 -6
  10. package/dist/runtime/components/form/Table.vue +264 -0
  11. package/dist/runtime/components/master/Autocomplete.vue +159 -0
  12. package/dist/runtime/composables/api.d.ts +9 -0
  13. package/dist/runtime/composables/api.mjs +64 -0
  14. package/dist/runtime/composables/document/template.d.ts +2 -0
  15. package/dist/runtime/composables/document/template.mjs +104 -0
  16. package/dist/runtime/composables/graphql.d.ts +17 -0
  17. package/dist/runtime/composables/graphql.mjs +61 -0
  18. package/dist/runtime/composables/menu.d.ts +19 -0
  19. package/dist/runtime/composables/menu.mjs +60 -0
  20. package/dist/runtime/composables/utils/fuzzy.d.ts +2 -0
  21. package/dist/runtime/composables/utils/fuzzy.mjs +19 -0
  22. package/dist/runtime/composables/utils/validation.d.ts +2 -2
  23. package/dist/runtime/composables/utils/validation.mjs +2 -2
  24. package/dist/runtime/labs/form/TextFieldMask.vue +5 -5
  25. package/dist/runtime/plugins/permission.d.ts +2 -0
  26. package/dist/runtime/plugins/permission.mjs +23 -0
  27. package/dist/runtime/types/menu.d.ts +15 -0
  28. package/package.json +6 -3
  29. /package/dist/runtime/components/{Pdf → pdf}/Print.vue +0 -0
  30. /package/dist/runtime/components/{Pdf → pdf}/View.vue +0 -0
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.0.10"
7
+ "version": "0.1.0"
8
8
  }
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, addTypeTemplate } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, addPlugin, addTypeTemplate } from '@nuxt/kit';
2
2
 
3
3
  const module = defineNuxtModule({
4
4
  meta: {
@@ -18,15 +18,20 @@ const module = defineNuxtModule({
18
18
  global: true
19
19
  });
20
20
  addImportsDir(resolver.resolve("runtime/composables/**"));
21
+ addPlugin({
22
+ src: resolver.resolve("runtime/plugins/permission")
23
+ });
24
+ addTypeTemplate({
25
+ src: resolver.resolve("runtime/types/modules.d.ts"),
26
+ filename: "types/modules.d.ts"
27
+ });
28
+ addTypeTemplate({
29
+ src: resolver.resolve("runtime/types/alert.d.ts"),
30
+ filename: "types/alert.d.ts"
31
+ });
21
32
  addTypeTemplate({
22
- filename: "types/modules.d.ts",
23
- getContents: () => `
24
- declare module 'painterro';
25
- declare module "@zxing/browser"
26
- declare module "vue-signature-pad"
27
- declare module "accounting"
28
- declare module "pdf-vue3"
29
- `
33
+ src: resolver.resolve("runtime/types/menu.d.ts"),
34
+ filename: "types/menu.d.ts"
30
35
  });
31
36
  }
32
37
  });
@@ -42,13 +42,13 @@ function exportFile() {
42
42
  @click="exportFile"
43
43
  >
44
44
  <template
45
- v-for="(_, slot, index) in ($slots as {})"
45
+ v-for="(_, name, index) in ($slots as {})"
46
46
  :key="index"
47
- #[slot]="scope"
47
+ #[name]="slotData"
48
48
  >
49
49
  <slot
50
- :name="slot"
51
- v-bind="{ scope }"
50
+ :name="name"
51
+ v-bind="(slotData as object)"
52
52
  />
53
53
  </template>
54
54
  </VBtn>
@@ -25,9 +25,15 @@ const openFileInput = () => {
25
25
  fileInput.value?.click()
26
26
  }
27
27
 
28
+ const reset = () => {
29
+ files.value = undefined
30
+ }
31
+
28
32
  watch(files, () => {
29
33
  emit('update:modelValue', files.value)
30
34
  }, { deep: true })
35
+
36
+ defineExpose({ reset })
31
37
  </script>
32
38
 
33
39
  <template>
@@ -36,13 +42,13 @@ watch(files, () => {
36
42
  @click="openFileInput"
37
43
  >
38
44
  <template
39
- v-for="(_, slot, index) in ($slots as {})"
45
+ v-for="(_, name, index) in ($slots as {})"
40
46
  :key="index"
41
- #[slot]="scope"
47
+ #[name]="slotData"
42
48
  >
43
49
  <slot
44
- :name="slot"
45
- v-bind="{ scope }"
50
+ :name="name"
51
+ v-bind="(slotData as object)"
46
52
  />
47
53
  </template>
48
54
  </v-btn>
@@ -9,6 +9,7 @@ const emit = defineEmits<{
9
9
  }>()
10
10
 
11
11
  const loading = ref(false)
12
+ const fileBtnRef = ref()
12
13
 
13
14
  function uploadedFile(files: File[] | File | undefined) {
14
15
  if (!files) {
@@ -34,6 +35,7 @@ function uploadedFile(files: File[] | File | undefined) {
34
35
  const workbook = XLSX.read(e.target?.result)
35
36
  emit('import', XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]))
36
37
  loading.value = false
38
+ fileBtnRef.value.reset()
37
39
  }
38
40
  loading.value = true
39
41
  reader.readAsArrayBuffer(files)
@@ -42,6 +44,7 @@ function uploadedFile(files: File[] | File | undefined) {
42
44
 
43
45
  <template>
44
46
  <FileBtn
47
+ ref="fileBtnRef"
45
48
  v-bind="$attrs"
46
49
  color="primary"
47
50
  :loading="loading"
@@ -51,13 +54,13 @@ function uploadedFile(files: File[] | File | undefined) {
51
54
  @update:model-value="uploadedFile"
52
55
  >
53
56
  <template
54
- v-for="(_, slot, index) in ($slots as {})"
57
+ v-for="(_, name, index) in ($slots as {})"
55
58
  :key="index"
56
- #[slot]="scope"
59
+ #[name]="slotData"
57
60
  >
58
61
  <slot
59
- :name="slot"
60
- v-bind="{ scope }"
62
+ :name="name"
63
+ v-bind="(slotData as object)"
61
64
  />
62
65
  </template>
63
66
  </FileBtn>
@@ -0,0 +1,217 @@
1
+ <script lang="ts" setup>
2
+ import {computed, ref, watch} from 'vue'
3
+ import * as prettier from 'prettier'
4
+ import prettierPluginHtml from 'prettier/plugins/html'
5
+ import {useDocumentTemplate, validationRulesRegex} from '../../composables/document/template'
6
+
7
+ interface Props {
8
+ modelValue?: string
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+
13
+ })
14
+
15
+ const emit = defineEmits(['update:modelValue'])
16
+
17
+ const isAdvanceMode = computed(() => {
18
+ return props.modelValue && !isValidJsonArrayOfObjects(props.modelValue)
19
+ })
20
+
21
+ const templateItems = ref<Record<string, any>>([])
22
+ const templateAdvanceCode = ref<string | undefined>()
23
+
24
+ const headers = ref([
25
+ { title: '',
26
+ key: 'operation',
27
+ width: '100px',
28
+ },
29
+ {
30
+ title: 'Input Type',
31
+ key: 'inputType',
32
+ },
33
+ {
34
+ title: 'Width',
35
+ key: 'width',
36
+ },
37
+ {
38
+ title: 'Input Label',
39
+ key: 'inputLabel',
40
+ },
41
+ {
42
+ title: 'Variable Name',
43
+ key: 'variableName',
44
+ },
45
+ {
46
+ title: 'Configuration',
47
+ key: 'configuration',
48
+ },
49
+ {
50
+ title: 'Action',
51
+ key: 'action',
52
+ width: '120px',
53
+ },
54
+ ])
55
+
56
+ function isValidJsonArrayOfObjects(str: string | undefined) {
57
+ try {
58
+ const parsed = JSON.parse(str as string)
59
+ return Array.isArray(parsed) && parsed.every(item => typeof item === 'object' && item !== null && !Array.isArray(item))
60
+ }
61
+ catch (e) {
62
+ return false
63
+ }
64
+ }
65
+
66
+ watch(templateItems, (newValue) => {
67
+ if (!isAdvanceMode.value) emit('update:modelValue', JSON.stringify(newValue))
68
+ }, { deep: true })
69
+
70
+ watch(templateAdvanceCode, (newValue) => {
71
+ if (isAdvanceMode.value) emit('update:modelValue', newValue)
72
+ })
73
+
74
+ watch(() => props.modelValue, (newValue) => {
75
+ if (isValidJsonArrayOfObjects(newValue)) templateItems.value = JSON.parse(newValue as string)
76
+ else templateAdvanceCode.value = newValue
77
+ }, { deep: true, immediate: true })
78
+
79
+ async function convertToAdvanceMode() {
80
+ if (!isAdvanceMode.value) {
81
+ emit('update:modelValue', await prettier.format(useDocumentTemplate(templateItems.value).replaceAll('>', '>\n'), { parser: 'html', plugins: [prettierPluginHtml] }))
82
+ }
83
+ }
84
+
85
+ const inputTypeChoice = ref(['VTextField', 'VTextarea', 'VSelect','VAutocomplete','FormDate','FormTime','FormDateTime','VCombobox', 'VRadio', 'VRadioInline', 'VCheckbox','VSwitch', 'MasterAutocomplete', 'Header', 'Separator', 'CustomCode'])
86
+ const requireOption = ref(['VSelect','VAutocomplete','VCombobox', 'VRadio', 'VRadioInline', 'MasterAutocomplete'])
87
+ const notRequireVariable = ref(['Header', 'Separator', 'CustomCode'])
88
+ const notRequireLabel = ref(['Separator', 'CustomCode'])
89
+
90
+ const choiceOption = ref(['VSelect', 'VRadio', 'VRadioInline'])
91
+
92
+ const ruleOptions = (inputType: string) => (value: any) => {
93
+ if (choiceOption.value.includes(inputType) && !(/^[^'",]+(,[^'",]+)*$/.test(value))) return 'Invalid options format'
94
+
95
+ return true
96
+ }
97
+ </script>
98
+
99
+ <template>
100
+ <FormTable
101
+ v-if="!isAdvanceMode"
102
+ v-model="templateItems"
103
+ :headers="headers"
104
+ title="Document Template"
105
+ >
106
+ <template #toolbarItems>
107
+ <VBtn
108
+ color="primary"
109
+ variant="flat"
110
+ @click="convertToAdvanceMode()"
111
+ >
112
+ Convert To Advance
113
+ </VBtn>
114
+ </template>
115
+ <template #form="{ data, rules }">
116
+ <v-container fluid>
117
+ <v-row dense>
118
+ <v-col cols="3">
119
+ <v-combobox
120
+ v-model="data.inputType"
121
+ label="Input Type"
122
+ :items="inputTypeChoice"
123
+ :rules="[rules.require()]"
124
+ />
125
+ </v-col>
126
+ <v-col
127
+ v-if="data.inputType!='Separator'"
128
+ cols="1"
129
+ >
130
+ <v-text-field
131
+ v-model="data.width"
132
+ label="Width"
133
+ :rules="[rules.require()]"
134
+ type="number"
135
+ />
136
+ </v-col>
137
+ <v-col cols="4">
138
+ <v-text-field
139
+ v-if="!notRequireLabel.includes(data.inputType)"
140
+ v-model="data.inputLabel"
141
+ label="Input Label"
142
+ :rules="[rules.require()]"
143
+ />
144
+ </v-col>
145
+ <v-col cols="4">
146
+ <v-text-field
147
+ v-if="!notRequireVariable.includes(data.inputType)"
148
+ v-model="data.variableName"
149
+ label="Variable Name"
150
+ :rules="[rules.require()]"
151
+ />
152
+ </v-col>
153
+ <v-col
154
+ v-if="data.inputType!='CustomCode'"
155
+ cols="12"
156
+ >
157
+ <v-textarea
158
+ v-model="data.inputOptions"
159
+ label="Input Options"
160
+ auto-grow
161
+ :rules="[rules.requireIf(requireOption.includes(data.inputType)), ruleOptions(data.inputType)]"
162
+ />
163
+ </v-col>
164
+ <v-col v-if="data.inputType!='CustomCode'">
165
+ <v-text-field
166
+ v-model="data.validationRules"
167
+ label="Validation Rules"
168
+ :rules="[rules.regex(validationRulesRegex)]"
169
+ />
170
+ </v-col>
171
+ <v-col v-if="data.inputType!='CustomCode'">
172
+ <v-text-field
173
+ v-model="data.inputAttributes"
174
+ label="Input Attributes"
175
+ />
176
+ </v-col>
177
+ <v-col v-if="data.inputType!='Separator'">
178
+ <v-text-field
179
+ v-model="data.columnAttributes"
180
+ label="Column Attributes"
181
+ />
182
+ </v-col>
183
+ <v-col
184
+ v-if="data.inputType=='CustomCode'"
185
+ cols="12"
186
+ >
187
+ <v-textarea
188
+ v-model="data.inputCustomCode"
189
+ label="Custom Code"
190
+ auto-grow
191
+ :rules="[rules.require()]"
192
+ />
193
+ </v-col>
194
+ </v-row>
195
+ </v-container>
196
+ </template>
197
+ <template #item.configuration="props">
198
+ <template v-if="props.item.inputType=='CustomCode'">
199
+ <b>Custom Code :</b><br>
200
+ <span style="white-space: pre-line">{{ props.item.inputCustomCode }}</span>
201
+ </template>
202
+ <template v-if="props.item.validationRules">
203
+ <b>Validation Rules :</b> {{ props.item.validationRules }}<br>
204
+ </template>
205
+ <template v-if="props.item.inputOptions">
206
+ <b>Input Options :</b> {{ props.item.inputOptions }}<br>
207
+ </template>
208
+ <template v-if="props.item.inputAttributes">
209
+ <b>Input Attributes :</b> {{ props.item.inputAttributes }}<br>
210
+ </template>
211
+ </template>
212
+ </FormTable>
213
+ <v-textarea
214
+ v-else
215
+ v-model="templateAdvanceCode"
216
+ />
217
+ </template>
@@ -0,0 +1,138 @@
1
+ <script lang="ts" setup>
2
+ import { ref, defineModel, computed, watch, watchEffect } from 'vue'
3
+ import { cloneDeep, isEqual } from 'lodash-es'
4
+
5
+ export interface FormDialogCallback {
6
+ done: Function
7
+ error: Function
8
+ }
9
+
10
+ interface Props {
11
+ maxWidth?: number | string
12
+ fullscreen?: boolean
13
+ title?: string
14
+ initialData?: object
15
+ formData?: object
16
+ saveCaption?: string
17
+ cancelCaption?: string
18
+ }
19
+
20
+ const props = withDefaults(defineProps<Props>(), {
21
+ saveCaption: 'บันทึก',
22
+ cancelCaption: 'ยกเลิก',
23
+ })
24
+
25
+ const isShowing = defineModel<boolean>({ default: false })
26
+ const isSaving = ref<boolean>(false)
27
+ const formPadRef = ref()
28
+ const formData = ref<object>({})
29
+
30
+ const emit = defineEmits(['create', 'update'])
31
+
32
+ function save() {
33
+ if (formPadRef.value.isValid) {
34
+ isSaving.value = true
35
+ emit((isCreating.value) ? 'create' : 'update', formData.value, callback)
36
+ }
37
+ }
38
+
39
+ function cancel() {
40
+ isShowing.value = false
41
+ }
42
+
43
+ const callback: FormDialogCallback = {
44
+ done: function () {
45
+ isSaving.value = false
46
+ isShowing.value = false
47
+ },
48
+ error: function () {
49
+ isSaving.value = false
50
+ },
51
+ }
52
+
53
+ const isDataChange = computed(() => {
54
+ return !((isCreating.value) ? isEqual(formData.value, createOriginalValue.value) : isEqual(formData.value, props.formData))
55
+ })
56
+
57
+ const isCreating = computed(() => {
58
+ return !props.formData
59
+ })
60
+
61
+ const createOriginalValue = computed(() => {
62
+ return Object.assign({}, props.initialData)
63
+ })
64
+
65
+ watchEffect(() => {
66
+ if (props.formData) {
67
+ formData.value = cloneDeep(props.formData)
68
+ }
69
+ else {
70
+ formData.value = Object.assign({}, props.initialData)
71
+ }
72
+ })
73
+
74
+ watch(() => isShowing.value, (newValue) => {
75
+ if (!newValue) formPadRef.value.reset()
76
+ })
77
+ </script>
78
+
79
+ <template>
80
+ <v-dialog
81
+ v-model="isShowing"
82
+ :fullscreen="fullscreen"
83
+ :max-width="maxWidth"
84
+ persistent
85
+ scrollable
86
+ >
87
+ <VCard>
88
+ <VToolbar>
89
+ <VToolbarTitle>
90
+ <slot name="title">
91
+ {{ (isCreating) ? "New" : "Edit" }} {{ title }}
92
+ </slot>
93
+ </VToolbarTitle>
94
+ <VSpacer />
95
+ <VToolbarItems>
96
+ <VBtn
97
+ icon="mdi mdi-close"
98
+ @click="cancel"
99
+ />
100
+ </VToolbarItems>
101
+ </VToolbar>
102
+ <VCardText>
103
+ <form-pad
104
+ ref="formPadRef"
105
+ v-model="formData"
106
+ >
107
+ <template #default="slotData">
108
+ <slot
109
+ v-bind="slotData"
110
+ :is-creating="isCreating"
111
+ :is-data-change="isDataChange"
112
+ />
113
+ </template>
114
+ </form-pad>
115
+ </VCardText>
116
+ <VCardActions>
117
+ <VSpacer />
118
+ <VBtn
119
+ color="primary"
120
+ variant="flat"
121
+ :loading="isSaving"
122
+ :disabled="!isDataChange"
123
+ @click="save"
124
+ >
125
+ {{ saveCaption }}
126
+ </VBtn>
127
+ <VBtn
128
+ color="error"
129
+ variant="flat"
130
+ :disabled="isSaving"
131
+ @click="cancel"
132
+ >
133
+ {{ cancelCaption }}
134
+ </VBtn>
135
+ </VCardActions>
136
+ </VCard>
137
+ </v-dialog>
138
+ </template>
@@ -0,0 +1,32 @@
1
+ <script lang="ts" setup>
2
+ import { watch } from 'vue'
3
+ import { cloneDeep } from 'lodash-es'
4
+
5
+ interface Props {
6
+ itemValue?: any
7
+ hook?: Function
8
+ modelValue?: any
9
+ }
10
+
11
+ const props = defineProps<Props>()
12
+ const emit = defineEmits(['update:modelValue'])
13
+
14
+ watch(() => props.itemValue, (newValue) => {
15
+ const resultValue = cloneDeep(newValue)
16
+ if (props.hook) {
17
+ Promise.resolve(props.hook(resultValue, props.modelValue)).then((result) => {
18
+ emit('update:modelValue', result)
19
+ }).catch(e => void e)
20
+ }
21
+ else {
22
+ emit('update:modelValue', resultValue)
23
+ }
24
+ }, { deep: true, immediate: true })
25
+ </script>
26
+
27
+ <template>
28
+ <input
29
+ type="hidden"
30
+ v-bind="$attrs"
31
+ >
32
+ </template>
@@ -1,11 +1,16 @@
1
1
  <script lang="ts" setup>
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any,import/no-self-import */
3
- import { compile, defineComponent, inject, onMounted, ref, shallowRef, watch, computed, withDefaults } from 'vue'
3
+ import { compile, defineComponent, inject, onMounted, ref, shallowRef, watch, computed, withDefaults, defineOptions } from 'vue'
4
4
  import { isObject } from 'lodash-es'
5
5
  import { watchDebounced } from '@vueuse/core'
6
6
  import { useRules } from '../../composables/utils/validation'
7
+ import { useDocumentTemplate } from '../../composables/document/template'
7
8
  import FormPad from './Pad.vue'
8
9
 
10
+ defineOptions({
11
+ inheritAttrs: false,
12
+ })
13
+
9
14
  interface Props {
10
15
  modelValue?: object
11
16
  template?: any
@@ -34,13 +39,15 @@ watch(() => props.readonly, (newValue) => {
34
39
 
35
40
  const { rules } = useRules()
36
41
 
37
- const trimmedTemplate = computed(() => props.template?.trim() || '')
42
+ const trimmedTemplate = ref<string>('')
43
+
44
+ const vueFunctions = { ref, shallowRef, computed, watch, onMounted }
38
45
 
39
46
  const templateScriptFunction = computed(() => {
40
47
  let templateScript = props.templateScript?.trim() || 'return {}'
41
48
  const pattern = /^\s*[{[].*[}\]]\s*$/
42
49
  if (pattern.test(templateScript)) templateScript = 'return {}'
43
- return Function('props', 'ctx', templateScript)
50
+ return Function('props', 'ctx', ...Object.keys(vueFunctions), templateScript)
44
51
  })
45
52
 
46
53
  const formPad = ref()
@@ -91,7 +98,7 @@ function buildFormComponent() {
91
98
  validate: () => formPadTemplate.value.validate(),
92
99
  resetValidate: () => formPadTemplate.value.resetValidate(),
93
100
  isValid,
94
- ...templateScriptFunction.value(props, ctx),
101
+ ...templateScriptFunction.value(props, ctx, ...Object.values(vueFunctions)),
95
102
  }
96
103
  },
97
104
  template: componentTemplate,
@@ -129,8 +136,11 @@ onMounted(() => {
129
136
  buildFormComponent()
130
137
  })
131
138
 
132
- watchDebounced(() => props.template, buildFormComponent, { debounce: 1000, maxWait: 5000 })
133
- watchDebounced(() => props.templateScript, buildFormComponent, { debounce: 1000, maxWait: 5000 })
139
+ watchDebounced(() => props.template, (newValue) => {
140
+ trimmedTemplate.value = useDocumentTemplate(newValue).trim() || ''
141
+ buildFormComponent()
142
+ }, { debounce: 500, maxWait: 5000, deep: true, immediate: true })
143
+ watchDebounced(() => props.templateScript, buildFormComponent, { debounce: 500, maxWait: 5000 })
134
144
 
135
145
  defineExpose({
136
146
  isValid,
@@ -148,6 +158,7 @@ defineExpose({
148
158
  ref="formPad"
149
159
  :disabled="disabled"
150
160
  :readonly="readonly"
161
+ :class="$attrs.class"
151
162
  >
152
163
  <template #default="formProvided">
153
164
  <slot
@@ -175,5 +186,6 @@ defineExpose({
175
186
  v-model="formData"
176
187
  :disabled="disabled"
177
188
  :readonly="readonly"
189
+ :class="$attrs.class"
178
190
  />
179
191
  </template>