feat: added whatsapp messages on lead/deal page in whatsapp tab

This commit is contained in:
Shariq Ansari 2024-04-17 15:24:55 +05:30
parent e04a4c6b4c
commit 61c8b16ff1
8 changed files with 158 additions and 0 deletions

View File

@ -336,3 +336,12 @@ def get_linked_tasks(name):
], ],
) )
return tasks or [] return tasks or []
@frappe.whitelist()
def get_whatsapp_messages(name):
whatsapp_messages = frappe.db.get_all(
"WhatsApp Message",
filters={"reference_doctype": "CRM Lead", "reference_name": name, "status": ("not in", ["failed", "Success"])},
fields=["name", "type", "to", "from", "content_type", "creation", "message", "status"],
)
return whatsapp_messages or []

View File

@ -35,6 +35,16 @@
</template> </template>
<span>{{ __('New Task') }}</span> <span>{{ __('New Task') }}</span>
</Button> </Button>
<Button
v-else-if="title == 'WhatsApp'"
variant="solid"
@click="$refs.whatsappBox.show = true"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New WhatsApp Message') }}</span>
</Button>
<Dropdown <Dropdown
v-else v-else
:options="[ :options="[
@ -58,6 +68,11 @@
label: __('New Task'), label: __('New Task'),
onClick: () => showTask(), onClick: () => showTask(),
}, },
{
icon: h(WhatsAppIcon, { class: 'h-4 w-4' }),
label: __('New WhatsApp Message'),
onClick: () => ($refs.emailBox.show = true),
},
]" ]"
@click.stop @click.stop
> >
@ -338,6 +353,11 @@
</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
@ -768,6 +788,8 @@ import EmailIcon from '@/components/Icons/EmailIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' 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 WhatsAppArea from '@/components/WhatsAppArea.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'
@ -875,6 +897,13 @@ const all_activities = createResource({
}, },
}) })
const whatsappMessages = createResource({
url: 'crm.api.activities.get_whatsapp_messages',
params: { name: doc.value.data.name },
cache: ['whatsapp', doc.value.data.name],
auto: true,
})
function get_activities() { function get_activities() {
if (!all_activities.data?.versions) return [] if (!all_activities.data?.versions) return []
if (!all_activities.data?.calls.length) if (!all_activities.data?.calls.length)
@ -900,6 +929,9 @@ 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)
@ -958,6 +990,8 @@ const emptyText = computed(() => {
text = 'No Notes' text = 'No Notes'
} else if (props.title == 'Tasks') { } else if (props.title == 'Tasks') {
text = 'No Tasks' text = 'No Tasks'
} else if (props.title == 'WhatsApp') {
text = 'No WhatsApp Messages'
} }
return text return text
}) })
@ -972,6 +1006,8 @@ const emptyTextIcon = computed(() => {
icon = NoteIcon icon = NoteIcon
} else if (props.title == 'Tasks') { } else if (props.title == 'Tasks') {
icon = TaskIcon icon = TaskIcon
} else if (props.title == 'WhatsApp') {
icon = WhatsAppIcon
} }
return h(icon, { class: 'text-gray-500' }) return h(icon, { class: 'text-gray-500' })
}) })

View File

@ -0,0 +1,16 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-check"
>
<path d="M20 6 9 17l-5-5" />
</svg>
</template>

View File

@ -0,0 +1,17 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-check-check"
>
<path d="M18 6 7 17l-5-5" />
<path d="m22 10-7.5 7.5L13 16" />
</svg>
</template>

View File

