@ramathibodi/nuxt-commons 0.1.47 → 0.1.49
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/dist/module.json +1 -1
- package/dist/module.mjs +19 -21
- package/dist/runtime/components/dialog/Host.vue +84 -0
- package/dist/runtime/components/dialog/default/Confirm.vue +101 -0
- package/dist/runtime/components/dialog/default/Loading.vue +48 -0
- package/dist/runtime/components/dialog/default/Notify.vue +70 -0
- package/dist/runtime/components/dialog/default/Printing.vue +35 -0
- package/dist/runtime/components/dialog/default/VerifyUser.vue +132 -0
- package/dist/runtime/components/document/TemplateBuilder.vue +109 -2
- package/dist/runtime/components/form/Table.vue +14 -4
- package/dist/runtime/components/model/Table.vue +15 -7
- package/dist/runtime/composables/dialog.d.ts +1 -0
- package/dist/runtime/composables/dialog.js +5 -0
- package/dist/runtime/composables/document/template.js +22 -5
- package/dist/runtime/composables/document/templateFormTable.d.ts +1 -0
- package/dist/runtime/composables/document/templateFormTable.js +42 -1
- package/dist/runtime/composables/utils/validation.js +1 -1
- package/dist/runtime/plugins/dialogManager.d.ts +2 -0
- package/dist/runtime/plugins/dialogManager.js +72 -0
- package/dist/runtime/types/dialogManager.d.ts +36 -0
- package/dist/runtime/types/menu.d.ts +1 -1
- package/package.json +2 -3
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -9,10 +9,12 @@ const module = defineNuxtModule({
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
// Default configuration options of the Nuxt module
|
|
12
|
-
defaults: {
|
|
12
|
+
defaults: {
|
|
13
|
+
// default empty, will use fallback if not provided
|
|
14
|
+
},
|
|
13
15
|
async setup(_options, _nuxt) {
|
|
14
16
|
const resolver = createResolver(import.meta.url);
|
|
15
|
-
|
|
17
|
+
addComponentsDir({
|
|
16
18
|
path: resolver.resolve("runtime/components"),
|
|
17
19
|
pathPrefix: true,
|
|
18
20
|
global: true
|
|
@@ -21,26 +23,22 @@ const module = defineNuxtModule({
|
|
|
21
23
|
addPlugin({
|
|
22
24
|
src: resolver.resolve("runtime/plugins/permission")
|
|
23
25
|
});
|
|
24
|
-
|
|
25
|
-
src: resolver.resolve("runtime/
|
|
26
|
-
|
|
27
|
-
});
|
|
28
|
-
addTypeTemplate({
|
|
29
|
-
src: resolver.resolve("runtime/types/alert.d.ts"),
|
|
30
|
-
filename: "types/alert.d.ts"
|
|
31
|
-
});
|
|
32
|
-
addTypeTemplate({
|
|
33
|
-
src: resolver.resolve("runtime/types/menu.d.ts"),
|
|
34
|
-
filename: "types/menu.d.ts"
|
|
35
|
-
});
|
|
36
|
-
addTypeTemplate({
|
|
37
|
-
src: resolver.resolve("runtime/types/graphqlOperation.d.ts"),
|
|
38
|
-
filename: "types/graphqlOperation.d.ts"
|
|
39
|
-
});
|
|
40
|
-
addTypeTemplate({
|
|
41
|
-
src: resolver.resolve("runtime/types/formDialog.d.ts"),
|
|
42
|
-
filename: "types/formDialog.d.ts"
|
|
26
|
+
addPlugin({
|
|
27
|
+
src: resolver.resolve("runtime/plugins/dialogManager"),
|
|
28
|
+
mode: "client"
|
|
43
29
|
});
|
|
30
|
+
const typeFiles = ["modules", "alert", "menu", "graphqlOperation", "formDialog", "dialogManager"];
|
|
31
|
+
for (const file of typeFiles) {
|
|
32
|
+
addTypeTemplate({
|
|
33
|
+
src: resolver.resolve(`runtime/types/${file}.d.ts`),
|
|
34
|
+
filename: `types/${file}.d.ts`
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const runtimeConfig = _nuxt.options.runtimeConfig.public["nuxt-commons"] || {};
|
|
38
|
+
_nuxt.options.runtimeConfig.public["nuxt-commons"] = {
|
|
39
|
+
...runtimeConfig,
|
|
40
|
+
..._options
|
|
41
|
+
};
|
|
44
42
|
}
|
|
45
43
|
});
|
|
46
44
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {shallowReactive, onMounted} from 'vue'
|
|
3
|
+
import {useDialog} from "../../composables/dialog"
|
|
4
|
+
import type {DialogOpenResult} from "../../types/dialogManager";
|
|
5
|
+
|
|
6
|
+
interface DialogEntry {
|
|
7
|
+
id: number
|
|
8
|
+
component: any
|
|
9
|
+
props: Record<string, any>
|
|
10
|
+
resolve: (val: any) => void
|
|
11
|
+
reject: (val: any) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const dialogs = shallowReactive<DialogEntry[]>([])
|
|
15
|
+
let idCounter = 0
|
|
16
|
+
|
|
17
|
+
function open(component: any, inputProps: Record<string, any> = {}) : DialogOpenResult {
|
|
18
|
+
const id = ++idCounter
|
|
19
|
+
|
|
20
|
+
const promise = new Promise<any>((resolve,reject) => {
|
|
21
|
+
const dialogProps = {
|
|
22
|
+
...inputProps,
|
|
23
|
+
modelValue: true,
|
|
24
|
+
'onUpdate:modelValue': (v: boolean) => {
|
|
25
|
+
if (!v) close(id, null)
|
|
26
|
+
},
|
|
27
|
+
onResolve: (val: any) => close(id, val),
|
|
28
|
+
onReject: (val: any) => closeWithReject(id, val)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
dialogs.push({
|
|
32
|
+
id,
|
|
33
|
+
component,
|
|
34
|
+
props: dialogProps,
|
|
35
|
+
resolve,
|
|
36
|
+
reject
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const closeInstance = (val?: any) => {
|
|
41
|
+
close(id,val)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rejectInstance = (val?: any) => {
|
|
45
|
+
closeWithReject(id,val)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { id, promise, closeInstance, rejectInstance}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function close(id: number, val: any) {
|
|
52
|
+
const index = dialogs.findIndex((d) => d.id === id)
|
|
53
|
+
if (index !== -1) {
|
|
54
|
+
dialogs[index].resolve(val)
|
|
55
|
+
dialogs.splice(index, 1)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function closeWithReject(id: number, val: any) {
|
|
60
|
+
const index = dialogs.findIndex((d) => d.id === id)
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
dialogs[index].reject(val)
|
|
63
|
+
dialogs.splice(index, 1)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
onMounted(() => {
|
|
68
|
+
useDialog()?.setDialogHost(open, close)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
defineExpose({ open, close })
|
|
72
|
+
</script>
|
|
73
|
+
<template>
|
|
74
|
+
<Teleport to="body">
|
|
75
|
+
<div>
|
|
76
|
+
<component
|
|
77
|
+
v-for="dialog in dialogs"
|
|
78
|
+
:key="dialog.id"
|
|
79
|
+
:is="dialog.component"
|
|
80
|
+
v-bind="dialog.props"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</Teleport>
|
|
84
|
+
</template>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, watch, computed, type Ref } from 'vue'
|
|
3
|
+
import { isEqual, isUndefined, isEmpty } from 'lodash-es'
|
|
4
|
+
|
|
5
|
+
interface DialogProps {
|
|
6
|
+
title?: string
|
|
7
|
+
modelValue: boolean
|
|
8
|
+
message?: string
|
|
9
|
+
buttonTrueText?: string
|
|
10
|
+
buttonFalseText?: string
|
|
11
|
+
type?: 'primary' | 'success' | 'warning' | 'info' | 'error'
|
|
12
|
+
confirmData?: string
|
|
13
|
+
width?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<DialogProps>(), {
|
|
17
|
+
title: 'Confirm',
|
|
18
|
+
message: 'Do you want to proceed?',
|
|
19
|
+
buttonTrueText: 'Ok',
|
|
20
|
+
buttonFalseText: 'Cancel',
|
|
21
|
+
type: 'primary',
|
|
22
|
+
width: 'auto',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const emit = defineEmits<{
|
|
26
|
+
(e: 'update:modelValue', value: boolean): void
|
|
27
|
+
(e: 'resolve', value: boolean): void
|
|
28
|
+
(e: 'reject', value: any): void
|
|
29
|
+
}>()
|
|
30
|
+
|
|
31
|
+
const dialogVisible: Ref<boolean> = ref(props.modelValue)
|
|
32
|
+
|
|
33
|
+
watch(() => props.modelValue, (val) => {
|
|
34
|
+
dialogVisible.value = val
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
watch(dialogVisible, (val) => {
|
|
38
|
+
if (!val) emit('update:modelValue', false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const txtConfirm = ref<string>('')
|
|
42
|
+
|
|
43
|
+
const isDisabled = computed(() => {
|
|
44
|
+
if (isUndefined(props.confirmData)) return false
|
|
45
|
+
if (isEmpty(props.confirmData)) return true
|
|
46
|
+
return !isEqual(txtConfirm.value, props.confirmData)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const handleResult = (result: boolean) => {
|
|
50
|
+
dialogVisible.value = false
|
|
51
|
+
emit('resolve', result) // emit back to dialog-manager
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<v-row justify="center">
|
|
57
|
+
<v-dialog
|
|
58
|
+
v-model="dialogVisible"
|
|
59
|
+
persistent
|
|
60
|
+
:width="props.width"
|
|
61
|
+
>
|
|
62
|
+
<v-card>
|
|
63
|
+
<v-toolbar
|
|
64
|
+
:color="props.type"
|
|
65
|
+
:title="props.title"
|
|
66
|
+
/>
|
|
67
|
+
<v-card-text>{{ props.message }}</v-card-text>
|
|
68
|
+
<v-card-text v-if="props.confirmData">
|
|
69
|
+
<v-text-field
|
|
70
|
+
v-model="txtConfirm"
|
|
71
|
+
variant="underlined"
|
|
72
|
+
>
|
|
73
|
+
<template #label>
|
|
74
|
+
<slot
|
|
75
|
+
name="labelTextConfirm"
|
|
76
|
+
:text="props.confirmData"
|
|
77
|
+
/>
|
|
78
|
+
</template>
|
|
79
|
+
</v-text-field>
|
|
80
|
+
</v-card-text>
|
|
81
|
+
<v-card-actions>
|
|
82
|
+
<v-spacer />
|
|
83
|
+
<v-btn
|
|
84
|
+
variant="text"
|
|
85
|
+
@click="handleResult(false)"
|
|
86
|
+
>
|
|
87
|
+
{{ props.buttonFalseText }}
|
|
88
|
+
</v-btn>
|
|
89
|
+
<v-btn
|
|
90
|
+
:color="props.type"
|
|
91
|
+
variant="text"
|
|
92
|
+
:disabled="isDisabled"
|
|
93
|
+
@click="handleResult(true)"
|
|
94
|
+
>
|
|
95
|
+
{{ props.buttonTrueText }}
|
|
96
|
+
</v-btn>
|
|
97
|
+
</v-card-actions>
|
|
98
|
+
</v-card>
|
|
99
|
+
</v-dialog>
|
|
100
|
+
</v-row>
|
|
101
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface DialogProps {
|
|
5
|
+
modelValue: boolean
|
|
6
|
+
color?: string
|
|
7
|
+
}
|
|
8
|
+
const emit = defineEmits(['update:modelValue'])
|
|
9
|
+
|
|
10
|
+
const props = defineProps<DialogProps>()
|
|
11
|
+
const dialogVisible = ref<boolean>(props.modelValue)
|
|
12
|
+
|
|
13
|
+
watch(() => props.modelValue, (newValue) => {
|
|
14
|
+
dialogVisible.value = newValue
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
watch(() => dialogVisible.value, (newValue) => {
|
|
18
|
+
emit('update:modelValue', newValue)
|
|
19
|
+
})
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<v-dialog
|
|
24
|
+
v-model="dialogVisible"
|
|
25
|
+
persistent
|
|
26
|
+
max-width="400"
|
|
27
|
+
class="rounded-xl"
|
|
28
|
+
>
|
|
29
|
+
<v-card :color="props.color || 'surface'" elevation="10" class="pa-4 rounded-xl">
|
|
30
|
+
<v-card-title class="text-h6 text-center">
|
|
31
|
+
<slot name="title">Please wait</slot>
|
|
32
|
+
</v-card-title>
|
|
33
|
+
|
|
34
|
+
<v-card-text class="text-center">
|
|
35
|
+
<v-progress-circular
|
|
36
|
+
indeterminate
|
|
37
|
+
size="40"
|
|
38
|
+
width="4"
|
|
39
|
+
color="primary"
|
|
40
|
+
class="mb-4"
|
|
41
|
+
/>
|
|
42
|
+
<div>
|
|
43
|
+
<slot>Loading data, please wait...</slot>
|
|
44
|
+
</div>
|
|
45
|
+
</v-card-text>
|
|
46
|
+
</v-card>
|
|
47
|
+
</v-dialog>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, watch, type Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface DialogProps {
|
|
5
|
+
title?: string
|
|
6
|
+
modelValue: boolean
|
|
7
|
+
message?: string
|
|
8
|
+
buttonText?: string
|
|
9
|
+
type?: 'primary' | 'success' | 'warning' | 'info' | 'error'
|
|
10
|
+
width?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<DialogProps>(), {
|
|
14
|
+
title: 'Notice',
|
|
15
|
+
message: 'Something happened.',
|
|
16
|
+
buttonText: 'OK',
|
|
17
|
+
type: 'primary',
|
|
18
|
+
width: 'auto',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
(e: 'update:modelValue', value: boolean): void
|
|
23
|
+
(e: 'resolve', value: boolean): void
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const dialogVisible: Ref<boolean> = ref(props.modelValue)
|
|
27
|
+
|
|
28
|
+
watch(() => props.modelValue, (val) => {
|
|
29
|
+
dialogVisible.value = val
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
watch(dialogVisible, (val) => {
|
|
33
|
+
if (!val) emit('update:modelValue', false)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const handleClose = () => {
|
|
37
|
+
dialogVisible.value = false
|
|
38
|
+
emit('resolve', true)
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<v-row justify="center">
|
|
44
|
+
<v-dialog
|
|
45
|
+
v-model="dialogVisible"
|
|
46
|
+
persistent
|
|
47
|
+
:width="props.width"
|
|
48
|
+
>
|
|
49
|
+
<v-card>
|
|
50
|
+
<v-toolbar
|
|
51
|
+
:color="props.type"
|
|
52
|
+
:title="props.title"
|
|
53
|
+
density="compact"
|
|
54
|
+
/>
|
|
55
|
+
<v-card-text class="text-body-2 text-center py-4">
|
|
56
|
+
{{ props.message }}
|
|
57
|
+
</v-card-text>
|
|
58
|
+
<v-card-actions class="justify-center">
|
|
59
|
+
<v-btn
|
|
60
|
+
:color="props.type"
|
|
61
|
+
variant="elevated"
|
|
62
|
+
@click="handleClose"
|
|
63
|
+
>
|
|
64
|
+
{{ props.buttonText }}
|
|
65
|
+
</v-btn>
|
|
66
|
+
</v-card-actions>
|
|
67
|
+
</v-card>
|
|
68
|
+
</v-dialog>
|
|
69
|
+
</v-row>
|
|
70
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface DialogProps {
|
|
5
|
+
modelValue: boolean
|
|
6
|
+
color?: string
|
|
7
|
+
}
|
|
8
|
+
const emit = defineEmits(['update:modelValue'])
|
|
9
|
+
const props = defineProps<DialogProps>()
|
|
10
|
+
const dialogVisible = ref(props.modelValue)
|
|
11
|
+
|
|
12
|
+
watch(() => props.modelValue, (newValue) => {
|
|
13
|
+
dialogVisible.value = newValue
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
watch(() => dialogVisible.value, (newValue) => {
|
|
17
|
+
emit('update:modelValue', newValue)
|
|
18
|
+
})
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<v-dialog
|
|
23
|
+
v-model="dialogVisible"
|
|
24
|
+
persistent
|
|
25
|
+
max-width="320"
|
|
26
|
+
class="rounded-lg"
|
|
27
|
+
>
|
|
28
|
+
<v-card :color="props.color || 'surface'" class="pa-3 rounded-lg d-flex flex-column align-center">
|
|
29
|
+
<v-icon size="36" color="primary" class="mb-2">mdi mdi-printer</v-icon>
|
|
30
|
+
<div class="text-center text-body-2">
|
|
31
|
+
<slot>Preparing to print...</slot>
|
|
32
|
+
</div>
|
|
33
|
+
</v-card>
|
|
34
|
+
</v-dialog>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import {computed, ref, type Ref, watch} from 'vue'
|
|
3
|
+
import {useDialog} from "../../../composables/dialog"
|
|
4
|
+
import {useGraphQlOperation} from "../../../composables/graphqlOperation";
|
|
5
|
+
import {type AuthenticationState, useState} from '#imports'
|
|
6
|
+
|
|
7
|
+
interface DialogProps {
|
|
8
|
+
title?: string
|
|
9
|
+
modelValue: boolean
|
|
10
|
+
message?: string
|
|
11
|
+
confirmButtonText?: string
|
|
12
|
+
cancelButtonText?: string
|
|
13
|
+
type?: 'primary' | 'success' | 'warning' | 'info' | 'error'
|
|
14
|
+
fixedUsername?: boolean | string
|
|
15
|
+
width?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(defineProps<DialogProps>(), {
|
|
19
|
+
title: 'Verify User',
|
|
20
|
+
message: 'Please enter your credentials to continue.',
|
|
21
|
+
confirmButtonText: 'Verify',
|
|
22
|
+
cancelButtonText: 'Cancel',
|
|
23
|
+
type: 'primary',
|
|
24
|
+
fixedUsername: true,
|
|
25
|
+
width: '400',
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
(e: 'update:modelValue', value: boolean): void
|
|
30
|
+
(e: 'resolve', value: string): void
|
|
31
|
+
(e: 'reject', value: any): void
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const authenState = useState<AuthenticationState>("authentication")
|
|
35
|
+
|
|
36
|
+
const dialogVisible: Ref<boolean> = ref(props.modelValue)
|
|
37
|
+
|
|
38
|
+
watch(() => props.modelValue, (val) => {
|
|
39
|
+
dialogVisible.value = val
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
watch(dialogVisible, (val) => {
|
|
43
|
+
if (!val) emit('update:modelValue', false)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const username = ref<string>('')
|
|
47
|
+
const password = ref<string>('')
|
|
48
|
+
|
|
49
|
+
watch(()=>props.fixedUsername, (fixedUsername) => {
|
|
50
|
+
if (fixedUsername === true) {
|
|
51
|
+
let userProfile = authenState.value?.userProfile as {username: string}
|
|
52
|
+
username.value = userProfile?.username ?? ""
|
|
53
|
+
} else {
|
|
54
|
+
username.value = (fixedUsername) ? fixedUsername as string : ""
|
|
55
|
+
}
|
|
56
|
+
},{immediate: true})
|
|
57
|
+
|
|
58
|
+
const isDisabled = computed(() => {
|
|
59
|
+
return !username.value || !password.value
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const verifyUser = async (username: string, password: string): Promise<boolean> => {
|
|
63
|
+
try {
|
|
64
|
+
let result: any = await useGraphQlOperation('Mutation',"verifyUser",["success"], {username: username, password: password})
|
|
65
|
+
return result.success
|
|
66
|
+
} catch (_error) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleConfirm = async () => {
|
|
72
|
+
const verified = await verifyUser(username.value, password.value)
|
|
73
|
+
if (verified) {
|
|
74
|
+
dialogVisible.value = false
|
|
75
|
+
emit('resolve', username.value)
|
|
76
|
+
} else {
|
|
77
|
+
useDialog().notify({message: "Invalid username or password",type: 'warning',title:"Error"})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const handleCancel = () => {
|
|
82
|
+
dialogVisible.value = false
|
|
83
|
+
emit('reject', 'User cancelled')
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<v-row justify="center">
|
|
89
|
+
<v-dialog
|
|
90
|
+
v-model="dialogVisible"
|
|
91
|
+
:width="props.width"
|
|
92
|
+
persistent
|
|
93
|
+
>
|
|
94
|
+
<v-card>
|
|
95
|
+
<v-toolbar
|
|
96
|
+
:color="props.type"
|
|
97
|
+
:title="props.title"
|
|
98
|
+
density="compact"
|
|
99
|
+
/>
|
|
100
|
+
<v-card-text>
|
|
101
|
+
<div class="mb-2">{{ props.message }}</div>
|
|
102
|
+
<v-text-field
|
|
103
|
+
v-model="username"
|
|
104
|
+
:readonly="!!props.fixedUsername"
|
|
105
|
+
label="Username"
|
|
106
|
+
variant="underlined"
|
|
107
|
+
/>
|
|
108
|
+
<v-text-field
|
|
109
|
+
v-model="password"
|
|
110
|
+
label="Password"
|
|
111
|
+
type="password"
|
|
112
|
+
variant="underlined"
|
|
113
|
+
/>
|
|
114
|
+
</v-card-text>
|
|
115
|
+
<v-card-actions>
|
|
116
|
+
<v-spacer />
|
|
117
|
+
<v-btn variant="text" @click="handleCancel">
|
|
118
|
+
{{ props.cancelButtonText }}
|
|
119
|
+
</v-btn>
|
|
120
|
+
<v-btn
|
|
121
|
+
:color="props.type"
|
|
122
|
+
:disabled="isDisabled"
|
|
123
|
+
variant="text"
|
|
124
|
+
@click="handleConfirm"
|
|
125
|
+
>
|
|
126
|
+
{{ props.confirmButtonText }}
|
|
127
|
+
</v-btn>
|
|
128
|
+
</v-card-actions>
|
|
129
|
+
</v-card>
|
|
130
|
+
</v-dialog>
|
|
131
|
+
</v-row>
|
|
132
|
+
</template>
|
|
@@ -143,6 +143,7 @@ const inputTypeChoice = ref([
|
|
|
143
143
|
{ label: 'File Upload', value: 'FormFile' },
|
|
144
144
|
{ label: 'Signature Pad', value: 'FormSignPad' },
|
|
145
145
|
{ label: 'Table', value: 'FormTable' },
|
|
146
|
+
{ label: 'Table Data', value: 'FormTableData' },
|
|
146
147
|
{ label: '[Decoration] Header', value: 'Header' },
|
|
147
148
|
{ label: '[Decoration] Separator', value: 'Separator' },
|
|
148
149
|
{ label: '[Advanced] Hidden Field', value: 'FormHidden' },
|
|
@@ -150,7 +151,7 @@ const inputTypeChoice = ref([
|
|
|
150
151
|
{ label: '[Advanced] Custom Code', value: 'CustomCode' },
|
|
151
152
|
]);
|
|
152
153
|
|
|
153
|
-
const requireOption = ref(['VSelect', 'VAutocomplete', 'VCombobox', 'VRadio', 'VRadioInline', 'MasterAutocomplete','FormTable','DocumentForm','FormCheckboxGroup'])
|
|
154
|
+
const requireOption = ref(['VSelect', 'VAutocomplete', 'VCombobox', 'VRadio', 'VRadioInline', 'MasterAutocomplete','FormTable','FormTableData','DocumentForm','FormCheckboxGroup'])
|
|
154
155
|
const notRequireVariable = ref(['Header', 'Separator', 'CustomCode','DocumentForm'])
|
|
155
156
|
const notRequireLabel = ref(['Separator', 'CustomCode', 'FormFile', 'FormHidden','DocumentForm'])
|
|
156
157
|
const notRequireWidth = ref(['Separator', 'FormHidden','DocumentForm'])
|
|
@@ -160,7 +161,7 @@ const notRequireInputAttributes = ref(['CustomCode','Header', 'FormHidden','Docu
|
|
|
160
161
|
const notRequireColumnAttributes = ref(['Separator', 'FormHidden','DocumentForm'])
|
|
161
162
|
const notRequireAdvancedSetting = ref(['Separator','CustomCode', 'FormHidden'])
|
|
162
163
|
|
|
163
|
-
const hasSpecificOption = ref(['FormHidden','FormTable'])
|
|
164
|
+
const hasSpecificOption = ref(['FormHidden','FormTable','FormTableData'])
|
|
164
165
|
|
|
165
166
|
const choiceOption = ref(['VRadio', 'VRadioInline','VSelect', 'VAutocomplete', 'VCombobox','FormCheckboxGroup'])
|
|
166
167
|
const inputOptionsLabel = ref<Record<string,string>>({
|
|
@@ -258,6 +259,112 @@ const ruleOptions = (inputType: string) => (value: any) => {
|
|
|
258
259
|
</v-row>
|
|
259
260
|
</template>
|
|
260
261
|
</form-pad>
|
|
262
|
+
<form-pad v-model="data.inputOptions" v-if="data.inputType=='FormTableData'">
|
|
263
|
+
<template #default="{data: optionData}">
|
|
264
|
+
<v-row dense>
|
|
265
|
+
<v-col cols="12">
|
|
266
|
+
<form-table v-model="optionData.headers" :headers="formTableHeaders" title="Headers">
|
|
267
|
+
<template #form="{data: headerData,rules}">
|
|
268
|
+
<v-container fluid>
|
|
269
|
+
<v-row dense>
|
|
270
|
+
<v-col cols="6"><v-text-field v-model="headerData.title" label="Title"></v-text-field></v-col>
|
|
271
|
+
<v-col cols="3"><v-text-field v-model="headerData.key" label="Key" :rules="[rules.require()]"></v-text-field></v-col>
|
|
272
|
+
<v-col cols="3"><v-text-field v-model="headerData.width" label="Width"></v-text-field></v-col>
|
|
273
|
+
</v-row>
|
|
274
|
+
<v-row dense>
|
|
275
|
+
<v-col cols="12">
|
|
276
|
+
<template-builder title="Template" v-model="headerData.template"></template-builder>
|
|
277
|
+
</v-col>
|
|
278
|
+
</v-row>
|
|
279
|
+
<v-row dense>
|
|
280
|
+
<v-col cols="12">
|
|
281
|
+
<template-builder title="Header Template" v-model="headerData.headerTemplate"></template-builder>
|
|
282
|
+
</v-col>
|
|
283
|
+
</v-row>
|
|
284
|
+
<v-expansion-panels>
|
|
285
|
+
|
|
286
|
+
<v-expansion-panel >
|
|
287
|
+
<v-expansion-panel-title collapse-icon="mdi mdi-minus" expand-icon="mdi mdi-plus" class="font-weight-bold">Advanced settings</v-expansion-panel-title>
|
|
288
|
+
<v-expansion-panel-text>
|
|
289
|
+
<v-container fluid>
|
|
290
|
+
<v-row dense>
|
|
291
|
+
<v-col cols="12" md="6" v-if="!notRequireInputAttributes.includes(data.inputType)">
|
|
292
|
+
<v-text-field
|
|
293
|
+
v-model="headerData.inputAttributes"
|
|
294
|
+
label="Input Attributes"
|
|
295
|
+
/>
|
|
296
|
+
</v-col>
|
|
297
|
+
|
|
298
|
+
<v-col cols="12" md="6">
|
|
299
|
+
<v-text-field
|
|
300
|
+
v-model="headerData.conditionalDisplay"
|
|
301
|
+
label="Conditional Display"
|
|
302
|
+
/>
|
|
303
|
+
</v-col>
|
|
304
|
+
<v-col cols="12" md="6">
|
|
305
|
+
<v-text-field
|
|
306
|
+
v-model="headerData.computedValue"
|
|
307
|
+
label="Computed Value"
|
|
308
|
+
/>
|
|
309
|
+
</v-col>
|
|
310
|
+
<v-col cols="12" md="6">
|
|
311
|
+
<v-text-field
|
|
312
|
+
v-model="headerData.retrievedValue"
|
|
313
|
+
label="Retrieved Value"
|
|
314
|
+
/>
|
|
315
|
+
</v-col>
|
|
316
|
+
</v-row>
|
|
317
|
+
</v-container>
|
|
318
|
+
</v-expansion-panel-text>
|
|
319
|
+
</v-expansion-panel>
|
|
320
|
+
</v-expansion-panels>
|
|
321
|
+
</v-container>
|
|
322
|
+
</template>
|
|
323
|
+
</form-table>
|
|
324
|
+
</v-col>
|
|
325
|
+
<v-col cols="12">
|
|
326
|
+
{{optionData}}
|
|
327
|
+
<form-table v-model="optionData.items"
|
|
328
|
+
hide-default-footer
|
|
329
|
+
:importable="false"
|
|
330
|
+
:exportable="false"
|
|
331
|
+
:searchable="false"
|
|
332
|
+
:headers="optionData.headers" title="Data">
|
|
333
|
+
<template #form="{data: headerData,rules}">
|
|
334
|
+
<v-container fluid>
|
|
335
|
+
<v-row dense>
|
|
336
|
+
<v-col v-for="header of optionData.headers" cols="12">
|
|
337
|
+
<v-text-field v-if="header.key != 'action' && !header.template"
|
|
338
|
+
v-model="headerData[header.title]"
|
|
339
|
+
:label="header.title">
|
|
340
|
+
|
|
341
|
+
</v-text-field></v-col>
|
|
342
|
+
</v-row>
|
|
343
|
+
</v-container>
|
|
344
|
+
</template>
|
|
345
|
+
|
|
346
|
+
<template v-for="header of useFilter(optionData.headers,(item)=> item.template)" #[`item.${header.key}`]="{item}">
|
|
347
|
+
<form-pad
|
|
348
|
+
:template="header.template"
|
|
349
|
+
v-model="item[header.key]">
|
|
350
|
+
</form-pad>
|
|
351
|
+
|
|
352
|
+
</template>
|
|
353
|
+
<template v-for="header of useFilter(optionData.headers,(item)=> item.headerTemplate)" #[`header.${header.key}`]="{item}">
|
|
354
|
+
<form-pad
|
|
355
|
+
:template="header.headerTemplate"
|
|
356
|
+
>
|
|
357
|
+
</form-pad>
|
|
358
|
+
|
|
359
|
+
</template>
|
|
360
|
+
|
|
361
|
+
</form-table>
|
|
362
|
+
|
|
363
|
+
</v-col>
|
|
364
|
+
</v-row>
|
|
365
|
+
</template>
|
|
366
|
+
|
|
367
|
+
</form-pad>
|
|
261
368
|
<form-pad v-model="data.inputOptions" v-if="data.inputType=='FormHidden'">
|
|
262
369
|
<template #default="{data: optionData}">
|
|
263
370
|
<v-row dense>
|
|
@@ -3,6 +3,7 @@ import {VDataTable} from 'vuetify/components/VDataTable'
|
|
|
3
3
|
import {VInput} from 'vuetify/components/VInput'
|
|
4
4
|
import {computed, defineOptions,defineExpose, nextTick, ref, useAttrs, watch, useTemplateRef} from 'vue'
|
|
5
5
|
import {omit} from 'lodash-es'
|
|
6
|
+
import {useDialog} from "../../composables/dialog"
|
|
6
7
|
import type {FormDialogCallback} from '../../types/formDialog'
|
|
7
8
|
|
|
8
9
|
defineOptions({
|
|
@@ -25,6 +26,7 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataTable['$props
|
|
|
25
26
|
inputPadOnly?: boolean
|
|
26
27
|
saveAndStay?: boolean
|
|
27
28
|
stringFields?: Array<string>
|
|
29
|
+
items?: Record<string, any>[]
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -83,6 +85,12 @@ watch(items, (newValue) => {
|
|
|
83
85
|
emit('update:modelValue', newValue)
|
|
84
86
|
}, { deep: true })
|
|
85
87
|
|
|
88
|
+
onMounted(()=>{
|
|
89
|
+
if (props.items){
|
|
90
|
+
items.value = props.items
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
86
94
|
function createItem(item: Record<string, any>, callback?: FormDialogCallback) {
|
|
87
95
|
if (items.value.length > 0) item[props.modelKey] = Math.max(...items.value.map(i => i[props.modelKey] || 0)) + 1
|
|
88
96
|
else item[props.modelKey] = 1
|
|
@@ -155,14 +163,16 @@ function moveToItem(currentItem: Record<string, any>, callback?: FormDialogCallb
|
|
|
155
163
|
if (callback) callback.done();
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
function deleteItem(deleteItem: Record<string, any>, callback?: FormDialogCallback) {
|
|
166
|
+
async function deleteItem(deleteItem: Record<string, any>, callback?: FormDialogCallback) {
|
|
159
167
|
const index = items.value.findIndex(item => item[props.modelKey] === deleteItem[props.modelKey])
|
|
160
168
|
|
|
161
169
|
if (index !== -1) {
|
|
162
|
-
|
|
170
|
+
let confirm = await useDialog().confirm({message: "Do you want to delete record?"})
|
|
171
|
+
if (confirm) {
|
|
172
|
+
items.value.splice(index, 1)
|
|
173
|
+
if (callback) callback.done()
|
|
174
|
+
}
|
|
163
175
|
}
|
|
164
|
-
|
|
165
|
-
if (callback) callback.done()
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
function openDialog(item?: object) {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
2
|
+
import {computed,watch, nextTick, ref, useAttrs} from 'vue'
|
|
3
|
+
import {VDataTable} from 'vuetify/components/VDataTable'
|
|
4
|
+
import {clone} from 'lodash-es'
|
|
5
|
+
import {useGraphqlModel} from '../../composables/graphqlModel'
|
|
6
|
+
import {useDialog} from "../../composables/dialog"
|
|
7
|
+
import type {GraphqlModelProps} from '../../composables/graphqlModel'
|
|
8
|
+
import type {FormDialogCallback} from "../../types/formDialog";
|
|
7
9
|
|
|
8
10
|
defineOptions({
|
|
9
11
|
inheritAttrs: false,
|
|
@@ -45,6 +47,7 @@ const plainAttrs = computed(() => {
|
|
|
45
47
|
if (props.headers) returnAttrs['headers'] = props.headers
|
|
46
48
|
return returnAttrs
|
|
47
49
|
})
|
|
50
|
+
|
|
48
51
|
const currentItem = ref<Record<string, any> | undefined>(undefined)
|
|
49
52
|
const isDialogOpen = ref<boolean>(false)
|
|
50
53
|
|
|
@@ -62,6 +65,11 @@ function openDialog(item?: object) {
|
|
|
62
65
|
})
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
async function confirmDeleteItem(item: Record<string, any>, callback?: FormDialogCallback) {
|
|
69
|
+
let confirm = await useDialog().confirm({message: "Do you want to delete record?"})
|
|
70
|
+
if (confirm) deleteItem(item,callback)
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
const operation = ref({ openDialog, createItem, importItems, updateItem, deleteItem, reload, setSearch, canServerPageable, canServerSearch, canCreate, canUpdate, canDelete })
|
|
66
74
|
|
|
67
75
|
const computedInitialData = computed(() => {
|
|
@@ -191,7 +199,7 @@ defineExpose({ reload,operation })
|
|
|
191
199
|
variant="flat"
|
|
192
200
|
density="compact"
|
|
193
201
|
icon="mdi mdi-delete"
|
|
194
|
-
@click="
|
|
202
|
+
@click="confirmDeleteItem(item)"
|
|
195
203
|
/>
|
|
196
204
|
</template>
|
|
197
205
|
</v-data-table-server>
|
|
@@ -232,7 +240,7 @@ defineExpose({ reload,operation })
|
|
|
232
240
|
variant="flat"
|
|
233
241
|
density="compact"
|
|
234
242
|
icon="mdi mdi-delete"
|
|
235
|
-
@click="
|
|
243
|
+
@click="confirmDeleteItem(item)"
|
|
236
244
|
/>
|
|
237
245
|
</template>
|
|
238
246
|
</v-data-table>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useDialog: () => import("../types/dialogManager").DialogPlugin;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { processTemplateFormTable } from "./templateFormTable.js";
|
|
1
|
+
import { processTemplateFormTable, processTemplateFormTableData } from "./templateFormTable.js";
|
|
2
2
|
import { processTemplateFormHidden } from "./templateFormHidden.js";
|
|
3
3
|
import { some, includes } from "lodash-es";
|
|
4
4
|
export const validationRulesRegex = /^(require(?:\([^)]*\))?|requireIf\([^)]*\)|requireTrue(?:\([^)]*\))?|requireTrueIf\([^)]*\)|numeric(?:\([^)]*\))?|range\([^)]*\)|integer(?:\([^)]*\))?|unique\([^)]*\)|length(?:\([^)]*\))?|lengthGreater\([^)]*\)|lengthLess\([^)]*\)|telephone(?:\([^)]*\))?|email(?:\([^)]*\))?|regex\([^)]*\)|idcard(?:\([^)]*\))?|DateFuture(?:\([^)]*\))?|DatetimeFuture(?:\([^)]*\))?|DateHappen(?:\([^)]*\))?|DatetimeHappen(?:\([^)]*\))?|DateAfter\([^)]*\)|DateBefore\([^)]*\)|DateEqual\([^)]*\))(,(require(?:\([^)]*\))?|requireIf\([^)]*\)|requireTrue(?:\([^)]*\))?|requireTrueIf\([^)]*\)|numeric(?:\([^)]*\))?|range\([^)]*\)|integer(?:\([^)]*\))?|unique\([^)]*\)|length(?:\([^)]*\))?|lengthGreater\([^)]*\)|lengthLess\([^)]*\)|telephone(?:\([^)]*\))?|email(?:\([^)]*\))?|regex\([^)]*\)|idcard(?:\([^)]*\))?|DateFuture(?:\([^)]*\))?|DatetimeFuture(?:\([^)]*\))?|DateHappen(?:\([^)]*\))?|DatetimeHappen(?:\([^)]*\))?|DateAfter\([^)]*\)|DateBefore\([^)]*\)|DateEqual\([^)]*\)))*$/;
|
|
@@ -52,10 +52,10 @@ function templateItemToString(item, parentTemplates) {
|
|
|
52
52
|
templateString = item.inputCustomCode || "";
|
|
53
53
|
break;
|
|
54
54
|
case "VRadio":
|
|
55
|
-
templateString = `${item.inputLabel ? `<p class="opacity-60">${item.inputLabel}</p>` : ""} <v-radio-group v-model="data.${item.variableName || ""}"${validationRules ? " " + validationRules.trim() : ""}>${optionString ? " " + optionString.trim() : ""}</v-radio-group>`;
|
|
55
|
+
templateString = `${item.inputLabel ? `<p class="opacity-60">${item.inputLabel}</p>` : ""} <v-radio-group ${item.inputAttributes?.trim()} v-model="data.${item.variableName || ""}"${validationRules ? " " + validationRules.trim() : ""}>${optionString ? " " + optionString.trim() : ""}</v-radio-group>`;
|
|
56
56
|
break;
|
|
57
57
|
case "VRadioInline":
|
|
58
|
-
templateString = `<v-radio-group v-model="data.${item.variableName || ""}" inline ${validationRules ? " " + validationRules.trim() : ""}>${item.inputLabel ? `<template #prepend><span class="opacity-60">${item.inputLabel}</span></template>` : ""}${optionString ? " " + optionString.trim() : ""}</v-radio-group>`;
|
|
58
|
+
templateString = `<v-radio-group ${item.inputAttributes?.trim()} v-model="data.${item.variableName || ""}" inline ${validationRules ? " " + validationRules.trim() : ""}>${item.inputLabel ? `<template #prepend><span class="opacity-60">${item.inputLabel}</span></template>` : ""}${optionString ? " " + optionString.trim() : ""}</v-radio-group>`;
|
|
59
59
|
break;
|
|
60
60
|
case "Separator":
|
|
61
61
|
templateString = "</v-row><v-row dense>";
|
|
@@ -66,6 +66,9 @@ function templateItemToString(item, parentTemplates) {
|
|
|
66
66
|
case "FormTable":
|
|
67
67
|
templateString = processTemplateFormTable(item, parentTemplates);
|
|
68
68
|
break;
|
|
69
|
+
case "FormTableData":
|
|
70
|
+
templateString = processTemplateFormTableData(item, parentTemplates);
|
|
71
|
+
break;
|
|
69
72
|
case "FormHidden":
|
|
70
73
|
templateString = processTemplateFormHidden(item, parentTemplates);
|
|
71
74
|
break;
|
|
@@ -112,13 +115,27 @@ export function escapeObjectForInlineBinding(obj) {
|
|
|
112
115
|
export function buildValidationRules(validationString) {
|
|
113
116
|
validationString = validationString.replace(/^\[|]$/g, "").trim();
|
|
114
117
|
if (!validationRulesRegex.test(validationString)) return "";
|
|
115
|
-
const rules =
|
|
118
|
+
const rules = [];
|
|
119
|
+
let current = "";
|
|
120
|
+
let depth = 0;
|
|
121
|
+
for (let char of validationString) {
|
|
122
|
+
if (char === "," && depth === 0) {
|
|
123
|
+
if (current.trim()) rules.push(current.trim());
|
|
124
|
+
current = "";
|
|
125
|
+
} else {
|
|
126
|
+
if (char === "(") depth++;
|
|
127
|
+
if (char === ")") depth--;
|
|
128
|
+
current += char;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (current.trim()) rules.push(current.trim());
|
|
132
|
+
const formatted = rules.map((rule) => {
|
|
116
133
|
rule = rule.trim();
|
|
117
134
|
if (!rule.startsWith("rules.")) rule = `rules.${rule}`;
|
|
118
135
|
if (!/\(.+\)$/.test(rule)) rule += "()";
|
|
119
136
|
return rule.replace(/"/g, "'");
|
|
120
137
|
});
|
|
121
|
-
return `:rules="[${
|
|
138
|
+
return `:rules="[${formatted.join(",")}]"`;
|
|
122
139
|
}
|
|
123
140
|
export function processDefaultTemplate(item, insideTemplate, optionString, validationRules) {
|
|
124
141
|
if (!validationRules) validationRules = item.validationRules ? buildValidationRules(item.validationRules) || "" : "";
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { type DocumentTemplateItem } from './template.js';
|
|
2
2
|
export declare function processTemplateFormTable(item: DocumentTemplateItem, parentTemplates: string | string[]): string;
|
|
3
|
+
export declare function processTemplateFormTableData(item: DocumentTemplateItem, parentTemplates: string | string[]): string;
|
|
@@ -5,5 +5,46 @@ import {
|
|
|
5
5
|
} from "./template.js";
|
|
6
6
|
export function processTemplateFormTable(item, parentTemplates) {
|
|
7
7
|
let tableOptions = Object.assign({ title: item.inputLabel || "", formTemplate: "" }, item.inputOptions);
|
|
8
|
-
|
|
8
|
+
let tableHeader = tableOptions.headers || [];
|
|
9
|
+
if (!tableHeader.some((h) => h.key === "action")) tableHeader.push({ title: "Action", key: "action", width: "100px" });
|
|
10
|
+
return processDefaultTemplate(item, `<template #form="{data,rules}">${useDocumentTemplate(tableOptions.formTemplate)}</template>`, `title="${tableOptions.title}" :headers='${escapeObjectForInlineBinding(tableHeader)}'`);
|
|
11
|
+
}
|
|
12
|
+
export function processTemplateFormTableData(item, parentTemplates) {
|
|
13
|
+
let tableOptions = Object.assign({ title: item.inputLabel || "", formTemplate: "" }, item.inputOptions);
|
|
14
|
+
let tableHeader = tableOptions.headers || [];
|
|
15
|
+
let tableItems = tableOptions.items || [];
|
|
16
|
+
let tableTemplate = "";
|
|
17
|
+
if (item.conditionalDisplay) {
|
|
18
|
+
item.inputAttributes = `${item.inputAttributes?.trim() || ""} v-if="${item.conditionalDisplay}"`.trim();
|
|
19
|
+
}
|
|
20
|
+
for (const header of tableHeader) {
|
|
21
|
+
const inputAttributes = header.conditionalDisplay ? `v-if="${header.conditionalDisplay}"`.trim() : "";
|
|
22
|
+
if (header.template) {
|
|
23
|
+
tableTemplate += `<template #item.${header.key}="{item}">
|
|
24
|
+
<form-pad ${inputAttributes} template='${header.template}' v-model="item.${header.key}">
|
|
25
|
+
</form-pad>
|
|
26
|
+
</template>`;
|
|
27
|
+
}
|
|
28
|
+
if (header.headerTemplate) {
|
|
29
|
+
tableTemplate += `<template #header.${header.key}>
|
|
30
|
+
<form-pad ${inputAttributes} template='${header.headerTemplate}'
|
|
31
|
+
@update:model-value='(value) =>{
|
|
32
|
+
for (const t of data.${item.variableName}) {
|
|
33
|
+
t.${header.key} = {...value}
|
|
34
|
+
}
|
|
35
|
+
}'>
|
|
36
|
+
</form-pad>
|
|
37
|
+
</template>`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
item.inputType = "FormTable";
|
|
41
|
+
return processDefaultTemplate(item, tableTemplate, `title="${tableOptions.title}"
|
|
42
|
+
:headers='${escapeObjectForInlineBinding(tableHeader)}'
|
|
43
|
+
:items='${escapeObjectForInlineBinding(tableItems)}'
|
|
44
|
+
:insertable="false"
|
|
45
|
+
:importable="false"
|
|
46
|
+
:exportable="false"
|
|
47
|
+
:searchable="false"
|
|
48
|
+
hide-default-footer
|
|
49
|
+
`);
|
|
9
50
|
}
|
|
@@ -7,7 +7,7 @@ export function useRules() {
|
|
|
7
7
|
const requireIf = (conditionIf, customError = "This field is required") => (value) => condition(!!value || value === false || value === 0 || !conditionIf, customError);
|
|
8
8
|
const requireTrue = (customError = "This field must be true") => (value) => condition(!!value, customError);
|
|
9
9
|
const requireTrueIf = (conditionIf, customError = "This field must be true") => (value) => condition(!!value || !conditionIf, customError);
|
|
10
|
-
const numeric = (customError = "This field must be a number") => (value) => condition(!value || !
|
|
10
|
+
const numeric = (customError = "This field must be a number") => (value) => condition(!value || !isNaN(Number(value)), customError);
|
|
11
11
|
const range = (minValue, maxValue, customError = `Value is out of range (${minValue}-${maxValue})`) => (value) => condition(!value || value >= minValue && value <= maxValue, customError);
|
|
12
12
|
const integer = (customError = "This field must be an integer") => (value) => condition(!value || isInteger(value) || /^\+?-?\d+$/.test(value), customError);
|
|
13
13
|
const unique = (data, fieldName, customError = "This field must be unique") => (value) => condition(!value || !data || !find(data, [fieldName, value]), customError);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
2
|
+
import { h, render } from "vue";
|
|
3
|
+
import DialogHost from "../components/dialog/Host.vue";
|
|
4
|
+
import DialogDefaultConfirm from "../components/dialog/default/Confirm.vue";
|
|
5
|
+
import DialogDefaultNotify from "../components/dialog/default/Notify.vue";
|
|
6
|
+
import DialogDefaultLoading from "../components/dialog/default/Loading.vue";
|
|
7
|
+
import DialogDefaultPrinting from "../components/dialog/default/Printing.vue";
|
|
8
|
+
import DialogDefaultVerifyUser from "../components/dialog/default/VerifyUser.vue";
|
|
9
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
10
|
+
const dialogComponents = {
|
|
11
|
+
confirmDialog: DialogDefaultConfirm,
|
|
12
|
+
notifyDialog: DialogDefaultNotify,
|
|
13
|
+
loadingDialog: DialogDefaultLoading,
|
|
14
|
+
printingDialog: DialogDefaultPrinting,
|
|
15
|
+
verifyUserDialog: DialogDefaultVerifyUser
|
|
16
|
+
};
|
|
17
|
+
let hostOpenFn = null;
|
|
18
|
+
let hostCloseFn = null;
|
|
19
|
+
const setDialogHost = (openFn, closeFn) => {
|
|
20
|
+
hostOpenFn = openFn;
|
|
21
|
+
hostCloseFn = closeFn;
|
|
22
|
+
};
|
|
23
|
+
const setDialogComponents = (components) => {
|
|
24
|
+
Object.assign(dialogComponents, components);
|
|
25
|
+
};
|
|
26
|
+
const open = (component, props = {}) => {
|
|
27
|
+
return hostOpenFn?.(component, props).promise ?? Promise.reject("No dialog host registered");
|
|
28
|
+
};
|
|
29
|
+
const openPromise = async (component, promiseInput, props = {}) => {
|
|
30
|
+
const dialogInstance = hostOpenFn?.(component, props);
|
|
31
|
+
try {
|
|
32
|
+
if (promiseInput instanceof Promise) {
|
|
33
|
+
return await promiseInput;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(promiseInput)) {
|
|
36
|
+
const promises = promiseInput.map((item) => {
|
|
37
|
+
if (typeof item === "function") {
|
|
38
|
+
try {
|
|
39
|
+
return Promise.resolve(item());
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return Promise.reject(err);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
return Promise.resolve(item);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return await Promise.all(promises);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return await Promise.resolve(promiseInput());
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return Promise.reject(err);
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
dialogInstance?.closeInstance();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const confirm = (props = {}) => open(dialogComponents.confirmDialog, props);
|
|
59
|
+
const notify = (props = {}) => open(dialogComponents.notifyDialog, props);
|
|
60
|
+
const verifyUser = (props = {}) => open(dialogComponents.verifyUserDialog, props);
|
|
61
|
+
const loading = async (promiseInput, props = {}) => openPromise(dialogComponents.loadingDialog, promiseInput, props);
|
|
62
|
+
const printing = async (promiseInput, props = {}) => openPromise(dialogComponents.printingDialog, promiseInput, props);
|
|
63
|
+
nuxtApp.hook("app:mounted", () => {
|
|
64
|
+
const el = document.createElement("div");
|
|
65
|
+
el.id = "__nuxt_dialog_host";
|
|
66
|
+
document.body.appendChild(el);
|
|
67
|
+
const vnode = h(DialogHost);
|
|
68
|
+
vnode.appContext = nuxtApp.vueApp._context;
|
|
69
|
+
render(vnode, el);
|
|
70
|
+
});
|
|
71
|
+
nuxtApp.provide("dialog", { open, confirm, notify, verifyUser, loading, printing, setDialogHost, setDialogComponents });
|
|
72
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface DialogPlugin {
|
|
2
|
+
open: (component: any,props?: Record<string, any>) => Promise<any>
|
|
3
|
+
openPromise: (component: any, promiseInput: Promise<any> | (() => any | Promise<any>) | (Promise<any> | (() => any | Promise<any>))[], props?: Record<string, any>) => Promise<any | any[]>
|
|
4
|
+
confirm: (props?: Record<string, any>) => Promise<boolean>
|
|
5
|
+
notify: (props?: Record<string, any>) => Promise<boolean>
|
|
6
|
+
verifyUser: (props?: Record<string, any>) => Promise<boolean>
|
|
7
|
+
loading: <T>(promiseInput: Promise<any> | (() => any | Promise<any>) | (Promise<any> | (() => any | Promise<any>))[],props?: Record<string, any>) => Promise<any | any[]>
|
|
8
|
+
printing: <T>(promiseInput: Promise<any> | (() => any | Promise<any>) | (Promise<any> | (() => any | Promise<any>))[],props?: Record<string, any>) => Promise<any | any[]>
|
|
9
|
+
setDialogHost: (openFn: DialogOpenFn,closeFn: DialogCloseFn) => void
|
|
10
|
+
setDialogComponents: (components:object) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DialogOpenResult<T = any> {
|
|
14
|
+
id: number
|
|
15
|
+
promise: Promise<T>
|
|
16
|
+
closeInstance: (value?: T) => void
|
|
17
|
+
rejectInstance: (value?: T) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DialogOpenFn {
|
|
21
|
+
(component: any, props?: Record<string, any>): DialogOpenResult
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DialogCloseFn {
|
|
25
|
+
(id: number, value?: any): void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module '#app' {
|
|
29
|
+
interface NuxtApp {
|
|
30
|
+
$dialog: DialogPlugin
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface RuntimeNuxtApp {
|
|
34
|
+
$dialog: DialogPlugin
|
|
35
|
+
}
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramathibodi/nuxt-commons",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"description": "Ramathibodi Nuxt modules for common components",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -116,6 +116,5 @@
|
|
|
116
116
|
"typescript": "^5.8.2",
|
|
117
117
|
"vitest": "^1.6.1",
|
|
118
118
|
"vue-tsc": "^2.2.10"
|
|
119
|
-
}
|
|
120
|
-
"packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531"
|
|
119
|
+
}
|
|
121
120
|
}
|