pimelon-ui 0.1.211 → 0.1.232

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.
Files changed (84) hide show
  1. package/icons/index.ts +10 -0
  2. package/melon/Billing/SignupBanner.vue +1 -1
  3. package/melon/Billing/TrialBanner.vue +1 -1
  4. package/melon/DataImport/DataImport.vue +165 -0
  5. package/melon/DataImport/DataImportList.vue +172 -0
  6. package/melon/DataImport/ImportSteps.vue +114 -0
  7. package/melon/DataImport/MappingStep.vue +187 -0
  8. package/melon/DataImport/PreviewStep.vue +355 -0
  9. package/melon/DataImport/TemplateModal.vue +215 -0
  10. package/melon/DataImport/UploadStep.vue +398 -0
  11. package/melon/DataImport/dataImport.ts +52 -0
  12. package/melon/DataImport/types.ts +45 -0
  13. package/melon/Filter/Filter.vue +169 -0
  14. package/melon/Filter/types.ts +15 -0
  15. package/melon/Filter/utils.ts +237 -0
  16. package/melon/Help/HelpModal.vue +4 -4
  17. package/melon/Icons/FilterIcon.vue +31 -0
  18. package/melon/Link/Link.vue +5 -2
  19. package/melon/Link/types.ts +4 -1
  20. package/melon/Onboarding/GettingStartedBanner.vue +1 -1
  21. package/melon/index.d.ts +56 -0
  22. package/melon/index.js +5 -0
  23. package/package.json +31 -3
  24. package/src/components/Alert/Alert.story.vue +78 -0
  25. package/src/components/Alert/Alert.vue +67 -45
  26. package/src/components/Alert/types.ts +7 -2
  27. package/src/components/Badge/Badge.vue +1 -1
  28. package/src/components/Breadcrumbs/Breadcrumbs.vue +28 -32
  29. package/src/components/Button/Button.vue +2 -0
  30. package/src/components/Button/types.ts +1 -0
  31. package/src/components/Calendar/Calendar.story.vue +1 -0
  32. package/src/components/Calendar/Calendar.vue +65 -3
  33. package/src/components/Charts/index.ts +0 -0
  34. package/src/components/Checkbox/types.ts +0 -1
  35. package/src/components/Combobox/Combobox.story.vue +18 -0
  36. package/src/components/Combobox/Combobox.vue +21 -3
  37. package/src/components/Combobox/types.ts +4 -1
  38. package/src/components/DatePicker/DateTimePicker.vue +1 -1
  39. package/src/components/DatePicker/index.ts +6 -0
  40. package/src/components/Dropdown/Dropdown.vue +19 -5
  41. package/src/components/Dropdown/types.ts +1 -0
  42. package/src/components/FileUploader/FileUploader.vue +6 -1
  43. package/src/components/FormControl/FormControl.story.vue +2 -2
  44. package/src/components/FormControl/FormControl.vue +10 -0
  45. package/src/components/FormControl/types.ts +1 -1
  46. package/src/components/ListView/ListGroupHeader.vue +1 -1
  47. package/src/components/ListView/ListGroupRows.vue +5 -0
  48. package/src/components/MultiSelect/MultiSelect.story.vue +68 -0
  49. package/src/components/MultiSelect/MultiSelect.vue +136 -0
  50. package/src/components/MultiSelect/index.ts +2 -0
  51. package/src/components/MultiSelect/types.ts +13 -0
  52. package/src/components/Select/Select.story.vue +16 -3
  53. package/src/components/Select/Select.vue +109 -96
  54. package/src/components/Sidebar/Sidebar.vue +7 -2
  55. package/src/components/Sidebar/SidebarSection.vue +14 -13
  56. package/src/components/Sidebar/index.ts +3 -0
  57. package/src/components/Sidebar/types.ts +1 -0
  58. package/src/components/Tabs/Tabs.story.vue +12 -9
  59. package/src/components/Tabs/Tabs.vue +66 -54
  60. package/src/components/Tabs/types.ts +11 -0
  61. package/src/components/TextEditor/extensions/suggestion/index.ts +5 -0
  62. package/src/components/TextEditor/link-extension.ts +1 -1
  63. package/src/components/Toast/Toast.vue +1 -1
  64. package/src/components/Toast/index.ts +14 -0
  65. package/src/components/VueGridLayout/Layout.vue +19 -1
  66. package/src/data-fetching/index.ts +4 -2
  67. package/src/index.ts +10 -25
  68. package/src/resources/{index.js → index.ts} +3 -1
  69. package/src/resources/{local.js → local.ts} +4 -4
  70. package/src/resources/realtime.ts +21 -0
  71. package/tsconfig.base.json +20 -0
  72. package/vite/index.js +16 -0
  73. package/src/components/Tabs/TabList.vue +0 -82
  74. package/src/components/Tabs/TabPanel.vue +0 -17
  75. package/src/components/Tabs/Tabs.story.md +0 -97
  76. package/src/resources/realtime.js +0 -15
  77. /package/{src/icons/CircleCheck.vue → icons/CircleCheckIcon.vue} +0 -0
  78. /package/{src/icons/DownSolid.vue → icons/DownSolidIcon.vue} +0 -0
  79. /package/{src/components → icons}/GreenCheckIcon.vue +0 -0
  80. /package/{melon/Icons → icons}/HelpIcon.vue +0 -0
  81. /package/{melon/Icons → icons}/LightningIcon.vue +0 -0
  82. /package/{melon/Icons → icons}/MaximizeIcon.vue +0 -0
  83. /package/{melon/Icons → icons}/MinimizeIcon.vue +0 -0
  84. /package/{melon/Icons → icons}/StepsIcon.vue +0 -0