@ -0,0 +1,28 @@
<template>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 15.5C7.5 16.5 10.3367 17.2101 12.0757 16.7964C13.8147 16.3826 15.3488 15.3614 16.4015 13.9167C17.4541 12.472 17.9562 10.6988 17.8172 8.91668C17.6781 7.13456 16.9072 5.46069 15.6432 4.19671C14.3792 2.93273 12.7053 2.16176 10.9232 2.02273C9.14108 1.8837 7.36789 2.38575 5.92318 3.43842C4.47847 4.49109 3.45724 6.02514 3.04352 7.76414C2.62979 9.50314 3 12 4 13.5L2.5 17L6 15.5Z"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.3355 12.3844L12 11.5C12.4877 11.0123 12.775 11.209 13.4481 11.4793C13.4824 11.4931 13.515 11.5112 13.5445 11.5333L13.8563 11.7664C14.0434 11.8987 14.0487 12.1733 13.8668 12.3126L13.2106 12.8153C12.9462 13.0178 12.5923 13.0604 12.2947 12.91C11.5548 12.536 10.1264 11.74 9.11332 10.7151C8.13237 9.72267 7.42487 8.40546 7.07965 7.68204C6.92664 7.3614 7.0003 6.98556 7.24735 6.72951L7.85039 6.10451C8.00431 5.94497 8.26796 5.97191 8.38609 6.15923L8.79347 6.72951C9.11332 7.24052 8.65816 7.67639 8.38609 7.85843L7.5 8.49417"
fill="currentColor"
/>
<path
d="M11.3355 12.3844L12 11.5C12.4877 11.0123 12.775 11.209 13.4481 11.4793C13.4824 11.4931 13.515 11.5112 13.5445 11.5333L13.8563 11.7664C14.0434 11.8987 14.0487 12.1733 13.8668 12.3126L13.2106 12.8153C12.9462 13.0178 12.5923 13.0604 12.2947 12.91C11.5548 12.536 10.1264 11.74 9.11332 10.7151C8.13237 9.72267 7.42487 8.40546 7.07965 7.68204C6.92664 7.3614 7.0003 6.98556 7.24735 6.72951L7.85039 6.10451C8.00431 5.94497 8.26796 5.97191 8.38609 6.15923L8.79347 6.72951C9.11332 7.24052 8.65816 7.67639 8.38609 7.85843L7.5 8.49417"
stroke="currentColor"
stroke-linejoin="round"
/>
</svg>
</template>

View File

@ -0,0 +1,40 @@
<template>
<div>
<div
v-for="whatsapp in messages"
:key="whatsapp.name"
class="flex"
:class="{ 'justify-end': whatsapp.type == 'Outgoing' }"
>
<div
class="mb-3 inline-flex max-w-[90%] gap-2 rounded-md bg-gray-50 p-2 text-base shadow-sm"
>
<div>{{ whatsapp.message }}</div>
<div class="-mb-1 flex items-end gap-1 text-gray-600 shrink-0">
<Tooltip :text="dateFormat(whatsapp.creation, 'ddd, MMM D, YYYY')">
<div class="text-2xs">{{ dateFormat(whatsapp.creation, 'hh:mm a') }}</div>
</Tooltip>
<div v-if="whatsapp.type == 'Outgoing'">
<CheckIcon v-if="whatsapp.status == 'sent'" class="size-4" />
<DoubleCheckIcon
v-else-if="['read', 'delivered'].includes(whatsapp.status)"
class="size-4"
:class="{ 'text-blue-500': whatsapp.status == 'read' }"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import CheckIcon from '@/components/Icons/CheckIcon.vue'
import DoubleCheckIcon from '@/components/Icons/DoubleCheckIcon.vue'
import { Tooltip } from 'frappe-ui'
import { dateFormat } from '@/utils'
const props = defineProps({
messages: Array,
})
</script>

View File

@ -293,6 +293,7 @@ import EmailIcon from '@/components/Icons/EmailIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue'
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue' import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue' import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
@ -459,6 +460,11 @@ const tabs = [
label: __('Notes'), label: __('Notes'),
icon: NoteIcon, icon: NoteIcon,
}, },
{
name: 'WhatsApp',
label: __('WhatsApp'),
icon: WhatsAppIcon,
},
] ]
const detailSections = computed(() => { const detailSections = computed(() => {

View File

@ -260,6 +260,7 @@ import EmailIcon from '@/components/Icons/EmailIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue'
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue' import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue' import LinkIcon from '@/components/Icons/LinkIcon.vue'
@ -423,6 +424,11 @@ const tabs = [
label: __('Notes'), label: __('Notes'),
icon: NoteIcon, icon: NoteIcon,
}, },
{
name: 'WhatsApp',
label: __('WhatsApp'),
icon: WhatsAppIcon,
},
] ]
function validateFile(file) { function validateFile(file) {