fix: added send email feature using email editor

This commit is contained in:
Shariq Ansari 2023-08-17 16:43:54 +05:30
parent 5d918fdeb6
commit a68716c26e
6 changed files with 254 additions and 15 deletions

View File

@ -9,6 +9,7 @@
"serve": "vite preview"
},
"dependencies": {
"@tiptap/vue-3": "^2.0.4",
"@vitejs/plugin-vue": "^4.2.3",
"@vueuse/core": "^10.3.0",
"@vueuse/integrations": "^10.3.0",
@ -17,11 +18,11 @@
"frappe-ui": "^0.1.0-alpha.11",
"pinia": "^2.0.33",
"postcss": "^8.4.5",
"sortablejs": "^1.15.0",
"socket.io-client": "^4.7.2",
"sortablejs": "^1.15.0",
"tailwindcss": "^3.3.3",
"vite": "^4.4.9",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
"vite": "^4.4.9"
"vue-router": "^4.2.2"
}
}

View File

@ -0,0 +1,100 @@
<template>
<div class="max-w-[81.7%] bg-white pl-16 p-4 pt-2 z-20">
<button
class="flex w-full items-center rounded-lg bg-gray-100 px-2 py-2 text-left text-base text-gray-600 hover:bg-gray-200"
@click="showCommunicationBox = true"
v-show="!showCommunicationBox"
>
<UserAvatar class="mr-3" :user="getUser().name" size="sm" />
Add a reply...
</button>
<div
v-show="showCommunicationBox"
class="w-full rounded-lg border bg-white p-4 focus-within:border-gray-400"
@keydown.ctrl.enter.capture.stop="submitComment"
@keydown.meta.enter.capture.stop="submitComment"
>
<div class="mb-4 flex items-center">
<UserAvatar :user="getUser().name" size="sm" />
<span class="ml-2 text-base font-medium text-gray-900">
{{ getUser().full_name }}
</span>
</div>
<EmailEditor
ref="newEmailEditor"
:value="newEmail"
@change="onNewEmailChange"
:submitButtonProps="{
variant: 'solid',
onClick: submitComment,
disabled: emailEmpty,
}"
:discardButtonProps="{
onClick: () => {
showCommunicationBox = false
newEmail = ''
},
}"
:editable="showCommunicationBox"
v-model="modelValue.data"
placeholder="Add a reply..."
/>
</div>
</div>
</template>
<script setup>
import UserAvatar from '@/components/UserAvatar.vue'
import EmailEditor from '@/components/EmailEditor.vue'
import { usersStore } from '@/stores/users'
import { call } from 'frappe-ui'
import { ref, watch, computed, defineModel } from 'vue'
const modelValue = defineModel()
const { getUser } = usersStore()
const showCommunicationBox = ref(false)
const newEmail = ref('<p>Hi,<br><br>Gentle reminder!<br>We have a call at 3 - 5 PM today.<br><br>Thanks &amp; Regards<br>Shariq Ansari</p>')
const newEmailEditor = ref(null)
watch(
() => showCommunicationBox.value,
(value) => {
if (value) {
newEmailEditor.value.editor.commands.focus()
}
}
)
const emailEmpty = computed(() => {
return !newEmail.value || newEmail.value === '<p></p>'
})
const onNewEmailChange = (value) => {
newEmail.value = value
}
async function sendMail() {
await call('frappe.core.doctype.communication.email.make', {
recipients: modelValue.value.data.email,
cc: '',
bcc: '',
subject: 'Email from Agent',
content: newEmail.value,
doctype: 'CRM Lead',
name: modelValue.value.data.name,
send_email: 1,
sender: getUser().name,
sender_full_name: getUser()?.full_name || undefined,
})
}
async function submitComment() {
if (emailEmpty.value) return
showCommunicationBox.value = false
await sendMail()
newEmail.value = ''
modelValue.value.reload()
}
</script>

View File

