@ramathibodi/nuxt-commons 0.1.73 → 0.1.75
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 +115 -96
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -0
- package/dist/runtime/components/Alert.vue +58 -54
- package/dist/runtime/components/BarcodeReader.vue +130 -122
- package/dist/runtime/components/ExportCSV.vue +110 -102
- package/dist/runtime/components/FileBtn.vue +79 -67
- package/dist/runtime/components/ImportCSV.vue +151 -139
- package/dist/runtime/components/MrzReader.vue +168 -0
- package/dist/runtime/components/SplitterPanel.vue +67 -59
- package/dist/runtime/components/TabsGroup.vue +39 -31
- package/dist/runtime/components/TextBarcode.vue +66 -54
- package/dist/runtime/components/device/IdCardButton.vue +95 -83
- package/dist/runtime/components/device/IdCardWebSocket.vue +207 -195
- package/dist/runtime/components/device/Scanner.vue +350 -338
- package/dist/runtime/components/dialog/Confirm.vue +112 -100
- package/dist/runtime/components/dialog/Host.vue +88 -84
- package/dist/runtime/components/dialog/Index.vue +84 -72
- package/dist/runtime/components/dialog/Loading.vue +51 -39
- package/dist/runtime/components/dialog/default/Confirm.vue +112 -100
- package/dist/runtime/components/dialog/default/Loading.vue +60 -48
- package/dist/runtime/components/dialog/default/Notify.vue +82 -70
- package/dist/runtime/components/dialog/default/Printing.vue +46 -34
- package/dist/runtime/components/dialog/default/VerifyUser.vue +144 -132
- package/dist/runtime/components/document/Form.vue +50 -42
- package/dist/runtime/components/document/TemplateBuilder.vue +536 -524
- package/dist/runtime/components/form/ActionPad.vue +156 -144
- package/dist/runtime/components/form/Birthdate.vue +116 -104
- package/dist/runtime/components/form/CheckboxGroup.vue +99 -87
- package/dist/runtime/components/form/CodeEditor.vue +45 -37
- package/dist/runtime/components/form/Date.vue +270 -258
- package/dist/runtime/components/form/DateTime.vue +220 -208
- package/dist/runtime/components/form/Dialog.vue +178 -166
- package/dist/runtime/components/form/EditPad.vue +157 -145
- package/dist/runtime/components/form/File.vue +295 -283
- package/dist/runtime/components/form/Hidden.vue +44 -32
- package/dist/runtime/components/form/Iterator.vue +538 -526
- package/dist/runtime/components/form/Login.vue +143 -131
- package/dist/runtime/components/form/Pad.vue +399 -387
- package/dist/runtime/components/form/SignPad.vue +226 -218
- package/dist/runtime/components/form/System.vue +34 -26
- package/dist/runtime/components/form/Table.vue +391 -379
- package/dist/runtime/components/form/TableData.vue +236 -224
- package/dist/runtime/components/form/Time.vue +177 -165
- package/dist/runtime/components/form/images/Capture.vue +245 -237
- package/dist/runtime/components/form/images/Edit.vue +133 -121
- package/dist/runtime/components/form/images/Field.vue +331 -320
- package/dist/runtime/components/form/images/Pad.vue +54 -42
- package/dist/runtime/components/label/Date.vue +37 -29
- package/dist/runtime/components/label/DateAgo.vue +102 -94
- package/dist/runtime/components/label/DateCount.vue +152 -144
- package/dist/runtime/components/label/Field.vue +111 -103
- package/dist/runtime/components/label/FormatMoney.vue +37 -29
- package/dist/runtime/components/label/Mask.vue +46 -38
- package/dist/runtime/components/label/Object.vue +21 -13
- package/dist/runtime/components/master/Autocomplete.vue +89 -81
- package/dist/runtime/components/master/Combobox.vue +88 -80
- package/dist/runtime/components/master/RadioGroup.vue +90 -78
- package/dist/runtime/components/master/Select.vue +70 -62
- package/dist/runtime/components/master/label.vue +55 -47
- package/dist/runtime/components/model/Autocomplete.vue +91 -79
- package/dist/runtime/components/model/Combobox.vue +90 -78
- package/dist/runtime/components/model/Pad.vue +114 -102
- package/dist/runtime/components/model/Select.vue +78 -72
- package/dist/runtime/components/model/Table.vue +370 -358
- package/dist/runtime/components/model/iterator.vue +497 -489
- package/dist/runtime/components/model/label.vue +58 -50
- package/dist/runtime/components/pdf/Print.vue +75 -63
- package/dist/runtime/components/pdf/View.vue +146 -134
- package/dist/runtime/composables/alert.d.ts +4 -0
- package/dist/runtime/composables/api.d.ts +4 -0
- package/dist/runtime/composables/dialog.d.ts +1 -1
- package/dist/runtime/composables/document/templateFormHidden.d.ts +4 -0
- package/dist/runtime/composables/graphql.d.ts +1 -1
- package/dist/runtime/composables/graphqlModel.d.ts +9 -9
- package/dist/runtime/composables/graphqlModelItem.d.ts +7 -7
- package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
- package/dist/runtime/composables/localStorageModel.d.ts +4 -0
- package/dist/runtime/composables/lookupList.d.ts +4 -0
- package/dist/runtime/composables/menu.d.ts +4 -0
- package/dist/runtime/composables/useMrzReader.d.ts +48 -0
- package/dist/runtime/composables/useMrzReader.js +423 -0
- package/dist/runtime/composables/useTesseract.d.ts +16 -0
- package/dist/runtime/composables/useTesseract.js +45 -0
- package/dist/runtime/composables/userPermission.d.ts +1 -1
- package/dist/runtime/labs/Calendar.vue +99 -99
- package/dist/runtime/labs/form/EditMobile.vue +152 -152
- package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
- package/dist/runtime/plugins/clientConfig.d.ts +1 -1
- package/dist/runtime/plugins/default.d.ts +1 -1
- package/dist/runtime/plugins/dialogManager.d.ts +1 -1
- package/dist/runtime/plugins/permission.d.ts +1 -1
- package/dist/runtime/types/alert.d.ts +11 -11
- package/dist/runtime/types/clientConfig.d.ts +13 -13
- package/dist/runtime/types/dialogManager.d.ts +35 -35
- package/dist/runtime/types/formDialog.d.ts +5 -5
- package/dist/runtime/types/graphqlOperation.d.ts +23 -23
- package/dist/runtime/types/menu.d.ts +31 -31
- package/dist/runtime/types/modules.d.ts +7 -7
- package/dist/runtime/types/permission.d.ts +13 -13
- package/dist/runtime/utils/asset.d.ts +2 -0
- package/dist/runtime/utils/asset.js +49 -0
- package/package.json +131 -122
- package/scripts/enrich-vue-docs-from-ai.mjs +197 -0
- package/scripts/generate-ai-summary.mjs +321 -0
- package/scripts/generate-composables-md.mjs +129 -0
- package/scripts/postInstall.cjs +70 -70
- package/templates/.codegen/codegen.ts +32 -32
- package/templates/.codegen/plugin-schema-object.js +161 -161
- package/templates/public/tesseract/mrz.traineddata.gz +0 -0
- package/templates/public/tesseract/ocrb.traineddata.gz +0 -0
|
@@ -1,102 +1,110 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* ExportCSV exports array data to spreadsheet files, flattening nested fields before file generation.
|
|
4
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
5
|
+
*/
|
|
6
|
+
import { ref } from 'vue'
|
|
7
|
+
import * as XLSX from 'xlsx'
|
|
8
|
+
import { VBtn } from 'vuetify/components/VBtn'
|
|
9
|
+
import { useAlert } from '../composables/alert'
|
|
10
|
+
|
|
11
|
+
interface ExportButtonProps extends /* @vue-ignore */ InstanceType<typeof VBtn['$props']> {
|
|
12
|
+
fileName?: string // File name used when downloading or printing generated files.
|
|
13
|
+
sheetName?: string // Configuration option used by ExportCSV.
|
|
14
|
+
modelValue?: object[] // Bound value for v-model synchronization with the parent component.
|
|
15
|
+
stringFields?: Array<string> // Field paths that must stay as string values (no nested object conversion).
|
|
16
|
+
tooltip?: string | Record<string,any> | undefined // Tooltip text or config object shown for the action control.
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Public props accepted by ExportCSV.
|
|
21
|
+
* Document each prop field with intent, defaults, and side effects for clear generated docs.
|
|
22
|
+
*/
|
|
23
|
+
const props = withDefaults(defineProps<ExportButtonProps>(), {
|
|
24
|
+
fileName: 'download',
|
|
25
|
+
sheetName: 'Sheet1',
|
|
26
|
+
stringFields: ()=>[],
|
|
27
|
+
tooltip: ()=>({text: 'Export', location: 'bottom'}),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const alert = useAlert()
|
|
31
|
+
const loading = ref(false)
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Triggers file export
|
|
35
|
+
*/
|
|
36
|
+
function exportFile() {
|
|
37
|
+
if (props.modelValue && Array.isArray(props.modelValue) && props.modelValue.length > 0) {
|
|
38
|
+
loading.value = true
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const workbook = XLSX.utils.book_new()
|
|
42
|
+
const worksheet = XLSX.utils.json_to_sheet(flattenNestedFields(props.modelValue))
|
|
43
|
+
const fileName = `${props.fileName}.xlsx`
|
|
44
|
+
|
|
45
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, props.sheetName)
|
|
46
|
+
XLSX.writeFile(workbook, fileName)
|
|
47
|
+
} catch (error: any) {
|
|
48
|
+
alert?.addAlert({ message: `Export failed: ${error.message}`, alertType: 'error' })
|
|
49
|
+
} finally {
|
|
50
|
+
loading.value = false
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
alert?.addAlert({ message: 'Invalid or no data to export', alertType: 'error' })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Recursively flattens nested fields for export
|
|
59
|
+
* @param items - Array of objects to flatten
|
|
60
|
+
*/
|
|
61
|
+
function flattenNestedFields(items: any[]) {
|
|
62
|
+
return items.map((item: any) => {
|
|
63
|
+
return flattenObject(item)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Recursively flattens an object, converting nested keys into dot-separated keys
|
|
69
|
+
* @param obj - Object to flatten
|
|
70
|
+
* @param parentKey - Parent key (for recursion)
|
|
71
|
+
* @param separator - Separator for nested keys
|
|
72
|
+
*/
|
|
73
|
+
function flattenObject(obj: any, parentKey = '', separator = '.') {
|
|
74
|
+
return Object.keys(obj).reduce((acc: any, key: string) => {
|
|
75
|
+
const newKey = parentKey ? `${parentKey}${separator}${key}` : key
|
|
76
|
+
const value = obj[key]
|
|
77
|
+
|
|
78
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && !props.stringFields.includes(newKey)) {
|
|
79
|
+
Object.assign(acc, flattenObject(value, newKey, separator))
|
|
80
|
+
} else {
|
|
81
|
+
acc[newKey] = typeof value === 'object' ? JSON.stringify(value) : value
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return acc
|
|
85
|
+
}, {})
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<VBtn
|
|
91
|
+
v-bind="$attrs"
|
|
92
|
+
color="primary"
|
|
93
|
+
:loading="loading"
|
|
94
|
+
:disabled="loading"
|
|
95
|
+
text="Export CSV"
|
|
96
|
+
@click="exportFile"
|
|
97
|
+
v-tooltip="props.tooltip"
|
|
98
|
+
>
|
|
99
|
+
<template
|
|
100
|
+
v-for="(_, name, index) in ($slots as {})"
|
|
101
|
+
:key="index"
|
|
102
|
+
#[name]="slotData"
|
|
103
|
+
>
|
|
104
|
+
<slot
|
|
105
|
+
:name="name"
|
|
106
|
+
v-bind="((slotData || {}) as object)"
|
|
107
|
+
/>
|
|
108
|
+
</template>
|
|
109
|
+
</VBtn>
|
|
110
|
+
</template>
|
|
@@ -1,67 +1,79 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* FileBtn wraps file selection behavior into a reusable button interface that works with form and upload flows.
|
|
4
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
5
|
+
*/
|
|
6
|
+
import {ref} from 'vue'
|
|
7
|
+
import {VBtn} from 'vuetify/components/VBtn'
|
|
8
|
+
|
|
9
|
+
interface Props extends /* @vue-ignore */ InstanceType<typeof VBtn['$props']> {
|
|
10
|
+
accept?: string // Accepted file MIME types or extensions for file selection.
|
|
11
|
+
multiple?: boolean // Allows selecting or uploading more than one file.
|
|
12
|
+
iconOnly?: boolean // icon name/class used in UI rendering
|
|
13
|
+
modelValue?: File | File[] | undefined // Bound value for v-model synchronization with the parent component.
|
|
14
|
+
tooltip?: string | Record<string,any> | undefined // Tooltip text or config object shown for the action control.
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Public props accepted by FileBtn.
|
|
19
|
+
* Document each prop field with intent, defaults, and side effects for clear generated docs.
|
|
20
|
+
*/
|
|
21
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
22
|
+
multiple: false,
|
|
23
|
+
accept: '*',
|
|
24
|
+
tooltip: 'Upload File',
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Custom events emitted by FileBtn.
|
|
29
|
+
* Parents can listen to these events to react to user actions and internal state changes.
|
|
30
|
+
*/
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
(event: 'update:modelValue', value: File | File[] | undefined): void
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const fileInput = ref<HTMLInputElement>()
|
|
36
|
+
const files = ref<File | File[]>()
|
|
37
|
+
|
|
38
|
+
const openFileInput = () => {
|
|
39
|
+
fileInput.value?.click()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const reset = () => {
|
|
43
|
+
files.value = undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const emitFiles = () => {
|
|
47
|
+
emit('update:modelValue', files.value)
|
|
48
|
+
files.value = []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
defineExpose({ reset })
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<v-btn
|
|
56
|
+
v-bind="$attrs"
|
|
57
|
+
@click="openFileInput"
|
|
58
|
+
v-tooltip="tooltip"
|
|
59
|
+
>
|
|
60
|
+
<template
|
|
61
|
+
v-for="(_, name, index) in ($slots as {})"
|
|
62
|
+
:key="index"
|
|
63
|
+
#[name]="slotData"
|
|
64
|
+
>
|
|
65
|
+
<slot
|
|
66
|
+
:name="name"
|
|
67
|
+
v-bind="((slotData || {}) as object)"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
70
|
+
</v-btn>
|
|
71
|
+
<v-file-input
|
|
72
|
+
ref="fileInput"
|
|
73
|
+
v-model="files"
|
|
74
|
+
@update:modelValue="emitFiles"
|
|
75
|
+
:accept="props.accept"
|
|
76
|
+
:multiple="props.multiple"
|
|
77
|
+
style="display: none"
|
|
78
|
+
/>
|
|
79
|
+
</template>
|
|
@@ -1,139 +1,151 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* ImportCSV imports spreadsheet rows, maps nested object keys, and emits normalized records for downstream processing.
|
|
4
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
5
|
+
*/
|
|
6
|
+
import * as XLSX from 'xlsx'
|
|
7
|
+
import { ref } from 'vue'
|
|
8
|
+
import { useAlert } from '../composables/alert'
|
|
9
|
+
import { VBtn } from 'vuetify/components/VBtn'
|
|
10
|
+
|
|
11
|
+
interface ImportButtonProps extends /* @vue-ignore */ InstanceType<typeof VBtn['$props']> {
|
|
12
|
+
stringFields?: Array<string> // Field paths that must stay as string values (no nested object conversion).
|
|
13
|
+
tooltip?: string | Record<string,any> | undefined // Tooltip text or config object shown for the action control.
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Public props accepted by ImportCSV.
|
|
18
|
+
* Document each prop field with intent, defaults, and side effects for clear generated docs.
|
|
19
|
+
*/
|
|
20
|
+
const props = withDefaults(defineProps<ImportButtonProps>(), {
|
|
21
|
+
stringFields: ()=>[],
|
|
22
|
+
tooltip: ()=>({text: 'Import', location: 'bottom'}),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const alert = useAlert()
|
|
26
|
+
/**
|
|
27
|
+
* Custom events emitted by ImportCSV.
|
|
28
|
+
* Parents can listen to these events to react to user actions and internal state changes.
|
|
29
|
+
*/
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
(e: 'import', value: object[]): void
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const loading = ref(false)
|
|
35
|
+
const fileBtnRef = ref()
|
|
36
|
+
|
|
37
|
+
function uploadedFile(files: File[] | File | undefined) {
|
|
38
|
+
if (!files) return
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(files) && files.length != 1) {
|
|
41
|
+
alert?.addAlert({ message: 'Please select a single file for import', alertType: 'error' })
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(files)) files = files[0]
|
|
46
|
+
|
|
47
|
+
const fileExtension = files.name.slice(files.name.lastIndexOf('.')).toLowerCase()
|
|
48
|
+
if (!['.xlsx', '.csv'].includes(fileExtension)) {
|
|
49
|
+
alert?.addAlert({ message: `Please upload a file with .csv or .xlsx extension only (${files.name})`, alertType: 'error' })
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const reader = new FileReader()
|
|
54
|
+
reader.onload = (e: ProgressEvent<FileReader>) => {
|
|
55
|
+
const workbook = XLSX.read(e.target?.result)
|
|
56
|
+
const parsedData = parseAndAggregateColumns(
|
|
57
|
+
XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]])
|
|
58
|
+
)
|
|
59
|
+
emit('import', parsedData)
|
|
60
|
+
loading.value = false
|
|
61
|
+
fileBtnRef.value.reset()
|
|
62
|
+
}
|
|
63
|
+
loading.value = true
|
|
64
|
+
reader.readAsArrayBuffer(files)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Recursively aggregates nested columns and properties.
|
|
69
|
+
* @param items - Array of data from the Excel sheet
|
|
70
|
+
*/
|
|
71
|
+
const parseAndAggregateColumns = (items: any[]) => {
|
|
72
|
+
return items.map((item: any) => {
|
|
73
|
+
const aggregatedItem: any = {}
|
|
74
|
+
|
|
75
|
+
for (const key in item) {
|
|
76
|
+
if (key.includes('.')) {
|
|
77
|
+
// Extract root key and subKey
|
|
78
|
+
const [rootKey, ...subKeys] = key.split('.')
|
|
79
|
+
|
|
80
|
+
// Recursively aggregate subKeys
|
|
81
|
+
aggregatedItem[rootKey] = aggregatedItem[rootKey] || {}
|
|
82
|
+
assignNestedValue(aggregatedItem[rootKey], subKeys, parseIfJson(item[key]))
|
|
83
|
+
} else {
|
|
84
|
+
// Directly assign root-level properties
|
|
85
|
+
if (props.stringFields.includes(key)) aggregatedItem[key] = item[key]
|
|
86
|
+
else aggregatedItem[key] = parseIfJson(item[key])
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return aggregatedItem
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Recursively assigns a value to a nested key structure.
|
|
96
|
+
* @param obj - The object to assign to
|
|
97
|
+
* @param keys - Array of keys leading to the final property
|
|
98
|
+
* @param value - The value to assign
|
|
99
|
+
*/
|
|
100
|
+
const assignNestedValue = (obj: any, keys: string[], value: any) => {
|
|
101
|
+
const [currentKey, ...remainingKeys] = keys
|
|
102
|
+
if (remainingKeys.length === 0) {
|
|
103
|
+
obj[currentKey] = value
|
|
104
|
+
} else {
|
|
105
|
+
obj[currentKey] = obj[currentKey] || {}
|
|
106
|
+
assignNestedValue(obj[currentKey], remainingKeys, value)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Attempt to parse a value as JSON or array.
|
|
112
|
+
* @param value - The value to parse
|
|
113
|
+
*/
|
|
114
|
+
const parseIfJson = (value: any) => {
|
|
115
|
+
if (typeof value === 'string') {
|
|
116
|
+
try {
|
|
117
|
+
let parsedValue = JSON.parse(value)
|
|
118
|
+
return (parsedValue==value) ? value : parsedValue
|
|
119
|
+
} catch {
|
|
120
|
+
// If parsing fails, return the original value
|
|
121
|
+
return value
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return value
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<FileBtn
|
|
130
|
+
ref="fileBtnRef"
|
|
131
|
+
v-bind="$attrs"
|
|
132
|
+
color="primary"
|
|
133
|
+
:loading="loading"
|
|
134
|
+
text="Import CSV"
|
|
135
|
+
accept=".csv, .xlsx"
|
|
136
|
+
:multiple="false"
|
|
137
|
+
@update:model-value="uploadedFile"
|
|
138
|
+
:tooltip="props.tooltip"
|
|
139
|
+
>
|
|
140
|
+
<template
|
|
141
|
+
v-for="(_, name, index) in ($slots as {})"
|
|
142
|
+
:key="index"
|
|
143
|
+
#[name]="slotData"
|
|
144
|
+
>
|
|
145
|
+
<slot
|
|
146
|
+
:name="name"
|
|
147
|
+
v-bind="((slotData || {}) as object)"
|
|
148
|
+
/>
|
|
149
|
+
</template>
|
|
150
|
+
</FileBtn>
|
|
151
|
+
</template>
|