@mixd-id/web-scaffold 0.1.2301231326 → 0.1.2301231328
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -4
- package/src/components/Carousel.vue +1 -1
- package/src/components/Checkbox.vue +1 -1
- package/src/components/ContextMenu.vue +88 -71
- package/src/components/ImportModal.vue +240 -0
- package/src/components/ListPage1.vue +1 -1
- package/src/components/ListPage2.vue +2 -2
- package/src/components/Radio.vue +1 -1
- package/src/components/Switch.vue +1 -1
- package/src/components/VirtualScroll.vue +1 -1
- package/src/components/VirtualTable.vue +1 -1
- package/src/index.js +3 -3
- package/src/utils/helpers.js +22 -0
- package/src/{helper.js → utils/helpers.mjs} +24 -26
- package/src/utils/importer.js +151 -0
- package/src/helpers.mjs +0 -31
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mixd-id/web-scaffold",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2301231328",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite serve",
|
|
7
7
|
"build": "vite build",
|
|
@@ -11,23 +11,30 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./src/index.js",
|
|
13
13
|
"./themes/default": "./src/themes/default/index.js",
|
|
14
|
-
"./themes/kliknss": "./src/themes/kliknss/index.js",
|
|
15
14
|
"./UI.vue": "./src/UI.vue",
|
|
16
|
-
"./helpers
|
|
15
|
+
"./helpers": {
|
|
16
|
+
"require": "./src/utils/helpers.js",
|
|
17
|
+
"import": "./src/utils/helpers.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./importer": "./src/utils/importer.js"
|
|
17
20
|
},
|
|
18
21
|
"dependencies": {
|
|
19
22
|
"@faker-js/faker": "^7.3.0",
|
|
20
23
|
"@tailwindcss/line-clamp": "^0.4.0",
|
|
21
24
|
"@vueuse/core": "^9.0.2",
|
|
25
|
+
"adm-zip": "^0.5.10",
|
|
22
26
|
"axios": "^0.27.2",
|
|
23
27
|
"compression": "^1.7.4",
|
|
24
28
|
"cookie-parser": "^1.4.6",
|
|
25
29
|
"cors": "^2.8.5",
|
|
26
30
|
"daisyui": "^2.19.0",
|
|
27
31
|
"dayjs": "^1.11.2",
|
|
32
|
+
"exceljs": "^4.3.0",
|
|
28
33
|
"express": "^4.18.1",
|
|
34
|
+
"file-type": "^18.2.1",
|
|
29
35
|
"glob": "^8.0.3",
|
|
30
|
-
"lodash
|
|
36
|
+
"lodash": "^4.17.21",
|
|
37
|
+
"md5": "^2.3.0",
|
|
31
38
|
"nprogress": "^0.2.0",
|
|
32
39
|
"pinia": "^2.0.14",
|
|
33
40
|
"prismjs": "^1.28.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Teleport to=".Y29u">
|
|
3
|
-
<div v-if="!!(
|
|
3
|
+
<div v-if="!!(computedState)" :class="$style.contextMenu" :style="computedStyle" ref="contextMenu">
|
|
4
4
|
<div :class="bodyClass">
|
|
5
5
|
<slot></slot>
|
|
6
6
|
</div>
|
|
@@ -41,12 +41,16 @@ export default {
|
|
|
41
41
|
|
|
42
42
|
computed:{
|
|
43
43
|
|
|
44
|
+
computedState(){
|
|
45
|
+
return this.state || this.isOpen
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
},
|
|
45
49
|
|
|
46
50
|
data(){
|
|
47
51
|
return {
|
|
48
52
|
computedStyle: null,
|
|
49
|
-
|
|
53
|
+
isOpen: false
|
|
50
54
|
}
|
|
51
55
|
},
|
|
52
56
|
|
|
@@ -54,75 +58,7 @@ export default {
|
|
|
54
58
|
|
|
55
59
|
state(to){
|
|
56
60
|
if(to){
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const caller = this.caller.$el ? this.caller.$el :
|
|
60
|
-
this.caller instanceof HTMLElement ? this.caller : null
|
|
61
|
-
|
|
62
|
-
if(caller){
|
|
63
|
-
|
|
64
|
-
this.$nextTick(() => {
|
|
65
|
-
const rect = caller.getBoundingClientRect()
|
|
66
|
-
|
|
67
|
-
let maxHeight, bottom
|
|
68
|
-
|
|
69
|
-
switch(this.position){
|
|
70
|
-
|
|
71
|
-
case 'bottom-right':
|
|
72
|
-
this.computedStyle = {
|
|
73
|
-
right: (window.innerWidth - (Math.round(rect.x) + rect.width)) + 'px',
|
|
74
|
-
top: Math.round(rect.y + rect.height + 8) + 'px',
|
|
75
|
-
maxHeight: (window.innerHeight - Math.round(rect.y + rect.height + 16)) + 'px',
|
|
76
|
-
transformOrigin: 'top right'
|
|
77
|
-
}
|
|
78
|
-
break
|
|
79
|
-
|
|
80
|
-
case 'top-left':
|
|
81
|
-
|
|
82
|
-
maxHeight = Math.round(rect.top - 16)
|
|
83
|
-
bottom = window.innerHeight - rect.top + 8
|
|
84
|
-
|
|
85
|
-
this.computedStyle = {
|
|
86
|
-
left: Math.round(rect.x) + 'px',
|
|
87
|
-
bottom: bottom + 'px',
|
|
88
|
-
maxHeight: maxHeight + 'px',
|
|
89
|
-
transformOrigin: 'bottom left'
|
|
90
|
-
}
|
|
91
|
-
break
|
|
92
|
-
|
|
93
|
-
case 'top-right':
|
|
94
|
-
|
|
95
|
-
maxHeight = Math.round(rect.top - 16)
|
|
96
|
-
bottom = window.innerHeight - rect.top + 8
|
|
97
|
-
|
|
98
|
-
this.computedStyle = {
|
|
99
|
-
right: (window.innerWidth - (Math.round(rect.x) + rect.width)) + 'px',
|
|
100
|
-
bottom: bottom + 'px',
|
|
101
|
-
maxHeight: maxHeight + 'px',
|
|
102
|
-
transformOrigin: 'bottom right'
|
|
103
|
-
}
|
|
104
|
-
break
|
|
105
|
-
|
|
106
|
-
default:
|
|
107
|
-
this.computedStyle = {
|
|
108
|
-
left: Math.round(rect.x) + 'px',
|
|
109
|
-
top: Math.round(rect.y + rect.height + 8) + 'px',
|
|
110
|
-
maxHeight: (window.innerHeight - Math.round(rect.y + rect.height + 16)) + 'px',
|
|
111
|
-
transformOrigin: 'top left'
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.onOpen()
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
else{
|
|
120
|
-
console.error('Undefined caller for context menu')
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else{
|
|
124
|
-
console.warn('Invalid context menu caller', this.caller)
|
|
125
|
-
}
|
|
61
|
+
this.open()
|
|
126
62
|
}
|
|
127
63
|
else{
|
|
128
64
|
window.removeEventListener('click', this.onDismiss)
|
|
@@ -162,10 +98,91 @@ export default {
|
|
|
162
98
|
}
|
|
163
99
|
this.computedStyle = null
|
|
164
100
|
this.$emit('dismiss')
|
|
101
|
+
this.isOpen = false
|
|
165
102
|
}
|
|
166
103
|
|
|
167
104
|
this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
|
|
168
105
|
this.$refs.contextMenu.classList.remove(this.$style.active)
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
open(caller){
|
|
109
|
+
|
|
110
|
+
if(caller){
|
|
111
|
+
this.isOpen = true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
caller = caller ?? this.caller
|
|
115
|
+
|
|
116
|
+
if(caller){
|
|
117
|
+
|
|
118
|
+
caller = caller.$el ? caller.$el :
|
|
119
|
+
caller instanceof HTMLElement ? caller : null
|
|
120
|
+
|
|
121
|
+
if(caller){
|
|
122
|
+
|
|
123
|
+
this.$nextTick(() => {
|
|
124
|
+
const rect = caller.getBoundingClientRect()
|
|
125
|
+
|
|
126
|
+
let maxHeight, bottom
|
|
127
|
+
|
|
128
|
+
switch(this.position){
|
|
129
|
+
|
|
130
|
+
case 'bottom-right':
|
|
131
|
+
this.computedStyle = {
|
|
132
|
+
right: (window.innerWidth - (Math.round(rect.x) + rect.width)) + 'px',
|
|
133
|
+
top: Math.round(rect.y + rect.height + 8) + 'px',
|
|
134
|
+
maxHeight: (window.innerHeight - Math.round(rect.y + rect.height + 16)) + 'px',
|
|
135
|
+
transformOrigin: 'top right'
|
|
136
|
+
}
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
case 'top-left':
|
|
140
|
+
|
|
141
|
+
maxHeight = Math.round(rect.top - 16)
|
|
142
|
+
bottom = window.innerHeight - rect.top + 8
|
|
143
|
+
|
|
144
|
+
this.computedStyle = {
|
|
145
|
+
left: Math.round(rect.x) + 'px',
|
|
146
|
+
bottom: bottom + 'px',
|
|
147
|
+
maxHeight: maxHeight + 'px',
|
|
148
|
+
transformOrigin: 'bottom left'
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
case 'top-right':
|
|
153
|
+
|
|
154
|
+
maxHeight = Math.round(rect.top - 16)
|
|
155
|
+
bottom = window.innerHeight - rect.top + 8
|
|
156
|
+
|
|
157
|
+
this.computedStyle = {
|
|
158
|
+
right: (window.innerWidth - (Math.round(rect.x) + rect.width)) + 'px',
|
|
159
|
+
bottom: bottom + 'px',
|
|
160
|
+
maxHeight: maxHeight + 'px',
|
|
161
|
+
transformOrigin: 'bottom right'
|
|
162
|
+
}
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
default:
|
|
166
|
+
this.computedStyle = {
|
|
167
|
+
left: Math.round(rect.x) + 'px',
|
|
168
|
+
top: Math.round(rect.y + rect.height + 8) + 'px',
|
|
169
|
+
maxHeight: (window.innerHeight - Math.round(rect.y + rect.height + 16)) + 'px',
|
|
170
|
+
transformOrigin: 'top left'
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.onOpen()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
else{
|
|
179
|
+
console.error('Undefined caller for context menu')
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else{
|
|
183
|
+
console.warn('Invalid context menu caller', this.caller)
|
|
184
|
+
}
|
|
185
|
+
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Modal :state="isOpen" width="480" height="560">
|
|
3
|
+
<template v-slot:head>
|
|
4
|
+
<div class="relative p-5">
|
|
5
|
+
<div v-if="step !== 3">
|
|
6
|
+
<h3>{{ title }}</h3>
|
|
7
|
+
<p v-if="step === 1" class="mt-1 text-text-400" v-html="description"></p>
|
|
8
|
+
<p v-else-if="step === 2" class="mt-1 text-text-400" v-html="description2"></p>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="absolute top-0 right-0 p-2">
|
|
11
|
+
<button type="button" class="p-2" @click="isOpen = false">
|
|
12
|
+
<svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
|
|
13
|
+
<path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
|
|
14
|
+
</svg>
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
<template v-slot:foot>
|
|
20
|
+
<div class="p-5" v-if="step === 2">
|
|
21
|
+
<Button type="button" class="w-[120px]" :disabled="!canImport" :state="isLoading ? 2 : 1" @click="proceedImport">Import</Button>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<div class="flex-1 p-5 flex flex-col items-center justify-center" v-if="step === 1">
|
|
26
|
+
<button type="button" @click="$refs.uploader.click()" class="rounded-3xl w-[200px] bg-base-300 px-6 aspect-square flex items-center justify-center text-center border-dashed border-[3px] border-text-100">
|
|
27
|
+
<div class="text-xl" v-if="!isLoading">
|
|
28
|
+
Klik untuk <br />
|
|
29
|
+
<span class="text-primary text-lg">Upload</span>
|
|
30
|
+
file
|
|
31
|
+
</div>
|
|
32
|
+
<div v-else class="flex items-center justify-center">
|
|
33
|
+
<svg class="animate-spin aspect-square w-[48px] text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
|
34
|
+
</div>
|
|
35
|
+
</button>
|
|
36
|
+
<input type="file" :accept="accept" ref="uploader" class="hidden" @change="onFileUploaded"/>
|
|
37
|
+
<br />
|
|
38
|
+
<br />
|
|
39
|
+
<br />
|
|
40
|
+
<br />
|
|
41
|
+
<div v-if="templateFiles.length > 0" class="flex flex-col gap-2">
|
|
42
|
+
<a v-for="(file, index) in templateFiles" :href="file" class="text-primary">
|
|
43
|
+
Download Sampel {{ templateFiles.length > 1 ? index + 1 : '' }}
|
|
44
|
+
</a>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="flex-1 p-5 flex flex-col gap-3" v-else-if="step === 2">
|
|
49
|
+
<div v-for="(key, index) in importData.keys" class="flex flex-row items-center gap-2">
|
|
50
|
+
<div class="w-[120px]">
|
|
51
|
+
<strong v-if="key.required">{{ key.text }}*</strong>
|
|
52
|
+
<label v-else>{{ key.text }}</label>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="flex-1">
|
|
55
|
+
<Dropdown v-model="importData.keys[index].value">
|
|
56
|
+
<option disabled selected>Pilih Kolom</option>
|
|
57
|
+
<option v-for="column in importData.columns" :value="column">{{ column }}</option>
|
|
58
|
+
</Dropdown>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="flex-1 flex items-center justify-center" v-else-if="step === 3">
|
|
64
|
+
<div class="flex flex-col items-center justify-center gap-1">
|
|
65
|
+
<svg width="96" height="96" viewBox="0 0 24 24" class="fill-green-600" xmlns="http://www.w3.org/2000/svg">
|
|
66
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12C3.5 7.30558 7.30558 3.5 12 3.5C14.5863 3.5 16.9028 4.65509 18.4618 6.47753L11.1768 13.7626C11.0791 13.8602 10.9209 13.8602 10.8232 13.7626L8.03033 10.9697C7.73744 10.6768 7.26256 10.6768 6.96967 10.9697C6.67678 11.2626 6.67678 11.7374 6.96967 12.0303L9.76256 14.8232C10.446 15.5066 11.554 15.5066 12.2374 14.8232L19.3437 7.717C20.0787 8.97466 20.5 10.4381 20.5 12Z" />
|
|
67
|
+
</svg>
|
|
68
|
+
<h3>{{ importData.result.title ?? 'Data berhasil diimport' }}</h3>
|
|
69
|
+
<p class="mb-4 text-text-400 text-center" v-html="importData.result.description"></p>
|
|
70
|
+
<div class="p-2 text-sm text-green-500 border-green-500 border-[1px] rounded-lg">
|
|
71
|
+
Total Data: {{ importData.result.count }}
|
|
72
|
+
</div>
|
|
73
|
+
<div class="px-6 my-4">
|
|
74
|
+
<VirtualTable v-if="warnings.length > 0" :columns="warningColumns" :items="warnings" class="h-[200px]"></VirtualTable>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="flex flex-row gap-3">
|
|
78
|
+
<Button class="w-full font-bold w-[160px]" @click="close">OK</Button>
|
|
79
|
+
<Button v-if="warnings.length > 0" variant="outline" class="w-full font-bold w-[60px]" @click="downloadWarning">
|
|
80
|
+
<svg width="21" height="21" viewBox="0 0 24 24" class="fill-text-300 mr-1" xmlns="http://www.w3.org/2000/svg">
|
|
81
|
+
<path d="M12.7519 4.99959C12.7519 4.58537 12.4161 4.24959 12.0019 4.24959C11.5877 4.24959 11.2519 4.58537 11.2519 4.99959V13.6894L8.03216 10.4697C7.73927 10.1768 7.26439 10.1768 6.9715 10.4697C6.67861 10.7626 6.67861 11.2374 6.9715 11.5303L10.7644 15.3233C11.4479 16.0067 12.5559 16.0067 13.2393 15.3233L17.0303 11.5323C17.3232 11.2394 17.3232 10.7645 17.0303 10.4716C16.7374 10.1787 16.2625 10.1787 15.9696 10.4716L12.7519 13.6893V4.99959Z" />
|
|
82
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 14.25C4.41421 14.25 4.75 14.5858 4.75 15V17C4.75 17.6904 5.30964 18.25 6 18.25H18C18.6904 18.25 19.25 17.6904 19.25 17V15C19.25 14.5858 19.5858 14.25 20 14.25C20.4142 14.25 20.75 14.5858 20.75 15V17C20.75 18.5188 19.5188 19.75 18 19.75H6C4.48122 19.75 3.25 18.5188 3.25 17V15C3.25 14.5858 3.58579 14.25 4 14.25Z" />
|
|
83
|
+
</svg>
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
</Modal>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<script>
|
|
93
|
+
|
|
94
|
+
import axios from "axios";
|
|
95
|
+
|
|
96
|
+
export default{
|
|
97
|
+
|
|
98
|
+
inject: [ 'socketEmit', 'socket', 'toast' ],
|
|
99
|
+
|
|
100
|
+
props: {
|
|
101
|
+
|
|
102
|
+
accept: { type: String, default: ".csv,.xlsx" },
|
|
103
|
+
|
|
104
|
+
title: { type: String, default: "Untitled Import" },
|
|
105
|
+
|
|
106
|
+
description: { type: String, default: "Import csv or xlsx file" },
|
|
107
|
+
|
|
108
|
+
description2: { type: String, default: "Map column to fields" },
|
|
109
|
+
|
|
110
|
+
urlAnalyse: { type: String },
|
|
111
|
+
|
|
112
|
+
urlImport: { type: String },
|
|
113
|
+
|
|
114
|
+
completeDescription: { type: String, default: "" },
|
|
115
|
+
|
|
116
|
+
warningFile: { type: String, default: "warning.txt" },
|
|
117
|
+
|
|
118
|
+
templateFile: String
|
|
119
|
+
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
computed: {
|
|
123
|
+
|
|
124
|
+
canImport(){
|
|
125
|
+
return (this.importData.keys ?? []).filter((_) => _.required && !_.value).length < 1
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
warnings(){
|
|
129
|
+
return (this.importData.result ?? {}).warnings ?? []
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
templateFiles(){
|
|
133
|
+
return (this.templateFile ?? '').split(',')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
data(){
|
|
139
|
+
return {
|
|
140
|
+
isOpen: false,
|
|
141
|
+
isLoading: false,
|
|
142
|
+
step: 1,
|
|
143
|
+
importData: null,
|
|
144
|
+
warningColumns: [
|
|
145
|
+
{ key:"text", label:"Warning", width:"420px", visible:true }
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
methods: {
|
|
151
|
+
|
|
152
|
+
onFileUploaded(){
|
|
153
|
+
|
|
154
|
+
const data = new FormData()
|
|
155
|
+
data.append('file', this.$refs.uploader.files[0])
|
|
156
|
+
data.append('filename', this.$refs.uploader.files[0].name)
|
|
157
|
+
|
|
158
|
+
this.isLoading = true
|
|
159
|
+
axios({
|
|
160
|
+
url: import.meta.env.VITE_API_HOST + this.urlAnalyse,
|
|
161
|
+
method: 'POST',
|
|
162
|
+
data
|
|
163
|
+
})
|
|
164
|
+
.then(({ data:res }) => {
|
|
165
|
+
|
|
166
|
+
this.step = 2
|
|
167
|
+
this.importData = res
|
|
168
|
+
|
|
169
|
+
this.importData.keys.forEach((key) => {
|
|
170
|
+
|
|
171
|
+
const labels = [
|
|
172
|
+
...(key.labels ?? []),
|
|
173
|
+
key.text
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
const value = res.columns.filter((_) => labels.includes(_)).pop()
|
|
177
|
+
if(value) key.value = value
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
.catch((e) => {
|
|
181
|
+
this.toast(e.response.data)
|
|
182
|
+
})
|
|
183
|
+
.finally(() => {
|
|
184
|
+
this.isLoading = false
|
|
185
|
+
})
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
open(){
|
|
189
|
+
this.reset()
|
|
190
|
+
this.isOpen = true
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
close(){
|
|
194
|
+
this.isOpen = false
|
|
195
|
+
this.$emit('dismiss')
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
reset(){
|
|
199
|
+
this.step = 1
|
|
200
|
+
this.importData = {}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
proceedImport(){
|
|
204
|
+
|
|
205
|
+
this.isLoading = true
|
|
206
|
+
axios({
|
|
207
|
+
url: import.meta.env.VITE_API_HOST + this.urlImport,
|
|
208
|
+
method: 'POST',
|
|
209
|
+
data: this.importData
|
|
210
|
+
})
|
|
211
|
+
.then(({ data:res }) => {
|
|
212
|
+
this.importData.result = res
|
|
213
|
+
this.step = 3
|
|
214
|
+
})
|
|
215
|
+
.catch((e) => {
|
|
216
|
+
this.toast(e.response.data)
|
|
217
|
+
})
|
|
218
|
+
.finally(() => {
|
|
219
|
+
this.isLoading = false
|
|
220
|
+
})
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
downloadWarning(){
|
|
224
|
+
const warningText = this.importData.result.warnings.map((_) => _.text).join("\n")
|
|
225
|
+
this.$download("data:attachment/text," + encodeURI(warningText), this.warningFile)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
<style module>
|
|
235
|
+
|
|
236
|
+
.comp{
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
</style>
|
package/src/components/Radio.vue
CHANGED
package/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {defineAsyncComponent, ref} from "vue"
|
|
2
|
-
import {observeInit} from './
|
|
3
|
-
import throttle from "lodash
|
|
4
|
-
//import './index.css'
|
|
2
|
+
import {observeInit} from './utils/helpers.mjs'
|
|
3
|
+
import throttle from "lodash/throttle"
|
|
5
4
|
|
|
6
5
|
const calculateIsMobile = () => {
|
|
7
6
|
if(typeof window === 'undefined') return false
|
|
@@ -63,6 +62,7 @@ export default{
|
|
|
63
62
|
app.component('Image', defineAsyncComponent(() => import("./components/Image.vue")))
|
|
64
63
|
app.component('ImagePreview', defineAsyncComponent(() => import("./components/ImagePreview.vue")))
|
|
65
64
|
app.component('ImageFullScreen', defineAsyncComponent(() => import("./components/ImageFullScreen.vue")))
|
|
65
|
+
app.component('ImportModal', defineAsyncComponent(() => import("./components/ImportModal.vue")))
|
|
66
66
|
app.component('ListPage1', defineAsyncComponent(() => import("./components/ListPage1.vue")))
|
|
67
67
|
app.component('ListPage2', defineAsyncComponent(() => import("./components/ListPage2.vue")))
|
|
68
68
|
app.component('ListPage1Filter', defineAsyncComponent(() => import("./components/ListPage1Filter.vue")))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const md5 = require("md5");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
|
|
4
|
+
const saveBuffer = async(buffer, dir = '/storage/files/images') => {
|
|
5
|
+
|
|
6
|
+
const { fileTypeFromBuffer } = await import('file-type')
|
|
7
|
+
|
|
8
|
+
const type = await fileTypeFromBuffer(buffer)
|
|
9
|
+
|
|
10
|
+
const storagePath = process.env.ROOT_PATH + (dir)
|
|
11
|
+
const ext = type && type.ext ? '.' + type.ext : ''
|
|
12
|
+
const filename = md5(buffer) + ext
|
|
13
|
+
const to = storagePath + '/' + filename
|
|
14
|
+
|
|
15
|
+
fs.writeFileSync(to, buffer)
|
|
16
|
+
|
|
17
|
+
return filename
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
saveBuffer
|
|
22
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
+
|
|
1
2
|
let __uid = new Date().getTime()
|
|
2
3
|
|
|
3
4
|
const uid = function(prefix){
|
|
4
5
|
return (prefix ?? '') + __uid++;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
console.log(text)
|
|
9
|
-
}
|
|
8
|
+
const csvToArray = (str, delimiter = null) => {
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if('IntersectionObserver' in window) {
|
|
14
|
-
app.config.globalProperties.$observe = observeInit()
|
|
15
|
-
debug('observer init done')
|
|
16
|
-
}
|
|
10
|
+
if(!delimiter){
|
|
11
|
+
delimiter = str.indexOf(';') >= 0 ? ';' : ','
|
|
17
12
|
}
|
|
18
|
-
}
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
str = str.trim()
|
|
15
|
+
|
|
16
|
+
const headers = str.slice(0, str.indexOf("\n")).split(delimiter);
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
const windowWidth = window.innerWidth
|
|
24
|
-
if(windowWidth >= 1536)
|
|
25
|
-
size = '2xl';
|
|
26
|
-
else if(windowWidth >= 1280)
|
|
27
|
-
size = 'xl';
|
|
28
|
-
else if(windowWidth >= 1024)
|
|
29
|
-
size = 'lg';
|
|
30
|
-
else if(windowWidth >= 768)
|
|
31
|
-
size = 'md';
|
|
18
|
+
const rows = str.slice(str.indexOf("\n") + 1).split("\n");
|
|
32
19
|
|
|
33
|
-
|
|
20
|
+
const arr = rows.map(function (row) {
|
|
21
|
+
const values = row.split(delimiter);
|
|
22
|
+
let el = {}
|
|
23
|
+
if(values.length === headers.length){
|
|
24
|
+
el = headers.reduce(function (object, header, index) {
|
|
25
|
+
object[header] = values[index];
|
|
26
|
+
return object;
|
|
27
|
+
}, {});
|
|
28
|
+
}
|
|
29
|
+
return el;
|
|
30
|
+
})
|
|
31
|
+
.filter((_) => Object.keys(_).length > 0);
|
|
32
|
+
|
|
33
|
+
return arr;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const parseBoolean = function(exp){
|
|
@@ -39,11 +39,9 @@ const parseBoolean = function(exp){
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export {
|
|
42
|
-
observerPlugins,
|
|
43
|
-
observeInit,
|
|
44
|
-
debug,
|
|
45
42
|
uid,
|
|
46
|
-
|
|
43
|
+
observeInit,
|
|
44
|
+
csvToArray,
|
|
47
45
|
parseBoolean
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const md5 = require("md5");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const AdmZip = require("adm-zip");
|
|
4
|
+
const glob = require("glob");
|
|
5
|
+
const Exceljs = require("exceljs")
|
|
6
|
+
const { saveBuffer } = require('./helpers.js')
|
|
7
|
+
|
|
8
|
+
const analyseRequest = async(req) => {
|
|
9
|
+
|
|
10
|
+
const file = req.files[0]
|
|
11
|
+
const ext = file.originalname.split('.').slice(-1).pop()
|
|
12
|
+
|
|
13
|
+
let folderName = md5('motor-import-analyse' + new Date().getTime())
|
|
14
|
+
let xlsxFileType, xlsxFile
|
|
15
|
+
|
|
16
|
+
fs.mkdirSync(process.env.ROOT_PATH + '/storage/files/temp/' + folderName)
|
|
17
|
+
|
|
18
|
+
if(ext === 'zip'){
|
|
19
|
+
|
|
20
|
+
const zip = new AdmZip(file.buffer)
|
|
21
|
+
zip.extractAllTo(process.env.ROOT_PATH + '/storage/files/temp/' + folderName, true)
|
|
22
|
+
|
|
23
|
+
const res = glob.sync(process.env.ROOT_PATH + '/storage/files/temp/' + folderName + '/**/*(*.xlsx|*.csv)')
|
|
24
|
+
|
|
25
|
+
if(res.length < 1)
|
|
26
|
+
throw new ValidationError({ file: [ 'File excel atau csv tidak ditemukan' ]})
|
|
27
|
+
else if(res.length > 1)
|
|
28
|
+
throw new ValidationError({ file: [ 'File excel atau csv tidak melebihi 1' ]})
|
|
29
|
+
else{
|
|
30
|
+
xlsxFile = res[0]
|
|
31
|
+
xlsxFileType = xlsxFile.split('.').slice(-1).pop()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
else{
|
|
36
|
+
xlsxFileType = ext
|
|
37
|
+
const filename = await saveBuffer(file.buffer, '/storage/files/temp/' + folderName)
|
|
38
|
+
xlsxFile = process.env.ROOT_PATH + '/storage/files/temp/' + folderName + '/' + filename
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const workbook = new Exceljs.Workbook();
|
|
42
|
+
switch(xlsxFileType){
|
|
43
|
+
|
|
44
|
+
case 'xlsx':
|
|
45
|
+
await workbook.xlsx.readFile(xlsxFile)
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
case 'csv':
|
|
49
|
+
await workbook.csv.readFile(xlsxFile, {
|
|
50
|
+
parserOptions: {
|
|
51
|
+
delimiter: ';'
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const worksheet = workbook.worksheets.length > 0 ? workbook.getWorksheet(1) : undefined
|
|
58
|
+
if(!worksheet)
|
|
59
|
+
throw new ValidationError({ worksheet:[ 'File tidak dapat dibaca' ] })
|
|
60
|
+
|
|
61
|
+
const columns = []
|
|
62
|
+
const header = worksheet.getRow(1)
|
|
63
|
+
if(!header)
|
|
64
|
+
throw new ValidationError({ worksheet:[ 'File tidak dapat dibaca' ] })
|
|
65
|
+
header.eachCell((cell) => {
|
|
66
|
+
columns.push(cell.value)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
columns,
|
|
71
|
+
folderName,
|
|
72
|
+
xlsxFileType,
|
|
73
|
+
xlsxFile
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const importRequest = async(req) => {
|
|
78
|
+
|
|
79
|
+
const { columns, folderName, xlsxFileType, xlsxFile, keys } = req.body
|
|
80
|
+
|
|
81
|
+
keys.forEach((key) => {
|
|
82
|
+
if (key.required && !key.value)
|
|
83
|
+
throw new ValidationError({name: ['Kolom ' + key.text + ' harus diisi']})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const rows = []
|
|
87
|
+
const cols = {}
|
|
88
|
+
const workbook = new Exceljs.Workbook();
|
|
89
|
+
switch(xlsxFileType){
|
|
90
|
+
|
|
91
|
+
case 'xlsx':
|
|
92
|
+
await workbook.xlsx.readFile(xlsxFile)
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
case 'csv':
|
|
96
|
+
await workbook.csv.readFile(xlsxFile, {
|
|
97
|
+
parserOptions: {
|
|
98
|
+
delimiter: ';'
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const worksheet = workbook.getWorksheet(1)
|
|
105
|
+
worksheet.eachRow((row, index) => {
|
|
106
|
+
if(index === 1){
|
|
107
|
+
row.eachCell((cell, index) => {
|
|
108
|
+
|
|
109
|
+
let mapped = false
|
|
110
|
+
keys.forEach((key) => {
|
|
111
|
+
if(key.value === cell.value){
|
|
112
|
+
cols[key.key] = index
|
|
113
|
+
mapped = true
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
if(!mapped){
|
|
118
|
+
cols[cell.value] = index
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
else{
|
|
123
|
+
const obj = {}
|
|
124
|
+
|
|
125
|
+
for(let key in cols){
|
|
126
|
+
const cell = row.getCell(cols[key])
|
|
127
|
+
obj[key] = cell.formulaType === 1 ? cell.result : cell.value
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
rows.push(obj)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
//console.log(JSON.stringify(rows, null, 2))
|
|
135
|
+
|
|
136
|
+
const images = glob.sync(process.env.ROOT_PATH + '/storage/files/temp/' + folderName +
|
|
137
|
+
'/**/*(*.jpg|*.jpeg|*.png|*.webp)')
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
rows,
|
|
141
|
+
images,
|
|
142
|
+
columns,
|
|
143
|
+
keys
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
analyseRequest,
|
|
150
|
+
importRequest
|
|
151
|
+
}
|
package/src/helpers.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const csvToArray = (str, delimiter = null) => {
|
|
2
|
-
|
|
3
|
-
if(!delimiter){
|
|
4
|
-
delimiter = str.indexOf(';') >= 0 ? ';' : ','
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
str = str.trim()
|
|
8
|
-
|
|
9
|
-
const headers = str.slice(0, str.indexOf("\n")).split(delimiter);
|
|
10
|
-
|
|
11
|
-
const rows = str.slice(str.indexOf("\n") + 1).split("\n");
|
|
12
|
-
|
|
13
|
-
const arr = rows.map(function (row) {
|
|
14
|
-
const values = row.split(delimiter);
|
|
15
|
-
let el = {}
|
|
16
|
-
if(values.length === headers.length){
|
|
17
|
-
el = headers.reduce(function (object, header, index) {
|
|
18
|
-
object[header] = values[index];
|
|
19
|
-
return object;
|
|
20
|
-
}, {});
|
|
21
|
-
}
|
|
22
|
-
return el;
|
|
23
|
-
})
|
|
24
|
-
.filter((_) => Object.keys(_).length > 0);
|
|
25
|
-
|
|
26
|
-
return arr;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export {
|
|
30
|
-
csvToArray
|
|
31
|
-
}
|