fix: send message from whatsapp tab
This commit is contained in:
parent
61c8b16ff1
commit
11770489dc
@ -341,7 +341,20 @@ def get_linked_tasks(name):
|
|||||||
def get_whatsapp_messages(name):
|
def get_whatsapp_messages(name):
|
||||||
whatsapp_messages = frappe.db.get_all(
|
whatsapp_messages = frappe.db.get_all(
|
||||||
"WhatsApp Message",
|
"WhatsApp Message",
|
||||||
filters={"reference_doctype": "CRM Lead", "reference_name": name, "status": ("not in", ["failed", "Success"])},
|
filters={"reference_doctype": "CRM Lead", "reference_name": name, "status": ("not in", ["failed"])},
|
||||||
fields=["name", "type", "to", "from", "content_type", "creation", "message", "status"],
|
fields=["name", "type", "to", "from", "content_type", "creation", "message", "status"],
|
||||||
)
|
)
|
||||||
return whatsapp_messages or []
|
return whatsapp_messages or []
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_whatsapp_message(reference_doctype, reference_name, to, message, content_type="text"):
|
||||||
|
doc = frappe.new_doc("WhatsApp Message")
|
||||||
|
doc.update({
|
||||||
|
"reference_doctype": reference_doctype,
|
||||||
|
"reference_name": reference_name,
|
||||||
|
"to": to,
|
||||||
|
"message": message,
|
||||||
|
"content_type": content_type,
|
||||||
|
})
|
||||||
|
doc.insert(ignore_permissions=True)
|
||||||
|
return doc.name
|
||||||
@ -35,16 +35,23 @@
|
|||||||
</template>
|
</template>
|
||||||
<span>{{ __('New Task') }}</span>
|
<span>{{ __('New Task') }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<div class="flex gap-2" v-else-if="title == 'WhatsApp'">
|
||||||
v-else-if="title == 'WhatsApp'"
|
<Button
|
||||||
variant="solid"
|
:label="__('Refresh')"
|
||||||
@click="$refs.whatsappBox.show = true"
|
@click="whatsappMessages.reload()"
|
||||||
>
|
:loading="whatsappMessages.loading"
|
||||||
<template #prefix>
|
>
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
<template #icon>
|
||||||
</template>
|
<RefreshIcon class="h-4 w-4" />
|
||||||
<span>{{ __('New WhatsApp Message') }}</span>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="solid" @click="$refs.whatsappBox.show = true">
|
||||||
|
<template #prefix>
|
||||||
|
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
<span>{{ __('New WhatsApp Message') }}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-else
|
v-else
|
||||||
:options="[
|
:options="[
|
||||||
@ -99,7 +106,16 @@
|
|||||||
<LoadingIndicator class="h-6 w-6" />
|
<LoadingIndicator class="h-6 w-6" />
|
||||||
<span>{{ __('Loading...') }}</span>
|
<span>{{ __('Loading...') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="activities?.length" class="activities flex-1 overflow-y-auto">
|
<div
|
||||||
|
v-else-if="title == 'WhatsApp' && whatsappMessages.data?.length"
|
||||||
|
class="activities flex-1 overflow-y-auto"
|
||||||
|
>
|
||||||
|
<WhatsAppArea class="px-10" :messages="whatsappMessages.data" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="activities?.length"
|
||||||
|
class="activities flex-1 overflow-y-auto"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="title == 'Notes'"
|
v-if="title == 'Notes'"
|
||||||
class="activity grid grid-cols-1 gap-4 px-10 pb-5 lg:grid-cols-2 xl:grid-cols-3"
|
class="activity grid grid-cols-1 gap-4 px-10 pb-5 lg:grid-cols-2 xl:grid-cols-3"
|
||||||
@ -353,11 +369,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<WhatsAppArea
|
|
||||||
v-else-if="title == 'WhatsApp'"
|
|
||||||
class="px-10"
|
|
||||||
:messages="activities"
|
|
||||||
/>
|
|
||||||
<div v-else v-for="(activity, i) in activities" class="activity">
|
<div v-else v-for="(activity, i) in activities" class="activity">
|
||||||
<div class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-10">
|
<div class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-10">
|
||||||
<div
|
<div
|
||||||
@ -766,6 +777,14 @@
|
|||||||
:doctype="doctype"
|
:doctype="doctype"
|
||||||
@scroll="scroll"
|
@scroll="scroll"
|
||||||
/>
|
/>
|
||||||
|
<WhatsAppBox
|
||||||
|
ref="whatsappBox"
|
||||||
|
v-if="title == 'WhatsApp'"
|
||||||
|
v-model="doc"
|
||||||
|
v-model:whatsapp="whatsappMessages"
|
||||||
|
:doctype="doctype"
|
||||||
|
@scroll="scroll"
|
||||||
|
/>
|
||||||
<NoteModal
|
<NoteModal
|
||||||
v-model="showNoteModal"
|
v-model="showNoteModal"
|
||||||
v-model:reloadNotes="all_activities"
|
v-model:reloadNotes="all_activities"
|
||||||
@ -790,6 +809,8 @@ import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
|||||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||||
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
||||||
import WhatsAppArea from '@/components/WhatsAppArea.vue'
|
import WhatsAppArea from '@/components/WhatsAppArea.vue'
|
||||||
|
import WhatsAppBox from '@/components/WhatsAppBox.vue'
|
||||||
|
import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
|
||||||
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
||||||
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
||||||
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
||||||
@ -902,6 +923,7 @@ const whatsappMessages = createResource({
|
|||||||
params: { name: doc.value.data.name },
|
params: { name: doc.value.data.name },
|
||||||
cache: ['whatsapp', doc.value.data.name],
|
cache: ['whatsapp', doc.value.data.name],
|
||||||
auto: true,
|
auto: true,
|
||||||
|
onSuccess: () => nextTick(() => scroll()),
|
||||||
})
|
})
|
||||||
|
|
||||||
function get_activities() {
|
function get_activities() {
|
||||||
@ -929,10 +951,8 @@ const activities = computed(() => {
|
|||||||
} else if (props.title == 'Notes') {
|
} else if (props.title == 'Notes') {
|
||||||
if (!all_activities.data?.notes) return []
|
if (!all_activities.data?.notes) return []
|
||||||
return sortByCreation(all_activities.data.notes)
|
return sortByCreation(all_activities.data.notes)
|
||||||
} else if (props.title == 'WhatsApp') {
|
|
||||||
if (!whatsappMessages.data) return []
|
|
||||||
return sortByCreation(whatsappMessages.data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activities.forEach((activity) => {
|
activities.forEach((activity) => {
|
||||||
activity.icon = timelineIcon(activity.activity_type, activity.is_lead)
|
activity.icon = timelineIcon(activity.activity_type, activity.is_lead)
|
||||||
|
|
||||||
|
|||||||
@ -3,19 +3,24 @@
|
|||||||
<div
|
<div
|
||||||
v-for="whatsapp in messages"
|
v-for="whatsapp in messages"
|
||||||
:key="whatsapp.name"
|
:key="whatsapp.name"
|
||||||
class="flex"
|
class="activity flex"
|
||||||
:class="{ 'justify-end': whatsapp.type == 'Outgoing' }"
|
:class="{ 'justify-end': whatsapp.type == 'Outgoing' }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mb-3 inline-flex max-w-[90%] gap-2 rounded-md bg-gray-50 p-2 text-base shadow-sm"
|
class="mb-3 inline-flex max-w-[90%] gap-2 rounded-md bg-gray-50 p-1.5 pl-2 text-base shadow-sm"
|
||||||
>
|
>
|
||||||
<div>{{ whatsapp.message }}</div>
|
<div v-html="formatWhatsAppMessage(whatsapp.message)"></div>
|
||||||
<div class="-mb-1 flex items-end gap-1 text-gray-600 shrink-0">
|
<div class="-mb-1 flex shrink-0 items-end gap-1 text-gray-600">
|
||||||
<Tooltip :text="dateFormat(whatsapp.creation, 'ddd, MMM D, YYYY')">
|
<Tooltip :text="dateFormat(whatsapp.creation, 'ddd, MMM D, YYYY')">
|
||||||
<div class="text-2xs">{{ dateFormat(whatsapp.creation, 'hh:mm a') }}</div>
|
<div class="text-2xs">
|
||||||
|
{{ dateFormat(whatsapp.creation, 'hh:mm a') }}
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div v-if="whatsapp.type == 'Outgoing'">
|
<div v-if="whatsapp.type == 'Outgoing'">
|
||||||
<CheckIcon v-if="whatsapp.status == 'sent'" class="size-4" />
|
<CheckIcon
|
||||||
|
v-if="['sent', 'Success'].includes(whatsapp.status)"
|
||||||
|
class="size-4"
|
||||||
|
/>
|
||||||
<DoubleCheckIcon
|
<DoubleCheckIcon
|
||||||
v-else-if="['read', 'delivered'].includes(whatsapp.status)"
|
v-else-if="['read', 'delivered'].includes(whatsapp.status)"
|
||||||
class="size-4"
|
class="size-4"
|
||||||
@ -37,4 +42,28 @@ import { dateFormat } from '@/utils'
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
messages: Array,
|
messages: Array,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function formatWhatsAppMessage(message) {
|
||||||
|
// if message contains _text_, make it italic
|
||||||
|
message = message.replace(/_(.*?)_/g, '<i>$1</i>')
|
||||||
|
// if message contains *text*, make it bold
|
||||||
|
message = message.replace(/\*(.*?)\*/g, '<b>$1</b>')
|
||||||
|
// if message contains ~text~, make it strikethrough
|
||||||
|
message = message.replace(/~(.*?)~/g, '<s>$1</s>')
|
||||||
|
// if message contains ```text```, make it monospace
|
||||||
|
message = message.replace(/```(.*?)```/g, '<code>$1</code>')
|
||||||
|
// if message contains `text`, make it inline code
|
||||||
|
message = message.replace(/`(.*?)`/g, '<code>$1</code>')
|
||||||
|
// if message contains > text, make it a blockquote
|
||||||
|
message = message.replace(/^> (.*)$/gm, '<blockquote>$1</blockquote>')
|
||||||
|
// if contain /n, make it a new line
|
||||||
|
message = message.replace(/\n/g, '<br>')
|
||||||
|
// if contains *<space>text, make it a bullet point
|
||||||
|
message = message.replace(/\* (.*?)(?=\s*\*|$)/g, '<li>$1</li>')
|
||||||
|
message = message.replace(/- (.*?)(?=\s*-|$)/g, '<li>$1</li>')
|
||||||
|
message = message.replace(/(\d+)\. (.*?)(?=\s*(\d+)\.|$)/g, '<li>$2</li>')
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
59
frontend/src/components/WhatsAppBox.vue
Normal file
59
frontend/src/components/WhatsAppBox.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-end gap-2 px-10 py-2.5">
|
||||||
|
<Textarea
|
||||||
|
type="textarea"
|
||||||
|
class="min-h-8 w-full"
|
||||||
|
:rows="rows"
|
||||||
|
v-model="content"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
@focus="rows = 6"
|
||||||
|
@blur="rows = 1"
|
||||||
|
/>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
class="min-h-8"
|
||||||
|
variant="solid"
|
||||||
|
:label="__('Send')"
|
||||||
|
@click="sendWhatsAppMessage"
|
||||||
|
:disabled="isEmpty"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { createResource, Textarea } from 'frappe-ui'
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
doctype: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const doc = defineModel()
|
||||||
|
const whatsapp = defineModel('whatsapp')
|
||||||
|
const rows = ref(1)
|
||||||
|
|
||||||
|
const content = ref('')
|
||||||
|
const placeholder = ref(__('Type your message here...'))
|
||||||
|
|
||||||
|
const isEmpty = computed(() => {
|
||||||
|
return !content.value || content.value === '<p></p>'
|
||||||
|
})
|
||||||
|
|
||||||
|
async function sendWhatsAppMessage() {
|
||||||
|
let args = {
|
||||||
|
reference_doctype: props.doctype,
|
||||||
|
reference_name: doc.value.data.name,
|
||||||
|
message: content.value,
|
||||||
|
to: doc.value.data.mobile_no,
|
||||||
|
content_type: 'text',
|
||||||
|
}
|
||||||
|
content.value = ''
|
||||||
|
createResource({
|
||||||
|
url: 'crm.api.activities.create_whatsapp_message',
|
||||||
|
params: args,
|
||||||
|
auto: true,
|
||||||
|
onSuccess: () => nextTick(() => whatsapp.value?.reload()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user