package/icons/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { default as CircleCheckIcon } from './CircleCheckIcon.vue'
2
+ export { default as DownSolidIcon } from './DownSolidIcon.vue'
3
+ export { default as GreenCheckIcon } from './GreenCheckIcon.vue'
4
+
5
+ // Melon Icons
6
+ export { default as HelpIcon } from './HelpIcon.vue'
7
+ export { default as LightningIcon } from './LightningIcon.vue'
8
+ export { default as MaximizeIcon } from './MaximizeIcon.vue'
9
+ export { default as MinimizeIcon } from './MinimizeIcon.vue'
10
+ export { default as StepsIcon } from './StepsIcon.vue'
@@ -25,7 +25,7 @@
25
25
  </Button>
26
26
  </template>
27
27
  <script setup>
28
- import LightningIcon from '../Icons/LightningIcon.vue'
28
+ import LightningIcon from '../../icons/LightningIcon.vue'
29
29
 
30
30
  const props = defineProps({
31
31
  isSidebarCollapsed: {
@@ -23,7 +23,7 @@
23
23
  </Button>
24
24
  </template>
25
25
  <script setup>
26
- import LightningIcon from '../Icons/LightningIcon.vue'
26
+ import LightningIcon from '../../icons/LightningIcon.vue'
27
27
  import FeatherIcon from '../../src/components/FeatherIcon.vue'
28
28
  import { Button } from '../../src/components/Button'
29
29
  import { createResource } from '../../src/resources'
@@ -0,0 +1,165 @@
1
+ <template>
2
+ <header
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
+ >
5
+ <Breadcrumbs :items="breadcrumbs" />
6
+ <ImportSteps class="flex-1 hidden lg:flex" v-if="step != 'list'" :data="data" :step="step" @updateStep="updateStep" />
7
+ </header>
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
+
11
+ <DataImportList
12
+ v-if="step === 'list'"
13
+ :dataImports="dataImports"
14
+ @updateStep="updateStep"
15
+ />
16
+
17
+ <UploadStep
18
+ v-else-if="step === 'upload'"
19
+ :dataImports="dataImports"
20
+ :doctype="doctype || data?.reference_doctype"
21
+ :fields="fields"
22
+ :data="data"
23
+ @updateStep="updateStep"
24
+ />
25
+
26
+ <MappingStep
27
+ v-else-if="step === 'map'"
28
+ :dataImports="dataImports"
29
+ :data="data as DataImport"
30
+ :fields="fields"
31
+ @updateStep="updateStep"
32
+ />
33
+
34
+ <PreviewStep
35
+ v-else-if="step === 'preview'"
36
+ :dataImports="dataImports"
37
+ :data="data as DataImport"
38
+ :fields="fields"
39
+ :doctypeMap="doctypeMap as Record<string, { title: string; listRoute?: string; pageRoute?: string }>"
40
+ @updateStep="updateStep"
41
+ />
42
+ </div>
43
+ </template>
44
+ <script setup lang="ts">
45
+ import { computed, nextTick, ref, watch } from 'vue'
46
+ import type { DataImportProps, DataImport } from './types'
47
+ import { createListResource, createResource } from '../../src/resources'
48
+ import { useRoute } from 'vue-router'
49
+ import Breadcrumbs from '../../src/components/Breadcrumbs/Breadcrumbs.vue'
50
+ import DataImportList from './DataImportList.vue'
51
+ import ImportSteps from './ImportSteps.vue'
52
+ import MappingStep from './MappingStep.vue'
53
+ import PreviewStep from './PreviewStep.vue'
54
+ import UploadStep from './UploadStep.vue'
55
+
56
+ const route = useRoute()
57
+ const step = ref<'upload' | 'map' | 'list' | 'preview'>('list')
58
+ const data = ref<DataImport | null>(null)
59
+
60
+ const props = defineProps<Partial<DataImportProps>>()
61
+
62
+ const dataImports = createListResource({
63
+ doctype: 'Data Import',
64
+ fields: [
65
+ 'name',
66
+ 'reference_doctype',
67
+ 'import_type',
68
+ 'status',
69
+ 'creation',
70
+ 'mute_emails',
71
+ 'import_file',
72
+ 'google_sheets_url',
73
+ 'template_options'
74
+ ],
75
+ auto: true,
76
+ orderBy: 'modified desc',
77
+ })
78
+
79
+ const fields = createResource({
80
+ url: "melon.desk.form.load.getdoctype",
81
+ makeParams: (values: { doctype: string }) => {
82
+ return {
83
+ doctype: values.doctype,
84
+ with_parent: 1,
85
+ }
86
+ },
87
+ auto: false,
88
+ })
89
+
90
+ watch(
91
+ () => [route.params, props, dataImports.data],
92
+ () => {
93
+ if (!dataImports.data?.length) return
94
+ if (props.doctype) {
95
+ step.value = 'upload'
96
+ fields.reload({
97
+ doctype: route.params.doctype,
98
+ })
99
+ } else if (props.importName) {
100
+ updateData()
101
+ if (!data.value?.import_file && !data.value?.google_sheets_url) {
102
+ step.value = 'upload'
103
+ } else if (step.value == 'upload' && route.query.step == 'map') {
104
+ step.value = route.query.step
105
+ } else {
106
+ step.value = 'preview'
107
+ }
108
+ if (data.value?.reference_doctype) {
109
+ fields.reload({
110
+ doctype: data.value?.reference_doctype,
111
+ })
112
+ }
113
+ }
114
+ },
115
+ { immediate: true },
116
+ )
117
+
118
+ watch(() => route.query, () => {
119
+ if (route.query.step == 'list') {
120
+ step.value = 'list'
121
+ }
122
+ })
123
+
124
+ const updateData = () => {
125
+ data.value = dataImports.data?.find(
126
+ (di: DataImport) => di.name === props.importName,
127
+ ) || null
128
+ }
129
+
130
+ const updateStep = (newStep: 'list' | 'upload' | 'map' | 'preview', newData: DataImport) => {
131
+ step.value = newStep
132
+ if (newData) {
133
+ data.value = newData
134
+ }
135
+ }
136
+
137
+ const doctypeTitle = computed(() => {
138
+ let doctype = props.doctype || data.value?.reference_doctype
139
+ return props.doctypeMap?.[doctype || '']?.title || doctype || ''
140
+ })
141
+
142
+ const breadcrumbs = computed(() => {
143
+ let crumbs = [
144
+ {
145
+ label: 'Data Import',
146
+ route: {
147
+ name: 'DataImportList',
148
+ query: {
149
+ step: 'list'
150
+ }
151
+ },
152
+ },
153
+ ]
154
+
155
+ if (step.value !== 'list') {
156
+ crumbs.push({
157
+ label: `Importing ${doctypeTitle.value}`,
158
+ })
159
+ }
160
+
161
+ return crumbs
162
+ })
163
+
164
+
165
+ </script>
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <div class="flex min-h-0 flex-col text-base py-5 w-[90%] lg:w-[700px] mx-auto">
3
+ <div class="flex items-center justify-between">
4
+ <div>
5
+ <div class="text-xl font-semibold mb-1 text-ink-gray-9">
6
+ Data Import
7
+ </div>
8
+ <div class="text-ink-gray-6 leading-5">
9
+ Import data into your system using CSV files.
10
+ </div>
11
+ </div>
12
+ <Button variant="solid" @click="showModal = true">
13
+ <template #prefix>
14
+ <FeatherIcon name="plus" class="size-4 stroke-1.5" />
15
+ </template>
16
+ Import
17
+ </Button>
18
+ </div>
19
+
20
+ <div class="flex items-center space-x-2 my-5">
21
+ <FormControl
22
+ v-model="search"
23
+ placeholder="Search imported files"
24
+ type="text"
25
+ class="flex-1"
26
+ />
27
+ <FormControl
28
+ v-model="importStatus"
29
+ type="select"
30
+ :options="importOptions"
31
+ />
32
+ </div>
33
+
34
+ <div v-if="dataImports.data?.length" class="overflow-y-scroll">
35
+ <div class="divide-y">
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
+ <div>
38
+ Name
39
+ </div>
40
+ <div class="pl-1">
41
+ Status
42
+ </div>
43
+ </div>
44
+ <div
45
+ v-for="dataImport in dataImports.data"
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
+ >
49
+ <div class="space-y-1">
50
+ <div class="text-ink-gray-7">
51
+ {{ dataImport.reference_doctype }}
52
+ </div>
53
+ <div class="text-ink-gray-5">
54
+ {{ dayjs(dataImport.creation).fromNow() }}
55
+ </div>
56
+ </div>
57
+ <Badge :label="dataImport.status" :theme="getBadgeColor(dataImport.status) as BadgeProps['theme']" class="w-fit" />
58
+ </div>
59
+ </div>
60
+ <div class="my-5 flex justify-center">
61
+ <Button v-if="props.dataImports.hasNextPage" @click="props.dataImports.next()">
62
+ <template #prefix>
63
+ <FeatherIcon name="refresh-cw" class="size-4 stroke-1.5" />
64
+ </template>
65
+ Load More
66
+ </Button>
67
+ </div>
68
+ </div>
69
+ <div v-else class="text-sm italic text-ink-gray-5 mt-5">
70
+ No data imports found.
71
+ </div>
72
+ <Dialog
73
+ v-model="showModal"
74
+ :options="{
75
+ title: 'New Data Import',
76
+ actions: [{
77
+ label: 'Continue',
78
+ variant: 'solid',
79
+ onClick({ close }) {
80
+ createDataImport(close)
81
+ }
82
+ }]
83
+ }"
84
+ >
85
+ <template #body-content>
86
+ <div>
87
+ <Link
88
+ v-model="doctypeForImport"
89
+ doctype="DocType"
90
+ :filters="{
91
+ 'allow_import': 1
92
+ }"
93
+ label="Choose a Document Type to import"
94
+ />
95
+ </div>
96
+ </template>
97
+ </Dialog>
98
+ </div>
99
+ </template>
100
+ <script setup lang="ts">
101
+ import { computed, ref, watch } from 'vue'
102
+ import { useRouter } from 'vue-router'
103
+ import type { DataImports, DataImport } from './types'
104
+ import { dayjs } from "../../src/utils/dayjs"
105
+ import { getBadgeColor } from "./dataImport"
106
+ import Badge from '../../src/components/Badge/Badge.vue'
107
+ import type { BadgeProps } from '../../src/components/Badge/types'
108
+ import { toast } from "../../src/components/Toast/index"
109
+ import Button from '../../src/components/Button/Button.vue'
110
+ import Dialog from '../../src/components/Dialog/Dialog.vue'
111
+ import FeatherIcon from '../../src/components/FeatherIcon.vue'
112
+ import FormControl from '../../src/components/FormControl/FormControl.vue'
113
+ import Link from "../Link/Link.vue"
114
+
115
+ const search = ref('')
116
+ const importStatus = ref('All')
117
+ const showModal = ref(false)
118
+ const doctypeForImport = ref<string | null>(null)
119
+ const emit = defineEmits(['updateStep'])
120
+ const router = useRouter()
121
+
122
+ const props = defineProps<{
123
+ dataImports: DataImports
124
+ }>()
125
+
126
+ const importOptions = computed(() => {
127
+ const options = ["All", "Pending", "Success", "Partial Success", "Error", "Timed Out"]
128
+ return options.map(option => ({ label: option, value: option }))
129
+ })
130
+
131
+ watch([search, importStatus], ([newSearch, newStatus]) => {
132
+ props.dataImports.update({
133
+ filters: [
134
+ newSearch ? [['name', 'like', `%${newSearch}%`]] : [],
135
+ newStatus !== 'All' ? [['status', '=', newStatus]] : [],
136
+ ].flat(),
137
+ })
138
+ props.dataImports.reload()
139
+ })
140
+
141
+ const createDataImport = (close: () => void) => {
142
+ props.dataImports.insert.submit({
143
+ reference_doctype: doctypeForImport.value!,
144
+ import_type: 'Insert New Records',
145
+ mute_emails: true,
146
+ status: 'Pending',
147
+ }, {
148
+ onSuccess(data: DataImport) {
149
+ router.replace({
150
+ name: 'DataImport',
151
+ params: {
152
+ importName: data.name
153
+ },
154
+ })
155
+ close()
156
+ },
157
+ onError(error: any) {
158
+ console.error(error)
159
+ toast.error(error.messages?.[0] || error)
160
+ }
161
+ })
162
+ }
163
+
164
+ const redirectToImport = (importName: string) => {
165
+ router.replace({
166
+ name: 'DataImport',
167
+ params: {
168
+ importName
169
+ },
170
+ })
171
+ }
172
+ </script>
@@ -0,0 +1,114 @@
1
+ <template>
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
+ :class="{
5
+ 'text-ink-gray-9 font-semibold': onUploadStep
6
+ }"
7
+ @click="emit('updateStep', 'upload', { ...data })"
8
+ >
9
+ <FeatherIcon v-if="uploadStepCompleted" name="check" class="size-5 text-sm border rounded-[5px] p-0.5" :class="{
10
+ 'text-ink-white bg-surface-gray-7': onUploadStep,
11
+ }"/>
12
+ <div v-else class="text-sm border rounded-[5px] px-1.5 py-0.5" :class="{
13
+ 'text-ink-white bg-surface-gray-7': onUploadStep,
14
+ }">
15
+ <span>
16
+ 1
17
+ </span>
18
+ </div>
19
+ <div>
20
+ Upload File
21
+ </div>
22
+ </div>
23
+ <div class="flex items-center space-x-1 lg:space-x-2 text-ink-gray-5"
24
+ :class="{
25
+ 'text-ink-gray-9 font-semibold': onMapStep,
26
+ 'cursor-pointer': uploadStepCompleted
27
+ }"
28
+ @click="moveToMapStep()"
29
+ >
30
+ <FeatherIcon v-if="mapStepCompleted" name="check" class="size-5 text-sm border rounded-[5px] p-0.5" :class="{
31
+ 'text-ink-white bg-surface-gray-7': onMapStep,
32
+ }"/>
33
+ <div v-else class="text-sm border rounded-[5px] px-1.5 py-0.5" :class="{
34
+ 'text-ink-white bg-surface-gray-7': onMapStep,
35
+ }">
36
+ <span>
37
+ 2
38
+ </span>
39
+ </div>
40
+ <div>
41
+ Map Data
42
+ </div>
43
+ </div>
44
+ <div class="flex items-center space-x-1 lg:space-x-2 text-ink-gray-5"
45
+ :class="{
46
+ 'text-ink-gray-9 font-semibold': onPreviewStep,
47
+ 'cursor-pointer': uploadStepCompleted
48
+ }"
49
+ @click="moveToPreviewStep()"
50
+ >
51
+ <FeatherIcon v-if="previewStepCompleted" name="check" class="size-5 text-sm border rounded-[5px] p-0.5" :class="{
52
+ 'text-ink-white bg-surface-gray-7': onPreviewStep,
53
+ }"/>
54
+ <div v-else class="text-sm border rounded-[5px] px-1.5 py-0.5" :class="{
55
+ 'text-ink-white bg-surface-gray-7': onPreviewStep,
56
+ }">
57
+ <span>
58
+ 3
59
+ </span>
60
+ </div>
61
+ <div>
62
+ Review & Import
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
67
+ <script setup lang="ts">
68
+ import type { DataImport } from './types'
69
+ import { computed } from 'vue'
70
+ import FeatherIcon from '../../src/components/FeatherIcon.vue'
71
+
72
+ const emit = defineEmits(['updateStep'])
73
+
74
+ const props = defineProps<{
75
+ data: DataImport | null
76
+ step: 'list' | 'upload' | 'map' | 'preview'
77
+ }>()
78
+
79
+ const onUploadStep = computed(() => {
80
+ return props.step === 'upload'
81
+ })
82
+
83
+ const uploadStepCompleted = computed(() => {
84
+ return props.data?.import_file || props.data?.google_sheets_url
85
+ })
86
+
87
+ const onMapStep = computed(() => {
88
+ return props.step === 'map'
89
+ })
90
+
91
+ const mapStepCompleted = computed(() => {
92
+ return props.data && props.data?.template_options && JSON.parse(props.data.template_options).column_to_field_map
93
+ })
94
+
95
+ const onPreviewStep = computed(() => {
96
+ return props.step === 'preview'
97
+ })
98
+
99
+ const previewStepCompleted = computed(() => {
100
+ return props.data?.status === 'Success'
101
+ })
102
+
103
+ const moveToMapStep = () => {
104
+ if (uploadStepCompleted.value) {
105
+ emit('updateStep', 'map', { ...props.data })
106
+ }
107
+ }
108
+
109
+ const moveToPreviewStep = () => {
110
+ if (uploadStepCompleted.value) {
111
+ emit('updateStep', 'preview', { ...props.data })
112
+ }
113
+ }
114
+ </script>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <div class="w-[85%] lg:w-[700px] mx-auto py-12 space-y-8">
3
+ <div class="flex justify-between">
4
+ <div class="space-y-2">
5
+ <div class="text-lg font-semibold text-ink-gray-9">
6
+ <span>
7
+ Map Data
8
+ </span>
9
+ <Badge v-if="data?.status" :theme="getBadgeColor(data?.status)">
10
+ {{ data?.status }}
11
+ </Badge>
12
+ </div>
13
+ <div class="leading-5">
14
+ Change the mapping of columns from your file to fields in the system
15
+ </div>
16
+ </div>
17
+
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" />
20
+ <Button label="Continue" variant="solid" @click="$emit('updateStep', 'preview')" />
21
+ </div>
22
+ </div>
23
+
24
+ <div v-if="Object.keys(columnMappings).length" class="border rounded-md space-y-8">
25
+ <div class="grid grid-cols-2 text-ink-gray-5 border-b py-2 px-4">
26
+ <div>
27
+ Fields in File
28
+ </div>
29
+ <div>
30
+ Fields in System
31
+ </div>
32
+ </div>
33
+ <div class="grid grid-cols-2 py-2 px-4 gap-y-8">
34
+ <template v-for="i in columnsFromFile.length" :key="i">
35
+ <div class="text-ink-gray-7">{{ columnsFromFile[i - 1] }}</div>
36
+ <Autocomplete
37
+ :model-value="columnMappings[columnsFromFile[i - 1]]"
38
+ :options="columnsFromSystem"
39
+ placeholder="Select field"
40
+ @update:model-value="(val: any) => updateColumnMappings(i, val)"
41
+ />
42
+ </template>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </template>
47
+ <script setup lang="ts">
48
+ import type { DataImport, DataImports } from './types';
49
+ import { fieldsToIgnore, getBadgeColor, getPreviewData } from './dataImport'
50
+ import { computed, nextTick, onMounted, ref } from 'vue';
51
+ import { toast } from "../../src/components/Toast/index"
52
+ import Autocomplete from '../../src/components/Autocomplete/Autocomplete.vue';
53
+ import Badge from '../../src/components/Badge/Badge.vue';
54
+ import Button from '../../src/components/Button/Button.vue';
55
+
56
+ const previewData = ref<any>(null);
57
+ const emit = defineEmits(['updateStep'])
58
+ const columnMappings = ref<Record<string, string>>({});
59
+ const mappingUpdated = ref(false);
60
+
61
+ const props = defineProps<{
62
+ dataImports: DataImports
63
+ data: DataImport
64
+ fields: any
65
+ }>()
66
+
67
+ onMounted(async () => {
68
+ previewData.value = await getPreviewData(props.data.name!, props.data.import_file, props.data.google_sheets_url);
69
+ initializeColumnMappings();
70
+ });
71
+
72
+ const initializeColumnMappings = () => {
73
+ const mappings: Record<string, string> = {};
74
+ let columnToFieldMap = []
75
+ if (props.data?.template_options)
76
+ columnToFieldMap = JSON.parse(props.data?.template_options)?.["column_to_field_map"];
77
+
78
+ if (Object.keys(columnToFieldMap).length > 0)
79
+ mappingUpdated.value = true;
80
+
81
+ columnsFromFile.value.forEach((col: string, index: number) => {
82
+ if (columnToFieldMap && columnToFieldMap[index])
83
+ mappings[col] = getMappedColumnName(columnToFieldMap[index]);
84
+ else
85
+ mappings[col] = col;
86
+ });
87
+
88
+ columnMappings.value = mappings;
89
+ }
90
+
91
+ const getMappedColumnName = (fieldname: string) => {
92
+ const field = columnsFromSystem.value.find((f: any) => f.value == fieldname);
93
+ if (field)
94
+ return field.label;
95
+ return fieldname;
96
+ }
97
+
98
+ const updateColumnMappings = (index: number, value: any) => {
99
+ if (!value) return;
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"] || {};
103
+ columnToFieldMap[index - 1] = value.value;
104
+
105
+ props.dataImports.setValue.submit({
106
+ ...props.data,
107
+ template_options: JSON.stringify({
108
+ ...templateOptions,
109
+ column_to_field_map: columnToFieldMap
110
+ })
111
+ }, {
112
+ onSuccess: (data: DataImport) => {
113
+ emit('updateStep', 'map', { ...data })
114
+ nextTick(() => {
115
+ initializeColumnMappings()
116
+ })
117
+ },
118
+ onError: (error: any) => {
119
+ toast.error(error.messages?.[0] || error)
120
+ console.error("Error updating column mappings:", error);
121
+ }
122
+ })
123
+ }
124
+
125
+ const columnsFromFile = computed(() => {
126
+ const columns: string[] = [];
127
+ previewData.value?.columns.forEach((col: any) => {
128
+ if (col.header_title != "Sr. No")
129
+ columns.push(col.header_title);
130
+ })
131
+ return columns;
132
+ })
133
+
134
+ const columnsFromSystem = computed(() => {
135
+ const parent = props.data!.reference_doctype
136
+ const docs = props.fields.data?.docs || []
137
+
138
+ return docs
139
+ .map((doc: any) => {
140
+ const isParent = doc.name === parent
141
+
142
+ const columns = doc.fields
143
+ .filter((f: any) => !fieldsToIgnore.includes(f.fieldtype))
144
+ .map((f: any) => ({
145
+ value: f.fieldname,
146
+ label: isParent
147
+ ? f.label
148
+ : `${f.label} (${getChildTableName(parent, doc.name)})`,
149
+ }))
150
+
151
+ return [
152
+ { value: "name", label: "ID" },
153
+ ...columns,
154
+ ]
155
+ })
156
+ .flat()
157
+ })
158
+
159
+ const resetMapping = () => {
160
+ let templateOptions = props.data?.template_options ? JSON.parse(props.data?.template_options) : {};
161
+ props.dataImports.setValue.submit({
162
+ ...props.data,
163
+ template_options: JSON.stringify({
164
+ ...templateOptions,
165
+ column_to_field_map: {}
166
+ })
167
+ }, {
168
+ onSuccess: (data: DataImport) => {
169
+ emit('updateStep', 'map', { ...data })
170
+ nextTick(() => {
171
+ initializeColumnMappings()
172
+ })
173
+ },
174
+ onError: (error: any) => {
175
+ toast.error(error.messages?.[0] || error)
176
+ console.error("Error resetting column mappings:", error);
177
+ }
178
+ })
179
+ }
180
+
181
+ const getChildTableName = (parent: string, child: string) => {
182
+ let parentFields = props.fields.data?.docs.find((doc: any) => doc.name == parent)?.fields || [];
183
+
184
+ let childField = parentFields.filter((field: any) => field.options == child)[0]
185
+ return childField?.label || child;
186
+ }
187
+ </script>