@ -0,0 +1,127 @@
<template>
<TextEditor
ref="textEditor"
:editor-class="['prose-sm max-w-none', editable && 'min-h-[4rem]']"
:content="value"
@change="editable ? $emit('change', $event) : null"
:starterkit-options="{ heading: { levels: [2, 3, 4, 5, 6] } }"
:placeholder="placeholder"
:editable="editable"
>
<template #top>
<div class="mb-2">
<span class="text-base text-gray-600">To:</span>
<span
v-if="modelValue.email"
class="ml-2 bg-gray-100 px-2 py-1 rounded-md text-sm text-gray-800 cursor-pointer"
>{{ modelValue.email }}</span
>
</div>
</template>
<template v-slot:editor="{ editor }">
<EditorContent
:class="[editable && 'max-h-[50vh] overflow-y-auto']"
:editor="editor"
/>
</template>
<template v-slot:bottom>
<div
v-if="editable"
class="mt-2 flex flex-col justify-between sm:flex-row sm:items-center"
>
<TextEditorFixedMenu
class="-ml-1 overflow-x-auto"
:buttons="textEditorMenuButtons"
/>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}"> Discard </Button>
<Button variant="solid" v-bind="submitButtonProps || {}">
Submit
</Button>
</div>
</div>
</template>
</TextEditor>
</template>
<script setup>
import { TextEditorFixedMenu, TextEditor } from 'frappe-ui'
import { EditorContent } from '@tiptap/vue-3'
import { ref, computed, defineModel } from 'vue'
const props = defineProps({
value: {
type: String,
default: '',
},
placeholder: {
type: String,
default: null,
},
editable: {
type: Boolean,
default: true,
},
editorProps: {
type: Object,
default: () => ({}),
},
submitButtonProps: {
type: Object,
default: () => ({}),
},
discardButtonProps: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['change'])
const modelValue = defineModel()
const textEditor = ref(null)
const editor = computed(() => {
return textEditor.value.editor
})
defineExpose({ editor })
const textEditorMenuButtons = [
'Paragraph',
['Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'],
'Separator',
'Bold',
'Italic',
'Separator',
'Bullet List',
'Numbered List',
'Separator',
'Align Left',
'Align Center',
'Align Right',
'FontColor',
'Separator',
'Image',
'Video',
'Link',
'Blockquote',
'Code',
'Horizontal Rule',
[
'InsertTable',
'AddColumnBefore',
'AddColumnAfter',
'DeleteColumn',
'AddRowBefore',
'AddRowAfter',
'DeleteRow',
'MergeCells',
'SplitCell',
'ToggleHeaderColumn',
'ToggleHeaderRow',
'ToggleHeaderCell',
'DeleteTable',
],
]
</script>

View File

@ -35,7 +35,7 @@
<Button label="Save" variant="solid" @click="() => updateLead()" />
</template>
</LayoutHeader>
<TabGroup v-if="lead.data" @change="onTabChange">
<TabGroup v-slot="{ selectedIndex }" v-if="lead.data" @change="onTabChange">
<TabList class="flex items-center gap-6 border-b pl-5 relative">
<Tab
ref="tabRef"
@ -58,14 +58,19 @@
:style="{ left: `${indicatorLeftValue}px` }"
/>
</TabList>
<TabPanels class="flex h-full overflow-hidden">
<TabPanel
class="flex-1 overflow-y-auto"
v-for="tab in tabs"
:key="tab.label"
>
<Activities :title="tab.activityTitle" :activities="tab.content" />
</TabPanel>
<div class="flex h-full overflow-hidden">
<div class="flex-1 flex flex-col">
<TabPanels class="flex flex-1 overflow-hidden">
<TabPanel
class="flex-1 overflow-y-auto"
v-for="tab in tabs"
:key="tab.label"
>
<Activities :title="tab.activityTitle" :activities="tab.content" />
</TabPanel>
</TabPanels>
<CommunicationArea v-if="[0, 1].includes(selectedIndex)" v-model="lead" />
</div>
<div
class="flex flex-col justify-between border-l w-[390px] overflow-hidden"
>
@ -197,7 +202,7 @@
</Tooltip>
</div>
</div>
</TabPanels>
</div>
</TabGroup>
</template>
<script setup>
@ -212,6 +217,7 @@ import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue'
import Breadcrumbs from '@/components/Breadcrumbs.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import CommunicationArea from '../components/CommunicationArea.vue'
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
import { TransitionPresets, useTransition } from '@vueuse/core'
import { dateFormat, timeAgo, dateTooltipFormat } from '@/utils'

View File

@ -5,7 +5,12 @@ import frappeui from 'frappe-ui/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [frappeui(), vue()],
plugins: [frappeui(), vue({
script: {
defineModel: true,
propsDestructure: true
}
})],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),

View File

@ -2752,7 +2752,7 @@
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.4.tgz#08e6c47a723200d02238d845cb09684c481f0066"
integrity sha512-C5LGGjH8VFET34V7vKkqlwpSzrPl+7oAcj9h+P3jvJQ076iYpmpnMtz6dNLSFGKpHp5mtyl4RoJzh7lTvlfyiA==
"@tiptap/vue-3@^2.0.3":
"@tiptap/vue-3@^2.0.3", "@tiptap/vue-3@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@tiptap/vue-3/-/vue-3-2.0.4.tgz#f04116a42aa60285026541a9c8db459dfa92e1a9"
integrity sha512-XfoFl1RKCElYIoloGoqMC2iG4RalEtaGvwSAmqqNGdITCdwnuDhLlCvGAjnVbIR4d3Y0NRPyXZzGWfWSi4bbHg==