Merge pull request #540 from frappe/develop
This commit is contained in:
commit
93bc4e6dba
@ -47,8 +47,7 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Initiated\nRinging\nIn Progress\nCompleted\nFailed\nBusy\nNo Answer\nQueued\nCanceled",
|
"options": "Initiated\nRinging\nIn Progress\nCompleted\nFailed\nBusy\nNo Answer\nQueued\nCanceled"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "start_time",
|
"fieldname": "start_time",
|
||||||
@ -83,8 +82,7 @@
|
|||||||
"fieldname": "duration",
|
"fieldname": "duration",
|
||||||
"fieldtype": "Duration",
|
"fieldtype": "Duration",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Duration",
|
"label": "Duration"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "recording_url",
|
"fieldname": "recording_url",
|
||||||
@ -145,7 +143,8 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Telephony Medium",
|
"label": "Telephony Medium",
|
||||||
"options": "\nManual\nTwilio\nExotel"
|
"options": "\nManual\nTwilio\nExotel",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_gyqe",
|
"fieldname": "section_break_gyqe",
|
||||||
@ -154,7 +153,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-17 21:46:01.558377",
|
"modified": "2025-01-22 17:57:59.289548",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Call Log",
|
"name": "CRM Call Log",
|
||||||
|
|||||||
@ -97,7 +97,7 @@ class CRMCallLog(Document):
|
|||||||
|
|
||||||
def parse_call_log(call):
|
def parse_call_log(call):
|
||||||
call["show_recording"] = False
|
call["show_recording"] = False
|
||||||
call["duration"] = seconds_to_duration(call.get("duration"))
|
call["_duration"] = seconds_to_duration(call.get("duration"))
|
||||||
if call.get("type") == "Incoming":
|
if call.get("type") == "Incoming":
|
||||||
call["activity_type"] = "incoming_call"
|
call["activity_type"] = "incoming_call"
|
||||||
contact = get_contact_by_phone_number(call.get("from"))
|
contact = get_contact_by_phone_number(call.get("from"))
|
||||||
@ -106,11 +106,11 @@ def parse_call_log(call):
|
|||||||
if call.get("receiver")
|
if call.get("receiver")
|
||||||
else [None, None]
|
else [None, None]
|
||||||
)
|
)
|
||||||
call["caller"] = {
|
call["_caller"] = {
|
||||||
"label": contact.get("full_name", "Unknown"),
|
"label": contact.get("full_name", "Unknown"),
|
||||||
"image": contact.get("image"),
|
"image": contact.get("image"),
|
||||||
}
|
}
|
||||||
call["receiver"] = {
|
call["_receiver"] = {
|
||||||
"label": receiver[0],
|
"label": receiver[0],
|
||||||
"image": receiver[1],
|
"image": receiver[1],
|
||||||
}
|
}
|
||||||
@ -122,11 +122,11 @@ def parse_call_log(call):
|
|||||||
if call.get("caller")
|
if call.get("caller")
|
||||||
else [None, None]
|
else [None, None]
|
||||||
)
|
)
|
||||||
call["caller"] = {
|
call["_caller"] = {
|
||||||
"label": caller[0],
|
"label": caller[0],
|
||||||
"image": caller[1],
|
"image": caller[1],
|
||||||
}
|
}
|
||||||
call["receiver"] = {
|
call["_receiver"] = {
|
||||||
"label": contact.get("full_name", "Unknown"),
|
"label": contact.get("full_name", "Unknown"),
|
||||||
"image": contact.get("image"),
|
"image": contact.get("image"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,6 +138,10 @@ def add_default_fields_layout(force=False):
|
|||||||
"doctype": "Address",
|
"doctype": "Address",
|
||||||
"layout": '[{"name": "details_section", "columns": [{"name": "column_uSSG", "fields": ["address_title", "address_type", "address_line1", "address_line2", "city", "state", "country", "pincode"]}]}]',
|
"layout": '[{"name": "details_section", "columns": [{"name": "column_uSSG", "fields": ["address_title", "address_type", "address_line1", "address_line2", "city", "state", "country", "pincode"]}]}]',
|
||||||
},
|
},
|
||||||
|
"CRM Call Log-Quick Entry": {
|
||||||
|
"doctype": "CRM Call Log",
|
||||||
|
"layout": '[{"name":"details_section","columns":[{"name":"column_uMSG","fields":["type","from","duration"]},{"name":"column_wiZT","fields":["to","status","caller","receiver"]}]}]',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebar_fields_layouts = {
|
sidebar_fields_layouts = {
|
||||||
|
|||||||
@ -53,15 +53,17 @@ def add_note_to_call_log(call_sid, note):
|
|||||||
_note = frappe.get_doc(
|
_note = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "FCRM Note",
|
"doctype": "FCRM Note",
|
||||||
|
"title": note.get("title", "Call Note"),
|
||||||
"content": note.get("content"),
|
"content": note.get("content"),
|
||||||
}
|
}
|
||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True)
|
||||||
call_log = frappe.get_cached_doc("CRM Call Log", call_sid)
|
|
||||||
call_log.link_with_reference_doc("FCRM Note", _note.name)
|
|
||||||
call_log.save(ignore_permissions=True)
|
|
||||||
else:
|
else:
|
||||||
_note = frappe.set_value("FCRM Note", note.get("name"), "content", note.get("content"))
|
_note = frappe.set_value("FCRM Note", note.get("name"), "content", note.get("content"))
|
||||||
|
|
||||||
|
call_log = frappe.get_cached_doc("CRM Call Log", call_sid)
|
||||||
|
call_log.link_with_reference_doc("FCRM Note", _note.name)
|
||||||
|
call_log.save(ignore_permissions=True)
|
||||||
|
|
||||||
return _note
|
return _note
|
||||||
|
|
||||||
|
|
||||||
@ -75,21 +77,30 @@ def add_task_to_call_log(call_sid, task):
|
|||||||
"doctype": "CRM Task",
|
"doctype": "CRM Task",
|
||||||
"title": task.get("title"),
|
"title": task.get("title"),
|
||||||
"description": task.get("description"),
|
"description": task.get("description"),
|
||||||
|
"assigned_to": task.get("assigned_to"),
|
||||||
|
"due_date": task.get("due_date"),
|
||||||
|
"status": task.get("status"),
|
||||||
|
"priority": task.get("priority"),
|
||||||
}
|
}
|
||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True)
|
||||||
call_log = frappe.get_doc("CRM Call Log", call_sid)
|
|
||||||
call_log.link_with_reference_doc("CRM Task", _task.name)
|
|
||||||
call_log.save(ignore_permissions=True)
|
|
||||||
else:
|
else:
|
||||||
_task = frappe.get_doc("CRM Task", task.get("name"))
|
_task = frappe.get_doc("CRM Task", task.get("name"))
|
||||||
_task.update(
|
_task.update(
|
||||||
{
|
{
|
||||||
"title": task.get("title"),
|
"title": task.get("title"),
|
||||||
"description": task.get("description"),
|
"description": task.get("description"),
|
||||||
|
"assigned_to": task.get("assigned_to"),
|
||||||
|
"due_date": task.get("due_date"),
|
||||||
|
"status": task.get("status"),
|
||||||
|
"priority": task.get("priority"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
_task.save(ignore_permissions=True)
|
_task.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
call_log = frappe.get_doc("CRM Call Log", call_sid)
|
||||||
|
call_log.link_with_reference_doc("CRM Task", _task.name)
|
||||||
|
call_log.save(ignore_permissions=True)
|
||||||
|
|
||||||
return _task
|
return _task
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ crm.patches.v1_0.rename_twilio_settings_to_crm_twilio_settings
|
|||||||
[post_model_sync]
|
[post_model_sync]
|
||||||
# Patches added in this section will be executed after doctypes are migrated
|
# Patches added in this section will be executed after doctypes are migrated
|
||||||
crm.patches.v1_0.create_email_template_custom_fields
|
crm.patches.v1_0.create_email_template_custom_fields
|
||||||
crm.patches.v1_0.create_default_fields_layout #10/12/2024
|
crm.patches.v1_0.create_default_fields_layout #22/01/2025
|
||||||
crm.patches.v1_0.create_default_sidebar_fields_layout
|
crm.patches.v1_0.create_default_sidebar_fields_layout
|
||||||
crm.patches.v1_0.update_deal_quick_entry_layout
|
crm.patches.v1_0.update_deal_quick_entry_layout
|
||||||
crm.patches.v1_0.update_layouts_to_new_format
|
crm.patches.v1_0.update_layouts_to_new_format
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
from crm.install import add_default_fields_layout
|
from crm.install import add_default_fields_layout
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
add_default_fields_layout()
|
add_default_fields_layout()
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 863eaae9ada2edb287fc09fb21d05212bb5eebe9
|
Subproject commit aea806331c179cc0cdb89b6a2f9de2130bd1061f
|
||||||
@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click="showCallLogModal = true" class="cursor-pointer">
|
<div @click="showCallLogDetailModal = true" class="cursor-pointer">
|
||||||
<div class="mb-1 flex items-center justify-stretch gap-2 py-1 text-base">
|
<div class="mb-1 flex items-center justify-stretch gap-2 py-1 text-base">
|
||||||
<div class="inline-flex items-center flex-wrap gap-1 text-ink-gray-5">
|
<div class="inline-flex items-center flex-wrap gap-1 text-ink-gray-5">
|
||||||
<Avatar
|
<Avatar
|
||||||
:image="activity.caller.image"
|
:image="activity._caller.image"
|
||||||
:label="activity.caller.label"
|
:label="activity._caller.label"
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
<span class="font-medium text-ink-gray-8 ml-1">
|
<span class="font-medium text-ink-gray-8 ml-1">
|
||||||
{{ activity.caller.label }}
|
{{ activity._caller.label }}
|
||||||
</span>
|
</span>
|
||||||
<span>{{
|
<span>{{
|
||||||
activity.type == 'Incoming'
|
activity.type == 'Incoming'
|
||||||
@ -41,14 +41,14 @@
|
|||||||
<MultipleAvatar
|
<MultipleAvatar
|
||||||
:avatars="[
|
:avatars="[
|
||||||
{
|
{
|
||||||
image: activity.caller.image,
|
image: activity._caller.image,
|
||||||
label: activity.caller.label,
|
label: activity._caller.label,
|
||||||
name: activity.caller.label,
|
name: activity._caller.label,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: activity.receiver.image,
|
image: activity._receiver.image,
|
||||||
label: activity.receiver.label,
|
label: activity._receiver.label,
|
||||||
name: activity.receiver.label,
|
name: activity._receiver.label,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -61,7 +61,10 @@
|
|||||||
<CalendarIcon class="size-3" />
|
<CalendarIcon class="size-3" />
|
||||||
</template>
|
</template>
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge v-if="activity.status == 'Completed'" :label="activity.duration">
|
<Badge
|
||||||
|
v-if="activity.status == 'Completed'"
|
||||||
|
:label="activity._duration"
|
||||||
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<DurationIcon class="size-3" />
|
<DurationIcon class="size-3" />
|
||||||
</template>
|
</template>
|
||||||
@ -89,7 +92,12 @@
|
|||||||
<AudioPlayer :src="activity.recording_url" />
|
<AudioPlayer :src="activity.recording_url" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CallLogModal v-model="showCallLogModal" :name="callLogName" />
|
<CallLogDetailModal
|
||||||
|
v-model="showCallLogDetailModal"
|
||||||
|
v-model:callLogModal="showCallLogModal"
|
||||||
|
v-model:callLog="callLog"
|
||||||
|
/>
|
||||||
|
<CallLogModal v-model="showCallLogModal" v-model:callLog="callLog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -98,16 +106,23 @@ import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
|||||||
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
||||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||||
import AudioPlayer from '@/components/Activities/AudioPlayer.vue'
|
import AudioPlayer from '@/components/Activities/AudioPlayer.vue'
|
||||||
|
import CallLogDetailModal from '@/components/Modals/CallLogDetailModal.vue'
|
||||||
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
||||||
import { statusLabelMap, statusColorMap } from '@/utils/callLog.js'
|
import { statusLabelMap, statusColorMap } from '@/utils/callLog.js'
|
||||||
import { formatDate, timeAgo } from '@/utils'
|
import { formatDate, timeAgo } from '@/utils'
|
||||||
import { Avatar, Badge, Tooltip } from 'frappe-ui'
|
import { Avatar, Badge, Tooltip, createResource } from 'frappe-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
activity: Object,
|
activity: Object,
|
||||||
})
|
})
|
||||||
|
|
||||||
const callLogName = ref(props.activity.name)
|
const callLog = createResource({
|
||||||
|
url: 'crm.fcrm.doctype.crm_call_log.crm_call_log.get_call_log',
|
||||||
|
params: { name: props.activity.name },
|
||||||
|
cache: ['call_log', props.activity.name],
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
const showCallLogDetailModal = ref(false)
|
||||||
const showCallLogModal = ref(false)
|
const showCallLogModal = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
372
frontend/src/components/Modals/CallLogDetailModal.vue
Normal file
372
frontend/src/components/Modals/CallLogDetailModal.vue
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="show">
|
||||||
|
<template #body>
|
||||||
|
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
|
||||||
|
<div class="mb-5 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||||
|
{{ __('Call Details') }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Dropdown
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
group: __('Options'),
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: note?.name ? __('Edit note') : __('Add note'),
|
||||||
|
icon: NoteIcon,
|
||||||
|
onClick: addEditNote,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: task?.name ? __('Edit task') : __('Add task'),
|
||||||
|
icon: TaskIcon,
|
||||||
|
onClick: addEditTask,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<Button variant="ghost" icon="more-horizontal" />
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
|
<Button
|
||||||
|
v-if="isManager() && !isMobileView"
|
||||||
|
variant="ghost"
|
||||||
|
class="w-7"
|
||||||
|
@click="openCallLogModal"
|
||||||
|
>
|
||||||
|
<EditIcon class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" class="w-7" @click="show = false">
|
||||||
|
<FeatherIcon name="x" class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3.5">
|
||||||
|
<div
|
||||||
|
v-for="field in detailFields"
|
||||||
|
:key="field.name"
|
||||||
|
class="flex gap-2 text-base text-ink-gray-8"
|
||||||
|
>
|
||||||
|
<div class="grid size-7 place-content-center">
|
||||||
|
<component :is="field.icon" />
|
||||||
|
</div>
|
||||||
|
<div class="flex min-h-7 w-full items-center gap-2">
|
||||||
|
<div
|
||||||
|
v-if="field.name == 'receiver'"
|
||||||
|
class="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
:image="field.value.caller.image"
|
||||||
|
:label="field.value.caller.label"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
<div class="ml-1 flex flex-col gap-1">
|
||||||
|
{{ field.value.caller.label }}
|
||||||
|
</div>
|
||||||
|
<FeatherIcon
|
||||||
|
name="arrow-right"
|
||||||
|
class="mx-1 h-4 w-4 text-ink-gray-5"
|
||||||
|
/>
|
||||||
|
<Avatar
|
||||||
|
:image="field.value.receiver.image"
|
||||||
|
:label="field.value.receiver.label"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
<div class="ml-1 flex flex-col gap-1">
|
||||||
|
{{ field.value.receiver.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tooltip v-else-if="field.tooltip" :text="field.tooltip">
|
||||||
|
{{ field.value }}
|
||||||
|
</Tooltip>
|
||||||
|
<div class="w-full" v-else-if="field.name == 'recording_url'">
|
||||||
|
<audio
|
||||||
|
class="audio-control w-full"
|
||||||
|
controls
|
||||||
|
:src="field.value"
|
||||||
|
></audio>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-full cursor-pointer rounded border px-2 pt-1.5 text-base text-ink-gray-7"
|
||||||
|
v-else-if="field.name == 'note'"
|
||||||
|
@click="() => (showNoteModal = true)"
|
||||||
|
>
|
||||||
|
<FadedScrollableDiv class="max-h-24 min-h-16 overflow-y-auto">
|
||||||
|
<div
|
||||||
|
v-if="field.value?.title"
|
||||||
|
:class="[field.value?.content ? 'mb-1 font-bold' : '']"
|
||||||
|
v-html="field.value?.title"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="field.value?.content"
|
||||||
|
v-html="field.value?.content"
|
||||||
|
/>
|
||||||
|
</FadedScrollableDiv>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-full cursor-pointer rounded border px-2 pt-1.5 text-base text-ink-gray-7"
|
||||||
|
v-else-if="field.name == 'task'"
|
||||||
|
@click="() => (showTaskModal = true)"
|
||||||
|
>
|
||||||
|
<FadedScrollableDiv class="max-h-24 min-h-16 overflow-y-auto">
|
||||||
|
<div
|
||||||
|
v-if="field.value?.title"
|
||||||
|
:class="[field.value?.description ? 'mb-1 font-bold' : '']"
|
||||||
|
v-html="field.value?.title"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="field.value?.description"
|
||||||
|
v-html="field.value?.description"
|
||||||
|
/>
|
||||||
|
</FadedScrollableDiv>
|
||||||
|
</div>
|
||||||
|
<div v-else :class="field.color ? `text-${field.color}-600` : ''">
|
||||||
|
{{ field.value }}
|
||||||
|
</div>
|
||||||
|
<div v-if="field.link">
|
||||||
|
<ArrowUpRightIcon
|
||||||
|
class="h-4 w-4 shrink-0 cursor-pointer text-ink-gray-5 hover:text-ink-gray-8"
|
||||||
|
@click="() => field.link()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!callLog?.data?._lead && !callLog?.data?._deal"
|
||||||
|
class="px-4 pb-7 pt-4 sm:px-6"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
class="w-full"
|
||||||
|
variant="solid"
|
||||||
|
:label="__('Create lead')"
|
||||||
|
@click="createLead"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
<NoteModal v-model="showNoteModal" :note="note" @after="addNoteToCallLog" />
|
||||||
|
<TaskModal v-model="showTaskModal" :task="task" @after="addTaskToCallLog" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
|
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
||||||
|
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
||||||
|
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||||
|
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||||
|
import Dealsicon from '@/components/Icons/DealsIcon.vue'
|
||||||
|
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
||||||
|
import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
||||||
|
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
||||||
|
import CheckCircleIcon from '@/components/Icons/CheckCircleIcon.vue'
|
||||||
|
import NoteModal from '@/components/Modals/NoteModal.vue'
|
||||||
|
import TaskModal from '@/components/Modals/TaskModal.vue'
|
||||||
|
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
|
||||||
|
import { getCallLogDetail } from '@/utils/callLog'
|
||||||
|
import { usersStore } from '@/stores/users'
|
||||||
|
import { isMobileView } from '@/composables/settings'
|
||||||
|
import { FeatherIcon, Dropdown, Avatar, Tooltip, call } from 'frappe-ui'
|
||||||
|
import { ref, computed, h, nextTick } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const { isManager } = usersStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const show = defineModel()
|
||||||
|
const showNoteModal = ref(false)
|
||||||
|
const showTaskModal = ref(false)
|
||||||
|
|
||||||
|
const callLog = defineModel('callLog')
|
||||||
|
|
||||||
|
const note = ref({
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const task = ref({
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
assigned_to: '',
|
||||||
|
due_date: '',
|
||||||
|
status: 'Backlog',
|
||||||
|
priority: 'Low',
|
||||||
|
})
|
||||||
|
|
||||||
|
const detailFields = computed(() => {
|
||||||
|
if (!callLog.value?.data) return []
|
||||||
|
|
||||||
|
let data = JSON.parse(JSON.stringify(callLog.value?.data))
|
||||||
|
|
||||||
|
for (const key in data) {
|
||||||
|
data[key] = getCallLogDetail(key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
note.value = data._notes?.[0] ?? null
|
||||||
|
task.value = data._tasks?.[0] ?? null
|
||||||
|
|
||||||
|
let details = [
|
||||||
|
{
|
||||||
|
icon: h(FeatherIcon, {
|
||||||
|
name: data.type.icon,
|
||||||
|
class: 'h-3.5 w-3.5',
|
||||||
|
}),
|
||||||
|
name: 'type',
|
||||||
|
value: data.type.label + ' Call',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ContactsIcon,
|
||||||
|
name: 'receiver',
|
||||||
|
value: {
|
||||||
|
receiver: data.receiver,
|
||||||
|
caller: data.caller,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: data._lead ? LeadsIcon : Dealsicon,
|
||||||
|
name: 'reference_doc',
|
||||||
|
value: data._lead ? 'Lead' : 'Deal',
|
||||||
|
link: () => {
|
||||||
|
if (data._lead) {
|
||||||
|
router.push({
|
||||||
|
name: 'Lead',
|
||||||
|
params: { leadId: data._lead },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'Deal',
|
||||||
|
params: { dealId: data._deal },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
condition: () => data._lead || data._deal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: CalendarIcon,
|
||||||
|
name: 'creation',
|
||||||
|
value: data.creation.label,
|
||||||
|
tooltip: data.creation.label,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: DurationIcon,
|
||||||
|
name: 'duration',
|
||||||
|
value: data.duration.label,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: CheckCircleIcon,
|
||||||
|
name: 'status',
|
||||||
|
value: data.status.label,
|
||||||
|
color: data.status.color,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: h(FeatherIcon, {
|
||||||
|
name: 'play-circle',
|
||||||
|
class: 'h-4 w-4 mt-2',
|
||||||
|
}),
|
||||||
|
name: 'recording_url',
|
||||||
|
value: data.recording_url,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: NoteIcon,
|
||||||
|
name: 'note',
|
||||||
|
value: data._notes?.[0] ?? null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: TaskIcon,
|
||||||
|
name: 'task',
|
||||||
|
value: data._tasks?.[0] ?? null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return details
|
||||||
|
.filter((detail) => detail.value)
|
||||||
|
.filter((detail) => (detail.condition ? detail.condition() : true))
|
||||||
|
})
|
||||||
|
|
||||||
|
function createLead() {
|
||||||
|
call('crm.fcrm.doctype.crm_call_log.crm_call_log.create_lead_from_call_log', {
|
||||||
|
call_log: callLog.value?.data,
|
||||||
|
}).then((d) => {
|
||||||
|
if (d) {
|
||||||
|
router.push({ name: 'Lead', params: { leadId: d } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCallLogModal = defineModel('callLogModal')
|
||||||
|
|
||||||
|
function openCallLogModal() {
|
||||||
|
showCallLogModal.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
show.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEditNote() {
|
||||||
|
if (!note.value?.name) {
|
||||||
|
note.value = {
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showNoteModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEditTask() {
|
||||||
|
if (!task.value?.name) {
|
||||||
|
task.value = {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
assigned_to: '',
|
||||||
|
due_date: '',
|
||||||
|
status: 'Backlog',
|
||||||
|
priority: 'Low',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showTaskModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addNoteToCallLog(_note, insert_mode = false) {
|
||||||
|
note.value = _note
|
||||||
|
if (insert_mode && _note.name) {
|
||||||
|
await call('crm.integrations.api.add_note_to_call_log', {
|
||||||
|
call_sid: callLog.value?.data?.id,
|
||||||
|
note: _note,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addTaskToCallLog(_task, insert_mode = false) {
|
||||||
|
task.value = _task
|
||||||
|
if (insert_mode && _task.name) {
|
||||||
|
await call('crm.integrations.api.add_task_to_call_log', {
|
||||||
|
call_sid: callLog.value?.data?.id,
|
||||||
|
task: _task,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.audio-control {
|
||||||
|
height: 36px;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgb(237, 237, 237);
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::-webkit-media-controls-panel {
|
||||||
|
background-color: rgb(237, 237, 237) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-control::-webkit-media-controls-panel {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,245 +1,212 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="show">
|
<Dialog v-model="show" :options="dialogOptions">
|
||||||
<template #body-title>
|
<template #body>
|
||||||
<div class="flex items-center gap-3">
|
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
|
||||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
<div class="mb-5 flex items-center justify-between">
|
||||||
{{ __('Call Details') }}
|
<div>
|
||||||
</h3>
|
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||||
|
{{ __(dialogOptions.title) || __('Untitled') }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
v-if="isManager() && !isMobileView"
|
||||||
|
variant="ghost"
|
||||||
|
class="w-7"
|
||||||
|
@click="openQuickEntryModal"
|
||||||
|
>
|
||||||
|
<EditIcon class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" class="w-7" @click="show = false">
|
||||||
|
<FeatherIcon name="x" class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="tabs.data">
|
||||||
|
<FieldLayout
|
||||||
|
:tabs="tabs.data"
|
||||||
|
:data="_callLog"
|
||||||
|
doctype="CRM Call Log"
|
||||||
|
/>
|
||||||
|
<ErrorMessage class="mt-2" :message="error" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="px-4 pb-7 pt-4 sm:px-6">
|
||||||
<template #body-content>
|
<div class="space-y-2">
|
||||||
<div class="flex flex-col gap-3.5">
|
<Button
|
||||||
<div
|
class="w-full"
|
||||||
v-for="field in detailFields"
|
v-for="action in dialogOptions.actions"
|
||||||
:key="field.name"
|
:key="action.label"
|
||||||
class="flex gap-2 text-base text-ink-gray-8"
|
v-bind="action"
|
||||||
>
|
:label="__(action.label)"
|
||||||
<div class="grid size-7 place-content-center">
|
:loading="loading"
|
||||||
<component :is="field.icon" />
|
/>
|
||||||
</div>
|
|
||||||
<div class="flex min-h-7 w-full items-center gap-2">
|
|
||||||
<div
|
|
||||||
v-if="field.name == 'receiver'"
|
|
||||||
class="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
:image="field.value.caller.image"
|
|
||||||
:label="field.value.caller.label"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<div class="ml-1 flex flex-col gap-1">
|
|
||||||
{{ field.value.caller.label }}
|
|
||||||
</div>
|
|
||||||
<FeatherIcon
|
|
||||||
name="arrow-right"
|
|
||||||
class="mx-1 h-4 w-4 text-ink-gray-5"
|
|
||||||
/>
|
|
||||||
<Avatar
|
|
||||||
:image="field.value.receiver.image"
|
|
||||||
:label="field.value.receiver.label"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<div class="ml-1 flex flex-col gap-1">
|
|
||||||
{{ field.value.receiver.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tooltip v-else-if="field.tooltip" :text="field.tooltip">
|
|
||||||
{{ field.value }}
|
|
||||||
</Tooltip>
|
|
||||||
<div class="w-full" v-else-if="field.name == 'recording_url'">
|
|
||||||
<audio
|
|
||||||
class="audio-control w-full"
|
|
||||||
controls
|
|
||||||
:src="field.value"
|
|
||||||
></audio>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="w-full cursor-pointer rounded border px-2 pt-1.5 text-base text-ink-gray-7"
|
|
||||||
v-else-if="field.name == 'note'"
|
|
||||||
@click="() => (showNoteModal = true)"
|
|
||||||
>
|
|
||||||
<FadedScrollableDiv class="max-h-24 min-h-16 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
v-if="field.value?.title"
|
|
||||||
:class="[field.value?.content ? 'mb-1 font-bold' : '']"
|
|
||||||
v-html="field.value?.title"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="field.value?.content"
|
|
||||||
v-html="field.value?.content"
|
|
||||||
/>
|
|
||||||
</FadedScrollableDiv>
|
|
||||||
</div>
|
|
||||||
<div v-else :class="field.color ? `text-${field.color}-600` : ''">
|
|
||||||
{{ field.value }}
|
|
||||||
</div>
|
|
||||||
<div v-if="field.link">
|
|
||||||
<ArrowUpRightIcon
|
|
||||||
class="h-4 w-4 shrink-0 cursor-pointer text-ink-gray-5 hover:text-ink-gray-8"
|
|
||||||
@click="() => field.link()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!callLog.data?._lead && !callLog.data?._deal" #actions>
|
|
||||||
<Button
|
|
||||||
class="w-full"
|
|
||||||
variant="solid"
|
|
||||||
:label="__('Create lead')"
|
|
||||||
@click="createLead"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<NoteModal v-model="showNoteModal" :note="callLog.value?.data?._notes?.[0]" />
|
<QuickEntryModal
|
||||||
|
v-if="showQuickEntryModal"
|
||||||
|
v-model="showQuickEntryModal"
|
||||||
|
doctype="CRM Call Log"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
|
||||||
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
import FieldLayout from '@/components/FieldLayout/FieldLayout.vue'
|
||||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
import { usersStore } from '@/stores/users'
|
||||||
import Dealsicon from '@/components/Icons/DealsIcon.vue'
|
import { isMobileView } from '@/composables/settings'
|
||||||
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
import { getRandom } from '@/utils'
|
||||||
import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
import { capture } from '@/telemetry'
|
||||||
import CheckCircleIcon from '@/components/Icons/CheckCircleIcon.vue'
|
import { FeatherIcon, createResource, ErrorMessage } from 'frappe-ui'
|
||||||
import NoteModal from '@/components/Modals/NoteModal.vue'
|
import { ref, nextTick, watch, computed } from 'vue'
|
||||||
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
|
|
||||||
import { FeatherIcon, Avatar, Tooltip, call, createResource } from 'frappe-ui'
|
|
||||||
import { getCallLogDetail } from '@/utils/callLog'
|
|
||||||
import { ref, computed, h, watch } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: {
|
options: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: {},
|
default: {
|
||||||
|
afterInsert: () => {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const show = defineModel()
|
const { isManager } = usersStore()
|
||||||
const showNoteModal = ref(false)
|
|
||||||
const router = useRouter()
|
|
||||||
const callLog = ref({})
|
|
||||||
|
|
||||||
const detailFields = computed(() => {
|
const show = defineModel()
|
||||||
if (!callLog.value.data) return []
|
const callLog = defineModel('callLog')
|
||||||
let details = [
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref(null)
|
||||||
|
const title = ref(null)
|
||||||
|
const editMode = ref(false)
|
||||||
|
|
||||||
|
let _callLog = ref({
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
from: '',
|
||||||
|
to: '',
|
||||||
|
medium: '',
|
||||||
|
duration: '',
|
||||||
|
caller: '',
|
||||||
|
receiver: '',
|
||||||
|
status: '',
|
||||||
|
recording_url: '',
|
||||||
|
telephony_medium: 'Manual',
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogOptions = computed(() => {
|
||||||
|
let title = !editMode.value ? __('New Call Log') : __('Edit Call Log')
|
||||||
|
let size = 'xl'
|
||||||
|
let actions = [
|
||||||
{
|
{
|
||||||
icon: h(FeatherIcon, {
|
label: editMode.value ? __('Save') : __('Create'),
|
||||||
name: callLog.value.data.type.icon,
|
variant: 'solid',
|
||||||
class: 'h-3.5 w-3.5',
|
onClick: () =>
|
||||||
}),
|
editMode.value ? updateCallLog() : createCallLog.submit(),
|
||||||
name: 'type',
|
|
||||||
value: callLog.value.data.type.label + ' Call',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: ContactsIcon,
|
|
||||||
name: 'receiver',
|
|
||||||
value: {
|
|
||||||
receiver: callLog.value.data.receiver,
|
|
||||||
caller: callLog.value.data.caller,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: callLog.value.data._lead ? LeadsIcon : Dealsicon,
|
|
||||||
name: 'reference_doc',
|
|
||||||
value: callLog.value.data._lead ? 'Lead' : 'Deal',
|
|
||||||
link: () => {
|
|
||||||
if (callLog.value.data._lead) {
|
|
||||||
router.push({
|
|
||||||
name: 'Lead',
|
|
||||||
params: { leadId: callLog.value.data._lead },
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
name: 'Deal',
|
|
||||||
params: { dealId: callLog.value.data._deal },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
condition: () => callLog.value.data._lead || callLog.value.data._deal,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: CalendarIcon,
|
|
||||||
name: 'creation',
|
|
||||||
value: callLog.value.data.creation.label,
|
|
||||||
tooltip: callLog.value.data.creation.label,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: DurationIcon,
|
|
||||||
name: 'duration',
|
|
||||||
value: callLog.value.data.duration.label,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: CheckCircleIcon,
|
|
||||||
name: 'status',
|
|
||||||
value: callLog.value.data.status.label,
|
|
||||||
color: callLog.value.data.status.color,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: h(FeatherIcon, {
|
|
||||||
name: 'play-circle',
|
|
||||||
class: 'h-4 w-4 mt-2',
|
|
||||||
}),
|
|
||||||
name: 'recording_url',
|
|
||||||
value: callLog.value.data.recording_url,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: NoteIcon,
|
|
||||||
name: 'note',
|
|
||||||
value: callLog.value.data._notes?.[0] ?? null,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return details
|
return { title, size, actions }
|
||||||
.filter((detail) => detail.value)
|
|
||||||
.filter((detail) => (detail.condition ? detail.condition() : true))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function createLead() {
|
const tabs = createResource({
|
||||||
call('crm.fcrm.doctype.crm_call_log.crm_call_log.create_lead_from_call_log', {
|
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout',
|
||||||
call_log: callLog.value.data,
|
cache: ['QuickEntry', 'CRM Call Log'],
|
||||||
}).then((d) => {
|
params: { doctype: 'CRM Call Log', type: 'Quick Entry' },
|
||||||
if (d) {
|
auto: true,
|
||||||
router.push({ name: 'Lead', params: { leadId: d } })
|
})
|
||||||
}
|
|
||||||
|
let doc = ref({})
|
||||||
|
|
||||||
|
function updateCallLog() {
|
||||||
|
error.value = null
|
||||||
|
const old = { ...doc.value }
|
||||||
|
const newCallLog = { ..._callLog.value }
|
||||||
|
|
||||||
|
const dirty = JSON.stringify(old) !== JSON.stringify(newCallLog)
|
||||||
|
|
||||||
|
if (!dirty) {
|
||||||
|
show.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
updateCallLogValues.submit({
|
||||||
|
doctype: 'CRM Call Log',
|
||||||
|
name: _callLog.value.name,
|
||||||
|
fieldname: newCallLog,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(show, (val) => {
|
const updateCallLogValues = createResource({
|
||||||
if (val) {
|
url: 'frappe.client.set_value',
|
||||||
callLog.value = createResource({
|
onSuccess(doc) {
|
||||||
url: 'crm.fcrm.doctype.crm_call_log.crm_call_log.get_call_log',
|
loading.value = false
|
||||||
params: { name: props.name },
|
if (doc.name) {
|
||||||
cache: ['call_log', props.name],
|
handleCallLogUpdate(doc)
|
||||||
auto: true,
|
}
|
||||||
transform: (doc) => {
|
},
|
||||||
for (const key in doc) {
|
onError(err) {
|
||||||
doc[key] = getCallLogDetail(key, doc)
|
loading.value = false
|
||||||
}
|
error.value = err
|
||||||
return doc
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const createCallLog = createResource({
|
||||||
|
url: 'frappe.client.insert',
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
doc: {
|
||||||
|
doctype: 'CRM Call Log',
|
||||||
|
id: getRandom(6),
|
||||||
|
telephony_medium: 'Manual',
|
||||||
|
..._callLog.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess(doc) {
|
||||||
|
loading.value = false
|
||||||
|
if (doc.name) {
|
||||||
|
capture('call_log_created')
|
||||||
|
handleCallLogUpdate(doc)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
loading.value = false
|
||||||
|
error.value = err
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleCallLogUpdate(doc) {
|
||||||
|
show.value = false
|
||||||
|
props.options.afterInsert && props.options.afterInsert(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => show.value,
|
||||||
|
(value) => {
|
||||||
|
if (!value) return
|
||||||
|
editMode.value = false
|
||||||
|
nextTick(() => {
|
||||||
|
// TODO: Issue with FormControl
|
||||||
|
// title.value.el.focus()
|
||||||
|
doc.value = callLog.value?.data || {}
|
||||||
|
_callLog.value = { ...doc.value }
|
||||||
|
if (_callLog.value.name) {
|
||||||
|
editMode.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const showQuickEntryModal = ref(false)
|
||||||
|
|
||||||
|
function openQuickEntryModal() {
|
||||||
|
showQuickEntryModal.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
show.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.audio-control {
|
|
||||||
height: 36px;
|
|
||||||
outline: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: rgb(237, 237, 237);
|
|
||||||
}
|
|
||||||
|
|
||||||
audio::-webkit-media-controls-panel {
|
|
||||||
background-color: rgb(237, 237, 237) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audio-control::-webkit-media-controls-panel {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -187,7 +187,8 @@ async function updateTask() {
|
|||||||
fieldname: _task.value,
|
fieldname: _task.value,
|
||||||
})
|
})
|
||||||
if (d.name) {
|
if (d.name) {
|
||||||
tasks.value.reload()
|
tasks.value?.reload()
|
||||||
|
emit('after', d)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let d = await call('frappe.client.insert', {
|
let d = await call('frappe.client.insert', {
|
||||||
@ -200,8 +201,8 @@ async function updateTask() {
|
|||||||
})
|
})
|
||||||
if (d.name) {
|
if (d.name) {
|
||||||
capture('task_created')
|
capture('task_created')
|
||||||
tasks.value.reload()
|
tasks.value?.reload()
|
||||||
emit('after')
|
emit('after', d, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
show.value = false
|
show.value = false
|
||||||
|
|||||||
@ -101,7 +101,10 @@
|
|||||||
:label="data[field.fieldname]"
|
:label="data[field.fieldname]"
|
||||||
class="dropdown-button flex w-full items-center justify-between rounded border border-gray-100 bg-surface-gray-2 px-2 py-1.5 text-base text-ink-gray-8 placeholder-ink-gray-4 transition-colors hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:border-outline-gray-4 focus:bg-surface-white focus:shadow-sm focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3"
|
class="dropdown-button flex w-full items-center justify-between rounded border border-gray-100 bg-surface-gray-2 px-2 py-1.5 text-base text-ink-gray-8 placeholder-ink-gray-4 transition-colors hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:border-outline-gray-4 focus:bg-surface-white focus:shadow-sm focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3"
|
||||||
>
|
>
|
||||||
<div v-if="data[field.fieldname]" class="truncate">
|
<div
|
||||||
|
v-if="data[field.fieldname]"
|
||||||
|
class="truncate"
|
||||||
|
>
|
||||||
{{ data[field.fieldname] }}
|
{{ data[field.fieldname] }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -8,6 +8,9 @@
|
|||||||
v-if="callLogsListView?.customListActions"
|
v-if="callLogsListView?.customListActions"
|
||||||
:actions="callLogsListView.customListActions"
|
:actions="callLogsListView.customListActions"
|
||||||
/>
|
/>
|
||||||
|
<Button variant="solid" :label="__('Create')" @click="createCallLog">
|
||||||
|
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||||
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<ViewControls
|
<ViewControls
|
||||||
@ -50,7 +53,16 @@
|
|||||||
<span>{{ __('No {0} Found', [__('Logs')]) }}</span>
|
<span>{{ __('No {0} Found', [__('Logs')]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CallLogModal v-model="showCallLogModal" :name="selectedCallLog" />
|
<CallLogDetailModal
|
||||||
|
v-model="showCallLogDetailModal"
|
||||||
|
v-model:callLogModal="showCallLogModal"
|
||||||
|
v-model:callLog="callLog"
|
||||||
|
/>
|
||||||
|
<CallLogModal
|
||||||
|
v-model="showCallLogModal"
|
||||||
|
v-model:callLog="callLog"
|
||||||
|
:options="{ afterInsert: () => callLogs.reload() }"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -60,11 +72,14 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
|||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import ViewControls from '@/components/ViewControls.vue'
|
import ViewControls from '@/components/ViewControls.vue'
|
||||||
import CallLogsListView from '@/components/ListViews/CallLogsListView.vue'
|
import CallLogsListView from '@/components/ListViews/CallLogsListView.vue'
|
||||||
|
import CallLogDetailModal from '@/components/Modals/CallLogDetailModal.vue'
|
||||||
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
import CallLogModal from '@/components/Modals/CallLogModal.vue'
|
||||||
import { getCallLogDetail } from '@/utils/callLog'
|
import { getCallLogDetail } from '@/utils/callLog'
|
||||||
|
import { createResource } from 'frappe-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const callLogsListView = ref(null)
|
const callLogsListView = ref(null)
|
||||||
|
const showCallLogModal = ref(false)
|
||||||
|
|
||||||
// callLogs data is loaded in the ViewControls component
|
// callLogs data is loaded in the ViewControls component
|
||||||
const callLogs = ref({})
|
const callLogs = ref({})
|
||||||
@ -88,11 +103,21 @@ const rows = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const showCallLogModal = ref(false)
|
const showCallLogDetailModal = ref(false)
|
||||||
const selectedCallLog = ref(null)
|
const callLog = ref({})
|
||||||
|
|
||||||
function showCallLog(name) {
|
function showCallLog(name) {
|
||||||
selectedCallLog.value = name
|
showCallLogDetailModal.value = true
|
||||||
|
callLog.value = createResource({
|
||||||
|
url: 'crm.fcrm.doctype.crm_call_log.crm_call_log.get_call_log',
|
||||||
|
params: { name },
|
||||||
|
cache: ['call_log', name],
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCallLog() {
|
||||||
|
callLog.value = {}
|
||||||
showCallLogModal.value = true
|
showCallLogModal.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -227,9 +227,7 @@ const _address = ref({})
|
|||||||
const contact = createResource({
|
const contact = createResource({
|
||||||
url: 'crm.api.contact.get_contact',
|
url: 'crm.api.contact.get_contact',
|
||||||
cache: ['contact', props.contactId],
|
cache: ['contact', props.contactId],
|
||||||
params: {
|
params: { name: props.contactId },
|
||||||
name: props.contactId,
|
|
||||||
},
|
|
||||||
auto: true,
|
auto: true,
|
||||||
transform: (data) => {
|
transform: (data) => {
|
||||||
return {
|
return {
|
||||||
@ -340,7 +338,7 @@ const sections = createResource({
|
|||||||
cache: ['sidePanelSections', 'Contact'],
|
cache: ['sidePanelSections', 'Contact'],
|
||||||
params: { doctype: 'Contact' },
|
params: { doctype: 'Contact' },
|
||||||
auto: true,
|
auto: true,
|
||||||
transform: (data) => getParsedSections(data),
|
transform: (data) => computed(() => getParsedSections(data)),
|
||||||
})
|
})
|
||||||
|
|
||||||
function getParsedSections(_sections) {
|
function getParsedSections(_sections) {
|
||||||
|
|||||||
@ -320,9 +320,7 @@ const tabs = [
|
|||||||
const deals = createResource({
|
const deals = createResource({
|
||||||
url: 'crm.api.contact.get_linked_deals',
|
url: 'crm.api.contact.get_linked_deals',
|
||||||
cache: ['deals', props.contactId],
|
cache: ['deals', props.contactId],
|
||||||
params: {
|
params: { contact: props.contactId },
|
||||||
contact: props.contactId,
|
|
||||||
},
|
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -337,7 +335,7 @@ const sections = createResource({
|
|||||||
cache: ['sidePanelSections', 'Contact'],
|
cache: ['sidePanelSections', 'Contact'],
|
||||||
params: { doctype: 'Contact' },
|
params: { doctype: 'Contact' },
|
||||||
auto: true,
|
auto: true,
|
||||||
transform: (data) => getParsedSections(data),
|
transform: (data) => computed(() => getParsedSections(data)),
|
||||||
})
|
})
|
||||||
|
|
||||||
function getParsedSections(_sections) {
|
function getParsedSections(_sections) {
|
||||||
|
|||||||
@ -9,9 +9,19 @@ export function getCallLogDetail(row, log, columns = []) {
|
|||||||
|
|
||||||
if (row === 'duration') {
|
if (row === 'duration') {
|
||||||
return {
|
return {
|
||||||
label: log.duration,
|
label: log._duration,
|
||||||
icon: 'clock',
|
icon: 'clock',
|
||||||
}
|
}
|
||||||
|
} else if (row === 'caller') {
|
||||||
|
return {
|
||||||
|
label: log._caller?.label,
|
||||||
|
image: log._caller?.image,
|
||||||
|
}
|
||||||
|
} else if (row === 'receiver') {
|
||||||
|
return {
|
||||||
|
label: log._receiver?.label,
|
||||||
|
image: log._receiver?.image,
|
||||||
|
}
|
||||||
} else if (row === 'type') {
|
} else if (row === 'type') {
|
||||||
return {
|
return {
|
||||||
label: log.type,
|
label: log.type,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user