@ramathibodi/nuxt-commons 0.0.1
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/README.md +81 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +7 -0
- package/dist/module.d.ts +7 -0
- package/dist/module.json +8 -0
- package/dist/module.mjs +34 -0
- package/dist/runtime/components/Alert.vue +52 -0
- package/dist/runtime/components/BarcodeReader.vue +98 -0
- package/dist/runtime/components/Calendar.vue +99 -0
- package/dist/runtime/components/Camera.vue +116 -0
- package/dist/runtime/components/ExportCSV.vue +55 -0
- package/dist/runtime/components/FileBtn.vue +56 -0
- package/dist/runtime/components/ImportCSV.vue +64 -0
- package/dist/runtime/components/Pdf/Print.vue +63 -0
- package/dist/runtime/components/Pdf/View.vue +70 -0
- package/dist/runtime/components/TabsGroup.vue +28 -0
- package/dist/runtime/components/TextBarcode.vue +52 -0
- package/dist/runtime/components/dialog/Confirm.vue +100 -0
- package/dist/runtime/components/dialog/Index.vue +72 -0
- package/dist/runtime/components/dialog/Loading.vue +34 -0
- package/dist/runtime/components/form/Date.vue +163 -0
- package/dist/runtime/components/form/DateTime.vue +107 -0
- package/dist/runtime/components/form/File.vue +187 -0
- package/dist/runtime/components/form/Login.vue +131 -0
- package/dist/runtime/components/form/Pad.vue +179 -0
- package/dist/runtime/components/form/SignPad.vue +186 -0
- package/dist/runtime/components/form/Time.vue +158 -0
- package/dist/runtime/components/form/images/CameraCrop.vue +58 -0
- package/dist/runtime/components/form/images/Edit.vue +143 -0
- package/dist/runtime/components/form/images/Preview.vue +48 -0
- package/dist/runtime/components/label/Date.vue +29 -0
- package/dist/runtime/components/label/FormatMoney.vue +29 -0
- package/dist/runtime/composables/alert.d.ts +13 -0
- package/dist/runtime/composables/alert.mjs +44 -0
- package/dist/runtime/composables/utils/validation.d.ts +32 -0
- package/dist/runtime/composables/utils/validation.mjs +36 -0
- package/dist/runtime/labs/form/EditMobile.vue +153 -0
- package/dist/runtime/labs/form/TextFieldMask.vue +43 -0
- package/dist/runtime/plugins/vueSignaturePad.d.ts +2 -0
- package/dist/runtime/plugins/vueSignaturePad.mjs +5 -0
- package/dist/runtime/types/alert.d.ts +11 -0
- package/dist/runtime/types/modules.d.ts +5 -0
- package/dist/runtime/utils/datetime.d.ts +25 -0
- package/dist/runtime/utils/datetime.mjs +166 -0
- package/dist/runtime/utils/object.d.ts +8 -0
- package/dist/runtime/utils/object.mjs +28 -0
- package/dist/types.d.mts +16 -0
- package/dist/types.d.ts +16 -0
- package/package.json +90 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, watch, watchEffect, nextTick } from 'vue'
|
|
3
|
+
import Datepicker from '@vuepic/vue-datepicker'
|
|
4
|
+
import '@vuepic/vue-datepicker/dist/main.css'
|
|
5
|
+
import { Datetime } from '../../utils/datetime'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
readonly?: boolean
|
|
9
|
+
enableSeconds?: boolean
|
|
10
|
+
locale?: 'TH' | 'EN'
|
|
11
|
+
pickerOnly?: boolean
|
|
12
|
+
modelValue?: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
16
|
+
readonly: false,
|
|
17
|
+
locale: 'TH',
|
|
18
|
+
pickerOnly: false,
|
|
19
|
+
enableSeconds: false,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits(['update:modelValue'])
|
|
23
|
+
|
|
24
|
+
const time = ref<string | null>(null)
|
|
25
|
+
const tempTime = ref<string | null>(null)
|
|
26
|
+
const isMenuOpen = ref(false)
|
|
27
|
+
const isTextFieldFocused = ref(false)
|
|
28
|
+
const isTextFieldTyped = ref(false)
|
|
29
|
+
|
|
30
|
+
function onTextFieldFocus(event: Event) {
|
|
31
|
+
isTextFieldFocused.value = true
|
|
32
|
+
nextTick(() => {
|
|
33
|
+
(event.target as HTMLInputElement).select()
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onTextFieldTyped() {
|
|
38
|
+
if (!isTextFieldTyped.value) {
|
|
39
|
+
isTextFieldTyped.value = true
|
|
40
|
+
isMenuOpen.value = false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function onTextFieldBlur() {
|
|
45
|
+
if (isTextFieldTyped.value) {
|
|
46
|
+
setTime(tempTime.value)
|
|
47
|
+
isTextFieldTyped.value = false
|
|
48
|
+
}
|
|
49
|
+
isTextFieldFocused.value = false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function onTextFieldEnterKey() {
|
|
53
|
+
onTextFieldBlur()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onTextFieldClear() {
|
|
57
|
+
reset()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function reset() {
|
|
61
|
+
time.value = null
|
|
62
|
+
tempTime.value = null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setTime(TimeString: string | null) {
|
|
66
|
+
const dateTime = Datetime().fromStringTime(TimeString, undefined, props.locale)
|
|
67
|
+
if (!dateTime.luxonDateTime.isValid) {
|
|
68
|
+
tempTime.value = null
|
|
69
|
+
time.value = null
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
time.value = dateTime.toFormat('HH:mm:ss', 'EN')
|
|
73
|
+
tempTime.value = time.value
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function setDatePicker(DateString: string | null) {
|
|
78
|
+
setTime(DateString)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
watchEffect(() => {
|
|
82
|
+
if (!isTextFieldFocused.value && time.value) {
|
|
83
|
+
const dateTime = Datetime().fromString(time.value, undefined, props.locale)
|
|
84
|
+
tempTime.value = dateTime.toFormat((props.enableSeconds) ? 'HH:mm:ss' : 'HH:mm', props.locale)
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
tempTime.value = time.value
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
watch(time, (newValue) => {
|
|
92
|
+
if (!isMenuOpen.value) emit('update:modelValue', newValue)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
watch(isMenuOpen, () => {
|
|
96
|
+
if (isMenuOpen.value && !time.value) {
|
|
97
|
+
time.value = Datetime().now().toFormat((props.enableSeconds) ? 'HH:mm:ss' : 'HH:mm:00')
|
|
98
|
+
}
|
|
99
|
+
if (!isMenuOpen.value) emit('update:modelValue', time.value)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
watch(() => props.modelValue, () => {
|
|
103
|
+
setTime(props.modelValue || null)
|
|
104
|
+
}, { immediate: true })
|
|
105
|
+
|
|
106
|
+
const isOpenMenu = (value: string) => {
|
|
107
|
+
if (value === 'txtField' && props.pickerOnly) {
|
|
108
|
+
isMenuOpen.value = true
|
|
109
|
+
}
|
|
110
|
+
else if (value === 'icon' && !props.pickerOnly) {
|
|
111
|
+
isMenuOpen.value = true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<v-menu
|
|
118
|
+
v-model="isMenuOpen"
|
|
119
|
+
:close-on-content-click="false"
|
|
120
|
+
:open-on-click="false"
|
|
121
|
+
>
|
|
122
|
+
<template #activator="{ props }">
|
|
123
|
+
<v-text-field
|
|
124
|
+
ref="textField"
|
|
125
|
+
v-model="tempTime"
|
|
126
|
+
:readonly="readonly"
|
|
127
|
+
v-bind="$attrs"
|
|
128
|
+
@focus="onTextFieldFocus"
|
|
129
|
+
@blur="onTextFieldBlur"
|
|
130
|
+
@keydown="onTextFieldTyped"
|
|
131
|
+
@keyup.enter="onTextFieldEnterKey"
|
|
132
|
+
@click:clear="onTextFieldClear"
|
|
133
|
+
@click="isOpenMenu('txtField')"
|
|
134
|
+
>
|
|
135
|
+
<template #append-inner>
|
|
136
|
+
<v-icon
|
|
137
|
+
v-bind="props"
|
|
138
|
+
@click="isOpenMenu('icon')"
|
|
139
|
+
>
|
|
140
|
+
fa:fa-regular fa-clock
|
|
141
|
+
</v-icon>
|
|
142
|
+
</template>
|
|
143
|
+
</v-text-field>
|
|
144
|
+
</template>
|
|
145
|
+
<Datepicker
|
|
146
|
+
v-model="time"
|
|
147
|
+
model-type="HH:mm:ss"
|
|
148
|
+
:enable-seconds="enableSeconds"
|
|
149
|
+
minutes-grid-increment="1"
|
|
150
|
+
time-picker
|
|
151
|
+
auto-apply
|
|
152
|
+
:close-on-auto-apply="false"
|
|
153
|
+
inline
|
|
154
|
+
:locale="locale"
|
|
155
|
+
@update:model-value="setDatePicker"
|
|
156
|
+
/>
|
|
157
|
+
</v-menu>
|
|
158
|
+
</template>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { isUndefined } from 'lodash'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
modelValue?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<Props>()
|
|
10
|
+
const emit = defineEmits(['update:modelValue', 'closeDialog', 'openCamera'])
|
|
11
|
+
|
|
12
|
+
const selectedImage = ref<string>()
|
|
13
|
+
const isDialogOpen = ref<boolean>(false)
|
|
14
|
+
const cameraRef = ref()
|
|
15
|
+
|
|
16
|
+
const handleAddImage = (image: string) => {
|
|
17
|
+
selectedImage.value = image
|
|
18
|
+
isDialogOpen.value = true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const handleCloseDialog = () => {
|
|
22
|
+
isDialogOpen.value = false
|
|
23
|
+
emit("openCamera", true);
|
|
24
|
+
emit('closeDialog', false)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleCameraClose = (shouldClose: boolean, editStatus: string) => {
|
|
28
|
+
if (isUndefined(editStatus)) {
|
|
29
|
+
emit('closeDialog', shouldClose)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handleImageEdit = (imageData: any) => {
|
|
34
|
+
emit('update:modelValue', imageData)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleOpenCamera = () => {
|
|
38
|
+
emit('openCamera', true)
|
|
39
|
+
isDialogOpen.value = false
|
|
40
|
+
cameraRef.value.openCamera()
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<Camera
|
|
46
|
+
ref="cameraRef"
|
|
47
|
+
@update:model-value="handleAddImage"
|
|
48
|
+
@close-dialog="handleCameraClose"
|
|
49
|
+
/>
|
|
50
|
+
<v-dialog v-model="isDialogOpen">
|
|
51
|
+
<FormImagesEdit
|
|
52
|
+
v-model="selectedImage"
|
|
53
|
+
@update:model-value="handleImageEdit"
|
|
54
|
+
@close-dialog="handleCloseDialog"
|
|
55
|
+
@open-camera="handleOpenCamera"
|
|
56
|
+
/>
|
|
57
|
+
</v-dialog>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted, watch } from 'vue'
|
|
3
|
+
import Cropper from 'cropperjs'
|
|
4
|
+
import 'cropperjs/dist/cropper.css'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
modelValue?: string
|
|
8
|
+
readonly?: boolean
|
|
9
|
+
flat?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
+
readonly: false,
|
|
14
|
+
flat: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits(['update:modelValue', 'closeDialog', 'openCamera'])
|
|
18
|
+
|
|
19
|
+
const cropper = ref<Cropper | null>(null)
|
|
20
|
+
const imageCanvas = ref<HTMLCanvasElement | null>(null)
|
|
21
|
+
|
|
22
|
+
const loadImageToCanvas = () => {
|
|
23
|
+
if (imageCanvas.value && props.modelValue) {
|
|
24
|
+
const canvas = imageCanvas.value as HTMLCanvasElement
|
|
25
|
+
const context = canvas.getContext('2d')
|
|
26
|
+
const image = new Image()
|
|
27
|
+
image.src = props.modelValue
|
|
28
|
+
image.onload = () => {
|
|
29
|
+
canvas.width = image.width
|
|
30
|
+
canvas.height = image.height
|
|
31
|
+
context?.clearRect(0, 0, canvas.width, canvas.height)
|
|
32
|
+
context?.drawImage(image, 0, 0)
|
|
33
|
+
if (!props.readonly) {
|
|
34
|
+
cropper.value?.destroy()
|
|
35
|
+
cropper.value = new Cropper(canvas, {
|
|
36
|
+
aspectRatio: 1,
|
|
37
|
+
rotatable: true,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onMounted(() => {
|
|
45
|
+
loadImageToCanvas()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
watch(() => props.modelValue, (newValue, oldValue) => {
|
|
49
|
+
if (newValue !== oldValue) {
|
|
50
|
+
loadImageToCanvas()
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const handleCrop = () => {
|
|
55
|
+
const croppedCanvas = cropper.value?.getCroppedCanvas()
|
|
56
|
+
if (croppedCanvas) {
|
|
57
|
+
const dataURL = croppedCanvas.toDataURL()
|
|
58
|
+
emit('update:modelValue', dataURL)
|
|
59
|
+
emit("closeDialog", false);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resetCrop = () => {
|
|
64
|
+
emit("closeDialog", false);
|
|
65
|
+
emit("openCamera", true);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const close = () => {
|
|
69
|
+
emit('closeDialog', false)
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<v-card :flat="flat">
|
|
75
|
+
<v-card-title>
|
|
76
|
+
<v-row
|
|
77
|
+
align="center"
|
|
78
|
+
justify="end"
|
|
79
|
+
>
|
|
80
|
+
<slot
|
|
81
|
+
name="closeButton"
|
|
82
|
+
:close="close"
|
|
83
|
+
>
|
|
84
|
+
<v-btn
|
|
85
|
+
icon="mdi mdi-close"
|
|
86
|
+
variant="text"
|
|
87
|
+
@click="close"
|
|
88
|
+
/>
|
|
89
|
+
</slot>
|
|
90
|
+
</v-row>
|
|
91
|
+
</v-card-title>
|
|
92
|
+
<v-card-text>
|
|
93
|
+
<v-row
|
|
94
|
+
v-if="props.readonly"
|
|
95
|
+
justify="center"
|
|
96
|
+
>
|
|
97
|
+
<v-img
|
|
98
|
+
:src="props.modelValue as string"
|
|
99
|
+
height="70vh"
|
|
100
|
+
/>
|
|
101
|
+
</v-row>
|
|
102
|
+
<v-row
|
|
103
|
+
v-else
|
|
104
|
+
justify="center"
|
|
105
|
+
>
|
|
106
|
+
<v-col cols="12">
|
|
107
|
+
<canvas
|
|
108
|
+
ref="imageCanvas"
|
|
109
|
+
class="canvas-photo"
|
|
110
|
+
style="max-height: 70vh; max-width: 40vh"
|
|
111
|
+
/>
|
|
112
|
+
<v-row
|
|
113
|
+
justify="center"
|
|
114
|
+
class="mt-2"
|
|
115
|
+
>
|
|
116
|
+
<slot
|
|
117
|
+
name="actionButtons"
|
|
118
|
+
:reset-crop="resetCrop"
|
|
119
|
+
:handle-crop="handleCrop"
|
|
120
|
+
>
|
|
121
|
+
<v-btn
|
|
122
|
+
class="ma-1"
|
|
123
|
+
prepend-icon="mdi mdi-camera"
|
|
124
|
+
color="primary"
|
|
125
|
+
@click="resetCrop"
|
|
126
|
+
>
|
|
127
|
+
Retake
|
|
128
|
+
</v-btn>
|
|
129
|
+
<v-btn
|
|
130
|
+
class="ma-1"
|
|
131
|
+
prepend-icon="fa-solid fa-check"
|
|
132
|
+
color="success"
|
|
133
|
+
@click="handleCrop"
|
|
134
|
+
>
|
|
135
|
+
Done
|
|
136
|
+
</v-btn>
|
|
137
|
+
</slot>
|
|
138
|
+
</v-row>
|
|
139
|
+
</v-col>
|
|
140
|
+
</v-row>
|
|
141
|
+
</v-card-text>
|
|
142
|
+
</v-card>
|
|
143
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
interface Props {
|
|
3
|
+
modelValue?: any
|
|
4
|
+
readonly?: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
8
|
+
readonly: false,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits([
|
|
12
|
+
'update:modelValue',
|
|
13
|
+
'closeDialogPreview',
|
|
14
|
+
'toPaint',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
const editImage = () => {
|
|
18
|
+
emit('toPaint')
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<v-card>
|
|
24
|
+
<v-toolbar>
|
|
25
|
+
<v-toolbar-title>Preview</v-toolbar-title>
|
|
26
|
+
<v-spacer />
|
|
27
|
+
<v-btn
|
|
28
|
+
v-if="!props.readonly"
|
|
29
|
+
icon="fa:fa-solid fa-pen"
|
|
30
|
+
variant="text"
|
|
31
|
+
@click="editImage"
|
|
32
|
+
/>
|
|
33
|
+
<v-btn
|
|
34
|
+
icon="fa:fa-solid fa-xmark"
|
|
35
|
+
variant="text"
|
|
36
|
+
@click="$emit('closeDialogPreview', false)"
|
|
37
|
+
/>
|
|
38
|
+
</v-toolbar>
|
|
39
|
+
<v-card-text>
|
|
40
|
+
<v-row>
|
|
41
|
+
<v-img
|
|
42
|
+
:src="modelValue.data"
|
|
43
|
+
height="70dvh"
|
|
44
|
+
/>
|
|
45
|
+
</v-row>
|
|
46
|
+
</v-card-text>
|
|
47
|
+
</v-card>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { type dateFormat, type dateTimeFormat, Datetime } from '../../utils/datetime'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
modelValue?: string
|
|
7
|
+
locale?: 'TH' | 'EN'
|
|
8
|
+
fromFormat?: string
|
|
9
|
+
format?: dateFormat | dateTimeFormat | string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
+
locale: 'TH',
|
|
14
|
+
format: 'shortDate',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const formattedDate = computed(() => {
|
|
18
|
+
if (!props.modelValue) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const dateTime = Datetime().fromString(props.modelValue, props.fromFormat)
|
|
23
|
+
return dateTime.luxonDateTime.isValid ? dateTime.toFormat(props.format, props.locale) : props.modelValue
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
{{ formattedDate }}
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import currency from 'currency.js'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
modelValue: number
|
|
7
|
+
decimal?: number
|
|
8
|
+
currency?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
12
|
+
modelValue: 0,
|
|
13
|
+
decimal: 2,
|
|
14
|
+
currency: 'USD',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const formattedCurrency = computed(() => {
|
|
18
|
+
return currency(props.modelValue, {
|
|
19
|
+
pattern: `# !`,
|
|
20
|
+
symbol: props.currency,
|
|
21
|
+
separator: ',',
|
|
22
|
+
precision: props.decimal,
|
|
23
|
+
}).format()
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
{{ formattedCurrency }}
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type InjectionKey, type Ref } from 'vue';
|
|
2
|
+
import type { AlertItem } from '../types/alert';
|
|
3
|
+
interface AlertProvide {
|
|
4
|
+
addAlert: (item: Partial<AlertItem>) => void;
|
|
5
|
+
takeAlert: (location?: string) => AlertItem | undefined;
|
|
6
|
+
hasAlert: (location?: string) => boolean;
|
|
7
|
+
clear: () => void;
|
|
8
|
+
items: Ref<AlertItem[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare const AlertKey: InjectionKey<AlertProvide>;
|
|
11
|
+
export declare function createAlert(): void;
|
|
12
|
+
export declare function useAlert(): AlertProvide | null;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { inject, provide, ref } from "vue";
|
|
2
|
+
import { head, isUndefined, now, remove } from "lodash-es";
|
|
3
|
+
export const AlertKey = Symbol.for("ramacare:alert");
|
|
4
|
+
export function createAlert() {
|
|
5
|
+
const items = ref([]);
|
|
6
|
+
provide(AlertKey, {
|
|
7
|
+
addAlert(item) {
|
|
8
|
+
const defaultValues = {
|
|
9
|
+
statusCode: 0,
|
|
10
|
+
statusMessage: "",
|
|
11
|
+
alertLocation: "default",
|
|
12
|
+
alertType: "info",
|
|
13
|
+
alertIcon: "",
|
|
14
|
+
alertDateTime: now(),
|
|
15
|
+
alertExpireIn: 1e3 * 60,
|
|
16
|
+
// 1 minute
|
|
17
|
+
data: {}
|
|
18
|
+
};
|
|
19
|
+
const newItem = { ...defaultValues, ...item };
|
|
20
|
+
if (!isUndefined(newItem.message)) {
|
|
21
|
+
items.value.push(newItem);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
takeAlert(location = "default") {
|
|
25
|
+
const item = head(items.value.filter((i) => i.alertLocation === location));
|
|
26
|
+
if (item)
|
|
27
|
+
remove(items.value, (i) => i === item);
|
|
28
|
+
return item;
|
|
29
|
+
},
|
|
30
|
+
hasAlert(location = "default") {
|
|
31
|
+
return items.value.some((i) => i.alertLocation === location);
|
|
32
|
+
},
|
|
33
|
+
clear() {
|
|
34
|
+
items.value = [];
|
|
35
|
+
},
|
|
36
|
+
items
|
|
37
|
+
});
|
|
38
|
+
setInterval(() => {
|
|
39
|
+
items.value = items.value.filter((i) => Number(i.alertDateTime) + Number(i.alertExpireIn) > now());
|
|
40
|
+
}, 1e3);
|
|
41
|
+
}
|
|
42
|
+
export function useAlert() {
|
|
43
|
+
return inject(AlertKey, null);
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare function useRules(): {
|
|
2
|
+
require: (customError?: string) => (value: any) => string | true;
|
|
3
|
+
requireIf: (conditionIf: boolean, customError?: string) => (value: any) => string | true;
|
|
4
|
+
requireTrue: (customError?: string) => (value: any) => string | true;
|
|
5
|
+
requireTrueIf: (conditionIf: boolean, customError?: string) => (value: any) => string | true;
|
|
6
|
+
numeric: (customError?: string) => (value: any) => string | true;
|
|
7
|
+
range: (minValue: number, maxValue: number, customError?: string) => (value: any) => string | true;
|
|
8
|
+
integer: (customError?: string) => (value: any) => string | true;
|
|
9
|
+
unique: (data: Array<any> | object, fieldName: string, customError?: string) => (value: any) => string | true;
|
|
10
|
+
length: (length: number, customError?: string) => (value: any) => string | true;
|
|
11
|
+
lengthGreater: (length: number, customError?: string) => (value: any) => string | true;
|
|
12
|
+
lengthLess: (length: number, customError?: string) => (value: any) => string | true;
|
|
13
|
+
telephone: (customError?: string) => (value: any) => string | true;
|
|
14
|
+
email: (customError?: string) => (value: any) => string | true;
|
|
15
|
+
regex: (regex: string, customError?: string) => (value: any) => string | true;
|
|
16
|
+
rules: import("vue").Ref<{
|
|
17
|
+
require: (customError?: string) => (value: any) => string | true;
|
|
18
|
+
requireIf: (conditionIf: boolean, customError?: string) => (value: any) => string | true;
|
|
19
|
+
requireTrue: (customError?: string) => (value: any) => string | true;
|
|
20
|
+
requireTrueIf: (conditionIf: boolean, customError?: string) => (value: any) => string | true;
|
|
21
|
+
numeric: (customError?: string) => (value: any) => string | true;
|
|
22
|
+
range: (minValue: number, maxValue: number, customError?: string) => (value: any) => string | true;
|
|
23
|
+
integer: (customError?: string) => (value: any) => string | true;
|
|
24
|
+
unique: (data: Array<any> | object, fieldName: string, customError?: string) => (value: any) => string | true;
|
|
25
|
+
length: (length: number, customError?: string) => (value: any) => string | true;
|
|
26
|
+
lengthGreater: (length: number, customError?: string) => (value: any) => string | true;
|
|
27
|
+
lengthLess: (length: number, customError?: string) => (value: any) => string | true;
|
|
28
|
+
telephone: (customError?: string) => (value: any) => string | true;
|
|
29
|
+
email: (customError?: string) => (value: any) => string | true;
|
|
30
|
+
regex: (regex: string, customError?: string) => (value: any) => string | true;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { isInteger, find } from "lodash-es";
|
|
3
|
+
export function useRules() {
|
|
4
|
+
const condition = (condition2, customError) => condition2 || customError;
|
|
5
|
+
const require = (customError = "This field is required") => (value) => condition(!!value || value === false || value === 0, customError);
|
|
6
|
+
const requireIf = (conditionIf, customError = "This field is required") => (value) => condition(!!value || value === false || value === 0 || !conditionIf, customError);
|
|
7
|
+
const requireTrue = (customError = "This field must be true") => (value) => condition(!!value, customError);
|
|
8
|
+
const requireTrueIf = (conditionIf, customError = "This field must be true") => (value) => condition(!!value || !conditionIf, customError);
|
|
9
|
+
const numeric = (customError = "This field must be a number") => (value) => condition(!value || !Number.isNaN(value), customError);
|
|
10
|
+
const range = (minValue, maxValue, customError = `Value is out of range (${minValue}-${maxValue})`) => (value) => condition(!value || value >= minValue && value <= maxValue, customError);
|
|
11
|
+
const integer = (customError = "This field must be an integer") => (value) => condition(!value || isInteger(value) || /^\+?-?\d+$/.test(value), customError);
|
|
12
|
+
const unique = (data, fieldName, customError = "This field must be unique") => (value) => condition(!value || !data || !find(data, [fieldName, value]), customError);
|
|
13
|
+
const length = (length2, customError = `Length must be ${length2}`) => (value) => condition(!value || value.length == length2, customError);
|
|
14
|
+
const lengthGreater = (length2, customError = `Length must be greater than ${length2}`) => (value) => condition(!value || value.length >= length2, customError);
|
|
15
|
+
const lengthLess = (length2, customError = `Length must be less than ${length2}`) => (value) => condition(!value || value.length <= length2, customError);
|
|
16
|
+
const telephone = (customError = "Invalid telephone number") => (value) => condition(!value || /^(?:\s*(?:0[23457][0-9]{7}|0[689][0-9]{8})(?:#[0-9]{1,5})?\s*(?:\([^()]+\))?\s*(?:,(?=.+))?)*$/.test(value), customError);
|
|
17
|
+
const email = (customError = "Invalid email address") => (value) => condition(!value || /^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,5})$/.test(value), customError);
|
|
18
|
+
const regex = (regex2, customError = "Invalid format") => (value) => condition(!value || new RegExp(regex2).test(value), customError);
|
|
19
|
+
const rules = ref({
|
|
20
|
+
require,
|
|
21
|
+
requireIf,
|
|
22
|
+
requireTrue,
|
|
23
|
+
requireTrueIf,
|
|
24
|
+
numeric,
|
|
25
|
+
range,
|
|
26
|
+
integer,
|
|
27
|
+
unique,
|
|
28
|
+
length,
|
|
29
|
+
lengthGreater,
|
|
30
|
+
lengthLess,
|
|
31
|
+
telephone,
|
|
32
|
+
email,
|
|
33
|
+
regex
|
|
34
|
+
});
|
|
35
|
+
return { rules, ...rules.value };
|
|
36
|
+
}
|