frappe-ui 0.1.225 → 0.1.227
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/frappe/DataImport/DataImport.vue +25 -20
- package/frappe/DataImport/DataImportList.vue +20 -8
- package/frappe/DataImport/ImportSteps.vue +6 -6
- package/frappe/DataImport/MappingStep.vue +36 -16
- package/frappe/DataImport/PreviewStep.vue +26 -31
- package/frappe/DataImport/TemplateModal.vue +9 -15
- package/frappe/DataImport/UploadStep.vue +58 -54
- package/frappe/DataImport/dataImport.ts +4 -5
- package/frappe/DataImport/types.ts +5 -2
- package/frappe/Link/types.ts +1 -1
- package/frappe/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/components/Checkbox/Checkbox.vue +1 -0
- package/src/components/Combobox/Combobox.vue +1 -0
- package/src/components/FormControl/FormControl.story.vue +2 -2
- package/src/components/FormControl/FormControl.vue +10 -0
- package/src/components/FormControl/types.ts +1 -1
- package/src/components/Tabs/Tabs.story.vue +12 -9
- package/src/components/Tabs/Tabs.vue +61 -54
- package/src/components/Tabs/types.ts +11 -0
- package/src/components/TextEditor/link-extension.ts +1 -1
- package/src/index.ts +0 -2
- package/vite/index.js +16 -0
- package/frappe/components/Link/Link.vue +0 -162
- package/src/components/Tabs/TabList.vue +0 -82
- package/src/components/Tabs/TabPanel.vue +0 -17
- package/src/components/Tabs/Tabs.story.md +0 -97
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
class="sticky flex items-center justify-between space-x-28 top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
|
4
4
|
>
|
|
5
5
|
<Breadcrumbs :items="breadcrumbs" />
|
|
6
|
-
<ImportSteps class="flex-1" v-if="step != 'list'" :data="data" :step="step" @updateStep="updateStep" />
|
|
6
|
+
<ImportSteps class="flex-1 hidden lg:flex" v-if="step != 'list'" :data="data" :step="step" @updateStep="updateStep" />
|
|
7
7
|
</header>
|
|
8
8
|
<div>
|
|
9
|
+
<ImportSteps class="flex-1 lg:hidden w-[90%] mx-auto mt-5" v-if="step != 'list'" :data="data" :step="step" @updateStep="updateStep" />
|
|
10
|
+
|
|
9
11
|
<DataImportList
|
|
10
12
|
v-if="step === 'list'"
|
|
11
13
|
:dataImports="dataImports"
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
<MappingStep
|
|
25
27
|
v-else-if="step === 'map'"
|
|
26
28
|
:dataImports="dataImports"
|
|
27
|
-
:data="data"
|
|
29
|
+
:data="data as DataImport"
|
|
28
30
|
:fields="fields"
|
|
29
31
|
@updateStep="updateStep"
|
|
30
32
|
/>
|
|
@@ -32,9 +34,9 @@
|
|
|
32
34
|
<PreviewStep
|
|
33
35
|
v-else-if="step === 'preview'"
|
|
34
36
|
:dataImports="dataImports"
|
|
35
|
-
:data="data"
|
|
37
|
+
:data="data as DataImport"
|
|
36
38
|
:fields="fields"
|
|
37
|
-
:doctypeMap="doctypeMap"
|
|
39
|
+
:doctypeMap="doctypeMap as Record<string, { title: string; listRoute?: string; pageRoute?: string }>"
|
|
38
40
|
@updateStep="updateStep"
|
|
39
41
|
/>
|
|
40
42
|
</div>
|
|
@@ -52,7 +54,7 @@ import PreviewStep from './PreviewStep.vue'
|
|
|
52
54
|
import UploadStep from './UploadStep.vue'
|
|
53
55
|
|
|
54
56
|
const route = useRoute()
|
|
55
|
-
const step = ref('list')
|
|
57
|
+
const step = ref<'upload' | 'map' | 'list' | 'preview'>('list')
|
|
56
58
|
const data = ref<DataImport | null>(null)
|
|
57
59
|
|
|
58
60
|
const props = defineProps<Partial<DataImportProps>>()
|
|
@@ -76,7 +78,7 @@ const props = defineProps<Partial<DataImportProps>>()
|
|
|
76
78
|
|
|
77
79
|
const fields = createResource({
|
|
78
80
|
url: "frappe.desk.form.load.getdoctype",
|
|
79
|
-
makeParams: (values) => {
|
|
81
|
+
makeParams: (values: { doctype: string }) => {
|
|
80
82
|
return {
|
|
81
83
|
doctype: values.doctype,
|
|
82
84
|
with_parent: 1,
|
|
@@ -86,7 +88,7 @@ const fields = createResource({
|
|
|
86
88
|
})
|
|
87
89
|
|
|
88
90
|
watch(
|
|
89
|
-
() => [props, dataImports.data],
|
|
91
|
+
() => [route.params, props, dataImports.data],
|
|
90
92
|
() => {
|
|
91
93
|
if (!dataImports.data?.length) return
|
|
92
94
|
if (props.doctype) {
|
|
@@ -103,9 +105,11 @@ watch(
|
|
|
103
105
|
} else {
|
|
104
106
|
step.value = 'preview'
|
|
105
107
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
if (data.value?.reference_doctype) {
|
|
109
|
+
fields.reload({
|
|
110
|
+
doctype: data.value?.reference_doctype,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
109
113
|
}
|
|
110
114
|
},
|
|
111
115
|
{ immediate: true },
|
|
@@ -119,15 +123,10 @@ watch(() => route.query, () => {
|
|
|
119
123
|
|
|
120
124
|
const updateData = () => {
|
|
121
125
|
data.value = dataImports.data?.find(
|
|
122
|
-
(di) => di.name === props.importName,
|
|
126
|
+
(di: DataImport) => di.name === props.importName,
|
|
123
127
|
) || null
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
const doctypeTitle = computed(() => {
|
|
127
|
-
let doctype = props.doctype || data.value?.reference_doctype
|
|
128
|
-
return props.doctypeMap?.[doctype || '']?.title || doctype || ''
|
|
129
|
-
})
|
|
130
|
-
|
|
131
130
|
const updateStep = (newStep: 'list' | 'upload' | 'map' | 'preview', newData: DataImport) => {
|
|
132
131
|
step.value = newStep
|
|
133
132
|
if (newData) {
|
|
@@ -135,16 +134,22 @@ const updateStep = (newStep: 'list' | 'upload' | 'map' | 'preview', newData: Dat
|
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
136
|
|
|
137
|
+
const doctypeTitle = computed(() => {
|
|
138
|
+
let doctype = props.doctype || data.value?.reference_doctype
|
|
139
|
+
return props.doctypeMap?.[doctype || '']?.title || doctype || ''
|
|
140
|
+
})
|
|
141
|
+
|
|
138
142
|
const breadcrumbs = computed(() => {
|
|
139
143
|
let crumbs = [
|
|
140
144
|
{
|
|
141
145
|
label: 'Data Import',
|
|
142
146
|
route: {
|
|
143
|
-
name: 'DataImportList',
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
name: 'DataImportList',
|
|
148
|
+
query: {
|
|
149
|
+
step: 'list'
|
|
150
|
+
}
|
|
151
|
+
},
|
|
146
152
|
},
|
|
147
|
-
}
|
|
148
153
|
]
|
|
149
154
|
|
|
150
155
|
if (step.value !== 'list') {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex min-h-0 flex-col text-base py-5 w-[700px] mx-auto">
|
|
2
|
+
<div class="flex min-h-0 flex-col text-base py-5 w-[90%] lg:w-[700px] mx-auto">
|
|
3
3
|
<div class="flex items-center justify-between">
|
|
4
4
|
<div>
|
|
5
5
|
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
<div v-if="dataImports.data?.length" class="overflow-y-scroll">
|
|
35
35
|
<div class="divide-y">
|
|
36
|
-
<div class="grid grid-cols-[85%,20%] items-center text-sm text-ink-gray-5 py-1.5 mx-2 my-0.5 px-1">
|
|
36
|
+
<div class="grid grid-cols-[75%,20%] lg:grid-cols-[85%,20%] items-center text-sm text-ink-gray-5 py-1.5 mx-2 my-0.5 px-1">
|
|
37
37
|
<div>
|
|
38
38
|
Name
|
|
39
39
|
</div>
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
<div
|
|
45
45
|
v-for="dataImport in dataImports.data"
|
|
46
|
-
@click="() => redirectToImport(dataImport.name)"
|
|
47
|
-
class="grid grid-cols-[85%,20%] items-center cursor-pointer py-2.5 px-1 mx-2"
|
|
46
|
+
@click="() => redirectToImport(dataImport.name!)"
|
|
47
|
+
class="grid grid-cols-[75%,20%] lg:grid-cols-[85%,20%] items-center cursor-pointer py-2.5 px-1 mx-2"
|
|
48
48
|
>
|
|
49
49
|
<div class="space-y-1">
|
|
50
50
|
<div class="text-ink-gray-7">
|
|
@@ -100,11 +100,12 @@
|
|
|
100
100
|
<script setup lang="ts">
|
|
101
101
|
import { computed, ref, watch } from 'vue'
|
|
102
102
|
import { useRouter } from 'vue-router'
|
|
103
|
-
import type { DataImports } from './types'
|
|
103
|
+
import type { DataImports, DataImport } from './types'
|
|
104
104
|
import { dayjs } from "../../src/utils/dayjs"
|
|
105
105
|
import { getBadgeColor } from "./dataImport"
|
|
106
106
|
import Badge from '../../src/components/Badge/Badge.vue'
|
|
107
107
|
import type { BadgeProps } from '../../src/components/Badge/types'
|
|
108
|
+
import { toast } from "../../src/components/Toast/index"
|
|
108
109
|
import Button from '../../src/components/Button/Button.vue'
|
|
109
110
|
import Dialog from '../../src/components/Dialog/Dialog.vue'
|
|
110
111
|
import FeatherIcon from '../../src/components/FeatherIcon.vue'
|
|
@@ -137,10 +138,12 @@ watch([search, importStatus], ([newSearch, newStatus]) => {
|
|
|
137
138
|
props.dataImports.reload()
|
|
138
139
|
})
|
|
139
140
|
|
|
140
|
-
const createDataImport = (close) => {
|
|
141
|
+
const createDataImport = (close: () => void) => {
|
|
141
142
|
props.dataImports.insert.submit({
|
|
142
|
-
reference_doctype: doctypeForImport.value
|
|
143
|
+
reference_doctype: doctypeForImport.value!,
|
|
143
144
|
import_type: 'Insert New Records',
|
|
145
|
+
mute_emails: true,
|
|
146
|
+
status: 'Pending',
|
|
144
147
|
}, {
|
|
145
148
|
onSuccess(data: DataImport) {
|
|
146
149
|
router.replace({
|
|
@@ -150,11 +153,20 @@ const createDataImport = (close) => {
|
|
|
150
153
|
},
|
|
151
154
|
})
|
|
152
155
|
close()
|
|
156
|
+
},
|
|
157
|
+
onError(error: any) {
|
|
158
|
+
console.error(error)
|
|
159
|
+
toast.error(error.messages?.[0] || error)
|
|
153
160
|
}
|
|
154
161
|
})
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
const redirectToImport = (importName: string) => {
|
|
158
|
-
|
|
165
|
+
router.replace({
|
|
166
|
+
name: 'DataImport',
|
|
167
|
+
params: {
|
|
168
|
+
importName
|
|
169
|
+
},
|
|
170
|
+
})
|
|
159
171
|
}
|
|
160
172
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex items-center space-x-10">
|
|
3
|
-
<div class="flex items-center space-x-2 text-ink-gray-5 cursor-pointer"
|
|
2
|
+
<div class="flex items-center space-x-3 lg:space-x-10 text-xs lg:text-base">
|
|
3
|
+
<div class="flex items-center space-x-1 lg:space-x-2 text-ink-gray-5 cursor-pointer"
|
|
4
4
|
:class="{
|
|
5
5
|
'text-ink-gray-9 font-semibold': onUploadStep
|
|
6
6
|
}"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
Upload File
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
|
-
<div class="flex items-center space-x-2 text-ink-gray-5"
|
|
23
|
+
<div class="flex items-center space-x-1 lg:space-x-2 text-ink-gray-5"
|
|
24
24
|
:class="{
|
|
25
25
|
'text-ink-gray-9 font-semibold': onMapStep,
|
|
26
26
|
'cursor-pointer': uploadStepCompleted
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
</span>
|
|
39
39
|
</div>
|
|
40
40
|
<div>
|
|
41
|
-
Map
|
|
41
|
+
Map Data
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
|
-
<div class="flex items-center space-x-2 text-ink-gray-5"
|
|
44
|
+
<div class="flex items-center space-x-1 lg:space-x-2 text-ink-gray-5"
|
|
45
45
|
:class="{
|
|
46
46
|
'text-ink-gray-9 font-semibold': onPreviewStep,
|
|
47
47
|
'cursor-pointer': uploadStepCompleted
|
|
@@ -73,7 +73,7 @@ const emit = defineEmits(['updateStep'])
|
|
|
73
73
|
|
|
74
74
|
const props = defineProps<{
|
|
75
75
|
data: DataImport | null
|
|
76
|
-
step: 'list' | '
|
|
76
|
+
step: 'list' | 'upload' | 'map' | 'preview'
|
|
77
77
|
}>()
|
|
78
78
|
|
|
79
79
|
const onUploadStep = computed(() => {
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="w-[700px] mx-auto
|
|
3
|
-
<div class="flex
|
|
2
|
+
<div class="w-[85%] lg:w-[700px] mx-auto py-12 space-y-8">
|
|
3
|
+
<div class="flex justify-between">
|
|
4
4
|
<div class="space-y-2">
|
|
5
5
|
<div class="text-lg font-semibold text-ink-gray-9">
|
|
6
|
-
|
|
6
|
+
<span>
|
|
7
|
+
Map Data
|
|
8
|
+
</span>
|
|
9
|
+
<Badge v-if="data?.status" :theme="getBadgeColor(data?.status)">
|
|
10
|
+
{{ data?.status }}
|
|
11
|
+
</Badge>
|
|
7
12
|
</div>
|
|
8
|
-
<div>
|
|
13
|
+
<div class="leading-5">
|
|
9
14
|
Change the mapping of columns from your file to fields in the system
|
|
10
15
|
</div>
|
|
11
16
|
</div>
|
|
12
17
|
|
|
13
|
-
<div class="space-x-2">
|
|
14
|
-
<Button label="
|
|
18
|
+
<div class="flex flex-col lg:flex-row space-y-2 lg:space-x-2 lg:space-y-0">
|
|
19
|
+
<Button v-if="mappingUpdated" label="Reset Mapping" @click="resetMapping" />
|
|
15
20
|
<Button label="Continue" variant="solid" @click="$emit('updateStep', 'preview')" />
|
|
16
21
|
</div>
|
|
17
22
|
</div>
|
|
@@ -32,7 +37,7 @@
|
|
|
32
37
|
:model-value="columnMappings[columnsFromFile[i - 1]]"
|
|
33
38
|
:options="columnsFromSystem"
|
|
34
39
|
placeholder="Select field"
|
|
35
|
-
@update:model-value="(val) => updateColumnMappings(i, val)"
|
|
40
|
+
@update:model-value="(val: any) => updateColumnMappings(i, val)"
|
|
36
41
|
/>
|
|
37
42
|
</template>
|
|
38
43
|
</div>
|
|
@@ -41,16 +46,17 @@
|
|
|
41
46
|
</template>
|
|
42
47
|
<script setup lang="ts">
|
|
43
48
|
import type { DataImport, DataImports } from './types';
|
|
44
|
-
import { fieldsToIgnore, getPreviewData } from './dataImport'
|
|
49
|
+
import { fieldsToIgnore, getBadgeColor, getPreviewData } from './dataImport'
|
|
45
50
|
import { computed, nextTick, onMounted, ref } from 'vue';
|
|
51
|
+
import { toast } from "../../src/components/Toast/index"
|
|
46
52
|
import Autocomplete from '../../src/components/Autocomplete/Autocomplete.vue';
|
|
53
|
+
import Badge from '../../src/components/Badge/Badge.vue';
|
|
47
54
|
import Button from '../../src/components/Button/Button.vue';
|
|
48
|
-
import FeatherIcon from '../../src/components/FeatherIcon.vue'
|
|
49
|
-
import Link from "../Link/Link.vue"
|
|
50
55
|
|
|
51
56
|
const previewData = ref<any>(null);
|
|
52
57
|
const emit = defineEmits(['updateStep'])
|
|
53
58
|
const columnMappings = ref<Record<string, string>>({});
|
|
59
|
+
const mappingUpdated = ref(false);
|
|
54
60
|
|
|
55
61
|
const props = defineProps<{
|
|
56
62
|
dataImports: DataImports
|
|
@@ -59,7 +65,7 @@ const props = defineProps<{
|
|
|
59
65
|
}>()
|
|
60
66
|
|
|
61
67
|
onMounted(async () => {
|
|
62
|
-
previewData.value = await getPreviewData(props.data.name
|
|
68
|
+
previewData.value = await getPreviewData(props.data.name!, props.data.import_file, props.data.google_sheets_url);
|
|
63
69
|
initializeColumnMappings();
|
|
64
70
|
});
|
|
65
71
|
|
|
@@ -69,6 +75,9 @@ const initializeColumnMappings = () => {
|
|
|
69
75
|
if (props.data?.template_options)
|
|
70
76
|
columnToFieldMap = JSON.parse(props.data?.template_options)?.["column_to_field_map"];
|
|
71
77
|
|
|
78
|
+
if (Object.keys(columnToFieldMap).length > 0)
|
|
79
|
+
mappingUpdated.value = true;
|
|
80
|
+
|
|
72
81
|
columnsFromFile.value.forEach((col: string, index: number) => {
|
|
73
82
|
if (columnToFieldMap && columnToFieldMap[index])
|
|
74
83
|
mappings[col] = getMappedColumnName(columnToFieldMap[index]);
|
|
@@ -88,13 +97,15 @@ const getMappedColumnName = (fieldname: string) => {
|
|
|
88
97
|
|
|
89
98
|
const updateColumnMappings = (index: number, value: any) => {
|
|
90
99
|
if (!value) return;
|
|
91
|
-
|
|
100
|
+
mappingUpdated.value = true;
|
|
101
|
+
let templateOptions = props.data?.template_options ? JSON.parse(props.data?.template_options) : {};
|
|
102
|
+
let columnToFieldMap = templateOptions["column_to_field_map"] || {};
|
|
92
103
|
columnToFieldMap[index - 1] = value.value;
|
|
93
104
|
|
|
94
105
|
props.dataImports.setValue.submit({
|
|
95
106
|
...props.data,
|
|
96
107
|
template_options: JSON.stringify({
|
|
97
|
-
...
|
|
108
|
+
...templateOptions,
|
|
98
109
|
column_to_field_map: columnToFieldMap
|
|
99
110
|
})
|
|
100
111
|
}, {
|
|
@@ -103,6 +114,10 @@ const updateColumnMappings = (index: number, value: any) => {
|
|
|
103
114
|
nextTick(() => {
|
|
104
115
|
initializeColumnMappings()
|
|
105
116
|
})
|
|
117
|
+
},
|
|
118
|
+
onError: (error: any) => {
|
|
119
|
+
toast.error(error.messages?.[0] || error)
|
|
120
|
+
console.error("Error updating column mappings:", error);
|
|
106
121
|
}
|
|
107
122
|
})
|
|
108
123
|
}
|
|
@@ -117,7 +132,7 @@ const columnsFromFile = computed(() => {
|
|
|
117
132
|
})
|
|
118
133
|
|
|
119
134
|
const columnsFromSystem = computed(() => {
|
|
120
|
-
const parent = props.data
|
|
135
|
+
const parent = props.data!.reference_doctype
|
|
121
136
|
const docs = props.fields.data?.docs || []
|
|
122
137
|
|
|
123
138
|
return docs
|
|
@@ -141,11 +156,12 @@ const columnsFromSystem = computed(() => {
|
|
|
141
156
|
.flat()
|
|
142
157
|
})
|
|
143
158
|
|
|
144
|
-
const
|
|
159
|
+
const resetMapping = () => {
|
|
160
|
+
let templateOptions = props.data?.template_options ? JSON.parse(props.data?.template_options) : {};
|
|
145
161
|
props.dataImports.setValue.submit({
|
|
146
162
|
...props.data,
|
|
147
163
|
template_options: JSON.stringify({
|
|
148
|
-
...
|
|
164
|
+
...templateOptions,
|
|
149
165
|
column_to_field_map: {}
|
|
150
166
|
})
|
|
151
167
|
}, {
|
|
@@ -154,6 +170,10 @@ const startOver = () => {
|
|
|
154
170
|
nextTick(() => {
|
|
155
171
|
initializeColumnMappings()
|
|
156
172
|
})
|
|
173
|
+
},
|
|
174
|
+
onError: (error: any) => {
|
|
175
|
+
toast.error(error.messages?.[0] || error)
|
|
176
|
+
console.error("Error resetting column mappings:", error);
|
|
157
177
|
}
|
|
158
178
|
})
|
|
159
179
|
}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="text-base h-full flex flex-col w-[700px] mx-auto py-12 space-y-10">
|
|
3
|
-
<div class="flex
|
|
4
|
-
<div class="
|
|
5
|
-
<div class="text-lg font-semibold text-ink-gray-9">
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
<div class="text-base h-full flex flex-col w-[90%] lg:w-[700px] mx-auto py-12 space-y-10">
|
|
3
|
+
<div class="flex flex-col space-y-1">
|
|
4
|
+
<div class="flex items-center justify-between text-ink-gray-7">
|
|
5
|
+
<div class="flex items-center space-x-2 text-lg font-semibold text-ink-gray-9">
|
|
6
|
+
<span>
|
|
7
|
+
Review and Import
|
|
8
|
+
</span>
|
|
9
|
+
|
|
10
|
+
<Badge :theme="getBadgeColor(data.status)">
|
|
11
|
+
{{ data.status }}
|
|
12
|
+
</Badge>
|
|
10
13
|
</div>
|
|
11
|
-
</div>
|
|
12
|
-
<div class="space-x-2">
|
|
13
|
-
<Badge :theme="statusTheme">
|
|
14
|
-
{{ data.status }}
|
|
15
|
-
</Badge>
|
|
16
14
|
<Button
|
|
17
15
|
v-if="data.status != 'Success'"
|
|
18
16
|
:label="data.status != 'Pending' ? 'Retry' : 'Import'"
|
|
19
17
|
variant="solid" @click="startImport" />
|
|
20
18
|
<Button v-else-if="listRoute" label="Done" @click="redirectToList()" />
|
|
21
19
|
</div>
|
|
20
|
+
<div class="leading-5">
|
|
21
|
+
Verify the data before starting the import process
|
|
22
|
+
</div>
|
|
22
23
|
</div>
|
|
23
24
|
|
|
24
25
|
<div v-if="mapping.length" class="space-y-2">
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
Column Mapping
|
|
27
28
|
</div>
|
|
28
29
|
<div class="border rounded-md bg-surface-gray-2 p-4 space-y-4 text-sm">
|
|
29
|
-
<div v-for="map in mapping" class="grid grid-cols-3 space-x-3">
|
|
30
|
+
<div v-for="map in mapping" class="grid grid-cols-[40%,10%,40%] lg:grid-cols-3 space-x-3 items-center">
|
|
30
31
|
<div class="">
|
|
31
32
|
{{ map[0] }}
|
|
32
33
|
</div>
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
</div>
|
|
86
87
|
|
|
87
88
|
<div class="rounded-md p-2" :class="importBannerClass">
|
|
88
|
-
{{ importSuccessCount }} rows imported successfully, {{ importErrorCount }} rows failed.
|
|
89
|
+
{{ importSuccessCount }} {{ importSuccessCount == 1 ? 'row' : 'rows' }} imported successfully, {{ importErrorCount }} {{ importErrorCount == 1 ? 'row' : 'rows' }} failed.
|
|
89
90
|
</div>
|
|
90
91
|
|
|
91
92
|
<TabButtons :buttons="tabButtons" v-model="activeTab" class="w-fit" />
|
|
@@ -152,16 +153,15 @@
|
|
|
152
153
|
</div>
|
|
153
154
|
</template>
|
|
154
155
|
<script setup lang="ts">
|
|
155
|
-
import { getPreviewData } from './dataImport'
|
|
156
|
+
import { getPreviewData, getBadgeColor } from './dataImport'
|
|
156
157
|
import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
|
158
|
+
import type { DataImport, DataImports } from './types';
|
|
157
159
|
import Badge from '../../src/components/Badge/Badge.vue';
|
|
158
160
|
import Button from '../../src/components/Button/Button.vue';
|
|
159
161
|
import call from '../../src/utils/call';
|
|
160
162
|
import FeatherIcon from '../../src/components/FeatherIcon.vue'
|
|
161
163
|
import initSocket from "../../src/utils/socketio";
|
|
162
|
-
import Tooltip from "../../src/components/Tooltip/Tooltip.vue"
|
|
163
164
|
import Popover from "../../src/components/Popover/Popover.vue"
|
|
164
|
-
import Switch from '../../src/components/Switch/Switch.vue';
|
|
165
165
|
import TabButtons from '../../src/components/TabButtons/TabButtons.vue';
|
|
166
166
|
|
|
167
167
|
const preview = ref<any>(null);
|
|
@@ -174,15 +174,16 @@ const props = defineProps<{
|
|
|
174
174
|
dataImports: DataImports
|
|
175
175
|
data: DataImport
|
|
176
176
|
fields: any
|
|
177
|
-
doctypeMap: Record<string, { title: string;
|
|
177
|
+
doctypeMap: Record<string, { title: string; listRoute?: string; pageRoute?: string }>
|
|
178
178
|
}>()
|
|
179
179
|
|
|
180
180
|
onMounted(async () => {
|
|
181
181
|
let socket = initSocket();
|
|
182
|
-
socket.on("data_import_refresh", (data) => {
|
|
182
|
+
socket.on("data_import_refresh", (data: { data_import: string }) => {
|
|
183
183
|
reloadPreviewData(data.data_import);
|
|
184
184
|
})
|
|
185
185
|
|
|
186
|
+
if (!props.data?.name) return;
|
|
186
187
|
preview.value = await getPreviewData(
|
|
187
188
|
props.data.name, props.data.import_file, props.data.google_sheets_url
|
|
188
189
|
);
|
|
@@ -227,9 +228,9 @@ const previewColumns = computed(() => {
|
|
|
227
228
|
})
|
|
228
229
|
|
|
229
230
|
const previewData = computed(() => {
|
|
230
|
-
const data = []
|
|
231
|
+
const data: Record<string, any>[] = [];
|
|
231
232
|
preview.value?.data.forEach((row: any) => {
|
|
232
|
-
const dataMap = {};
|
|
233
|
+
const dataMap: Record<string, any> = {};
|
|
233
234
|
Object.keys(row).forEach((key: any, index: number) => {
|
|
234
235
|
let columnLabel = getColumnLabel(index)
|
|
235
236
|
let mappedFieldIndex = getMappedColumnName(index);
|
|
@@ -308,7 +309,7 @@ const importBannerClass = computed(() => {
|
|
|
308
309
|
})
|
|
309
310
|
|
|
310
311
|
const mapping = computed(() => {
|
|
311
|
-
let warningMap = []
|
|
312
|
+
let warningMap: string[][] = [];
|
|
312
313
|
if (!preview.value?.warnings?.length) return [];
|
|
313
314
|
preview.value.warnings.forEach((warning: any) => {
|
|
314
315
|
const regex = /<strong>(.*?)<\/strong>/g;
|
|
@@ -335,7 +336,7 @@ const pageRoute = computed(() => {
|
|
|
335
336
|
return props.doctypeMap[props.data.reference_doctype]?.pageRoute
|
|
336
337
|
})
|
|
337
338
|
|
|
338
|
-
const redirectToPage = (docname) => {
|
|
339
|
+
const redirectToPage = (docname: string) => {
|
|
339
340
|
if (!pageRoute.value) return;
|
|
340
341
|
window.location.href = pageRoute.value.replace('docname', docname);
|
|
341
342
|
}
|
|
@@ -348,13 +349,7 @@ const tabButtons = computed(() => {
|
|
|
348
349
|
]
|
|
349
350
|
})
|
|
350
351
|
|
|
351
|
-
const
|
|
352
|
-
if (props.data?.status == 'Success') return 'green'
|
|
353
|
-
else if (props.data?.status == 'Error') return 'red'
|
|
354
|
-
else return 'orange'
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
const rowMessage = (row) => {
|
|
352
|
+
const rowMessage = (row: any) => {
|
|
358
353
|
return JSON.parse(row.messages)?.[0]?.message
|
|
359
354
|
}
|
|
360
355
|
</script>
|
|
@@ -12,12 +12,6 @@
|
|
|
12
12
|
:options="['Excel', 'CSV']"
|
|
13
13
|
type="select"
|
|
14
14
|
/>
|
|
15
|
-
<FormControl
|
|
16
|
-
label="Export Type"
|
|
17
|
-
v-model="exportType"
|
|
18
|
-
:options="['All Records', '5 Records', 'Blank Template']"
|
|
19
|
-
type="select"
|
|
20
|
-
/>
|
|
21
15
|
</div>
|
|
22
16
|
<div class="border-t">
|
|
23
17
|
<p class="mt-2 text-ink-gray-5">
|
|
@@ -35,14 +29,13 @@
|
|
|
35
29
|
</div>
|
|
36
30
|
<div class="grid grid-cols-2 gap-5">
|
|
37
31
|
<div v-for="field in fields.data[doctype]" :key="field.fieldname" class="flex items-center space-x-2">
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/>
|
|
32
|
+
<Checkbox
|
|
33
|
+
:id="`checkbox-${doctype}-${field.fieldname}`"
|
|
34
|
+
:checked="fieldSelection[doctype][field.fieldname]"
|
|
35
|
+
@change="(e: Event) => fieldSelection[doctype][field.fieldname] = (e.target as HTMLInputElement).checked"
|
|
36
|
+
/>
|
|
44
37
|
<label
|
|
45
|
-
:for="`checkbox-${field.fieldname}`"
|
|
38
|
+
:for="`checkbox-${doctype}-${field.fieldname}`"
|
|
46
39
|
:class="{
|
|
47
40
|
'text-ink-red-3': field.reqd
|
|
48
41
|
}">
|
|
@@ -65,10 +58,11 @@
|
|
|
65
58
|
</template>
|
|
66
59
|
<script setup lang="ts">
|
|
67
60
|
import { ref } from 'vue'
|
|
68
|
-
import type {
|
|
61
|
+
import type { DocField } from './types'
|
|
69
62
|
import { createResource } from '../../src/resources'
|
|
70
63
|
import { fieldsToIgnore, getChildTableName } from './dataImport'
|
|
71
64
|
import Button from "../../src/components/Button/Button.vue"
|
|
65
|
+
import Checkbox from "../../src/components/Checkbox/Checkbox.vue"
|
|
72
66
|
import Dialog from "../../src/components/Dialog/Dialog.vue"
|
|
73
67
|
import FormControl from "../../src/components/FormControl/FormControl.vue"
|
|
74
68
|
|
|
@@ -114,7 +108,7 @@ const prepareDoctypeMap = (docs: any[], doctypeMap: Record<string, { fieldname:
|
|
|
114
108
|
fieldname: field.fieldname,
|
|
115
109
|
label: field.label,
|
|
116
110
|
reqd: field.reqd,
|
|
117
|
-
disabled: doc.name == props.doctype && field.reqd
|
|
111
|
+
disabled: doc.name == props.doctype && field.reqd ? true : false,
|
|
118
112
|
}
|
|
119
113
|
})
|
|
120
114
|
})
|