Merge branch 'develop' into pot_develop_2025-03-30

This commit is contained in:
Shariq Ansari 2025-04-07 16:51:07 +05:30 committed by GitHub
commit 899b09ac40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 344 additions and 258 deletions

View File

@ -418,16 +418,23 @@ def get_data(
rows.append(field)
for kc in kanban_columns:
column_filters = {column_field: kc.get("name")}
# Start with base filters
column_filters = []
# Convert and add the main filters first
if filters:
base_filters = convert_filter_to_tuple(doctype, filters)
column_filters.extend(base_filters)
# Add the column-specific filter
if column_field and kc.get("name"):
column_filters.append([doctype, column_field, "=", kc.get("name")])
order = kc.get("order")
if (column_field in filters and filters.get(column_field) != kc.get("name")) or kc.get("delete"):
if kc.get("delete"):
column_data = []
else:
column_filters.update(filters.copy())
page_length = 20
if kc.get("page_length"):
page_length = kc.get("page_length")
page_length = kc.get("page_length", 20)
if order:
column_data = get_records_based_on_order(
@ -437,26 +444,20 @@ def get_data(
column_data = frappe.get_list(
doctype,
fields=rows,
filters=convert_filter_to_tuple(doctype, column_filters),
filters=column_filters,
order_by=order_by,
page_length=page_length,
)
new_filters = filters.copy()
new_filters.update({column_field: kc.get("name")})
all_count = frappe.get_list(
doctype,
filters=convert_filter_to_tuple(doctype, new_filters),
filters=column_filters,
fields="count(*) as total_count",
)[0].total_count
kc["all_count"] = all_count
kc["count"] = len(column_data)
for d in column_data:
getCounts(d, doctype)
if order:
column_data = sorted(
column_data,

View File

@ -41,13 +41,15 @@
"fieldname": "from",
"fieldtype": "Data",
"in_list_view": 1,
"label": "From"
"label": "From",
"reqd": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"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",
"reqd": 1
},
{
"fieldname": "start_time",
@ -69,13 +71,15 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Type",
"options": "Incoming\nOutgoing"
"options": "Incoming\nOutgoing",
"reqd": 1
},
{
"fieldname": "to",
"fieldtype": "Data",
"in_list_view": 1,
"label": "To"
"label": "To",
"reqd": 1
},
{
"description": "Call duration in seconds",
@ -153,7 +157,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-01-22 17:57:59.289548",
"modified": "2025-04-01 16:01:54.479309",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Call Log",

View File

@ -19,7 +19,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Title"
"label": "Title",
"reqd": 1
},
{
"fieldname": "content",
@ -49,7 +50,7 @@
"link_fieldname": "note"
}
],
"modified": "2024-01-19 21:56:30.123334",
"modified": "2025-04-01 15:30:14.742001",
"modified_by": "Administrator",
"module": "FCRM",
"name": "FCRM Note",

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Frappe CRM VERSION\n"
"Report-Msgid-Bugs-To: shariq@frappe.io\n"
"POT-Creation-Date: 2025-03-30 09:35+0000\n"
"PO-Revision-Date: 2025-03-30 09:35+0000\n"
"POT-Creation-Date: 2025-04-06 09:35+0000\n"
"PO-Revision-Date: 2025-04-06 09:35+0000\n"
"Last-Translator: shariq@frappe.io\n"
"Language-Team: shariq@frappe.io\n"
"MIME-Version: 1.0\n"
@ -20,7 +20,7 @@ msgstr ""
msgid " (New)"
msgstr ""
#: frontend/src/components/Modals/TaskModal.vue:95
#: frontend/src/components/Modals/TaskModal.vue:66
#: frontend/src/components/Telephony/TaskPanel.vue:67
msgid "01/04/2024 11:30 PM"
msgstr ""
@ -631,8 +631,8 @@ msgstr ""
msgid "Call using {0}"
msgstr ""
#: frontend/src/components/Modals/NoteModal.vue:43
#: frontend/src/components/Modals/TaskModal.vue:43
#: frontend/src/components/Modals/NoteModal.vue:30
#: frontend/src/components/Modals/TaskModal.vue:30
msgid "Call with John Doe"
msgstr ""
@ -886,7 +886,7 @@ msgstr ""
#: crm/fcrm/doctype/fcrm_note/fcrm_note.json
#: frontend/src/components/Modals/EmailTemplateModal.vue:61
#: frontend/src/components/Modals/EmailTemplateModal.vue:74
#: frontend/src/components/Modals/NoteModal.vue:47
#: frontend/src/components/Modals/NoteModal.vue:34
msgid "Content"
msgstr ""
@ -923,14 +923,14 @@ msgid "Converted successfully"
msgstr ""
#: frontend/src/components/Modals/AddressModal.vue:100
#: frontend/src/components/Modals/CallLogModal.vue:104
#: frontend/src/components/Modals/CallLogModal.vue:85
#: frontend/src/components/Modals/ContactModal.vue:37
#: frontend/src/components/Modals/DealModal.vue:63
#: frontend/src/components/Modals/EmailTemplateModal.vue:9
#: frontend/src/components/Modals/LeadModal.vue:34
#: frontend/src/components/Modals/NoteModal.vue:8
#: frontend/src/components/Modals/OrganizationModal.vue:37
#: frontend/src/components/Modals/TaskModal.vue:8
#: frontend/src/components/Modals/NoteModal.vue:6
#: frontend/src/components/Modals/OrganizationModal.vue:25
#: frontend/src/components/Modals/TaskModal.vue:6
#: frontend/src/components/Modals/ViewModal.vue:16
#: frontend/src/pages/CallLogs.vue:11 frontend/src/pages/Contacts.vue:13
#: frontend/src/pages/Contacts.vue:57 frontend/src/pages/Deals.vue:13
@ -963,12 +963,12 @@ msgid "Create New"
msgstr ""
#: frontend/src/components/Activities/Activities.vue:383
#: frontend/src/components/Modals/NoteModal.vue:18
#: frontend/src/components/Modals/NoteModal.vue:15
msgid "Create Note"
msgstr ""
#: frontend/src/components/Activities/Activities.vue:398
#: frontend/src/components/Modals/TaskModal.vue:18
#: frontend/src/components/Modals/TaskModal.vue:15
msgid "Create Task"
msgstr ""
@ -1197,7 +1197,7 @@ msgstr ""
#. Label of the description (Text Editor) field in DocType 'CRM Task'
#: crm/fcrm/doctype/crm_holiday/crm_holiday.json
#: crm/fcrm/doctype/crm_task/crm_task.json
#: frontend/src/components/Modals/TaskModal.vue:48
#: frontend/src/components/Modals/TaskModal.vue:35
msgid "Description"
msgstr ""
@ -1343,7 +1343,7 @@ msgstr ""
msgid "Edit"
msgstr ""
#: frontend/src/components/Modals/CallLogModal.vue:100
#: frontend/src/components/Modals/CallLogModal.vue:81
msgid "Edit Call Log"
msgstr ""
@ -1363,7 +1363,7 @@ msgstr ""
msgid "Edit Grid Row Fields Layout"
msgstr ""
#: frontend/src/components/Modals/NoteModal.vue:18
#: frontend/src/components/Modals/NoteModal.vue:15
msgid "Edit Note"
msgstr ""
@ -1371,7 +1371,7 @@ msgstr ""
msgid "Edit Quick Entry Layout"
msgstr ""
#: frontend/src/components/Modals/TaskModal.vue:18
#: frontend/src/components/Modals/TaskModal.vue:15
msgid "Edit Task"
msgstr ""
@ -2059,7 +2059,7 @@ msgstr ""
#: frontend/src/components/Filter.vue:75 frontend/src/components/Filter.vue:108
#: frontend/src/components/Modals/AssignmentModal.vue:35
#: frontend/src/components/Modals/TaskModal.vue:75
#: frontend/src/components/Modals/TaskModal.vue:51
#: frontend/src/components/Telephony/TaskPanel.vue:47
msgid "John Doe"
msgstr ""
@ -2446,7 +2446,7 @@ msgstr ""
msgid "New Address"
msgstr ""
#: frontend/src/components/Modals/CallLogModal.vue:100
#: frontend/src/components/Modals/CallLogModal.vue:81
msgid "New Call Log"
msgstr ""
@ -2735,13 +2735,13 @@ msgstr ""
msgid "Only one {0} can be set as primary."
msgstr ""
#: frontend/src/components/Modals/NoteModal.vue:25
#: frontend/src/components/Modals/TaskModal.vue:25
#: frontend/src/components/Modals/NoteModal.vue:18
#: frontend/src/components/Modals/TaskModal.vue:18
msgid "Open Deal"
msgstr ""
#: frontend/src/components/Modals/NoteModal.vue:26
#: frontend/src/components/Modals/TaskModal.vue:26
#: frontend/src/components/Modals/NoteModal.vue:19
#: frontend/src/components/Modals/TaskModal.vue:19
msgid "Open Lead"
msgstr ""
@ -3331,7 +3331,7 @@ msgstr ""
#: frontend/src/components/Controls/GridRowFieldsModal.vue:26
#: frontend/src/components/DropdownItem.vue:21
#: frontend/src/components/Modals/AddressModal.vue:100
#: frontend/src/components/Modals/CallLogModal.vue:104
#: frontend/src/components/Modals/CallLogModal.vue:85
#: frontend/src/components/Modals/DataFieldsModal.vue:26
#: frontend/src/components/Modals/QuickEntryModal.vue:26
#: frontend/src/components/Modals/SidePanelModal.vue:26
@ -3709,8 +3709,8 @@ msgstr ""
#. Label of the title (Data) field in DocType 'FCRM Note'
#: crm/fcrm/doctype/crm_task/crm_task.json
#: crm/fcrm/doctype/fcrm_note/fcrm_note.json
#: frontend/src/components/Modals/NoteModal.vue:41
#: frontend/src/components/Modals/TaskModal.vue:41
#: frontend/src/components/Modals/NoteModal.vue:30
#: frontend/src/components/Modals/TaskModal.vue:30
msgid "Title"
msgstr ""
@ -3757,8 +3757,8 @@ msgstr ""
msgid "Tomorrow"
msgstr ""
#: frontend/src/components/Modals/NoteModal.vue:56
#: frontend/src/components/Modals/TaskModal.vue:58
#: frontend/src/components/Modals/NoteModal.vue:37
#: frontend/src/components/Modals/TaskModal.vue:39
msgid "Took a call with John Doe and discussed the new project."
msgstr ""
@ -3860,8 +3860,8 @@ msgstr ""
#: frontend/src/components/ColumnSettings.vue:134
#: frontend/src/components/Modals/AssignmentModal.vue:17
#: frontend/src/components/Modals/EmailTemplateModal.vue:9
#: frontend/src/components/Modals/NoteModal.vue:8
#: frontend/src/components/Modals/TaskModal.vue:8
#: frontend/src/components/Modals/NoteModal.vue:6
#: frontend/src/components/Modals/TaskModal.vue:6
#: frontend/src/components/Settings/GeneralSettings.vue:112
#: frontend/src/components/Settings/ProfileSettings.vue:71
#: frontend/src/components/Settings/SettingsPage.vue:31
@ -4144,7 +4144,7 @@ msgstr ""
msgid "kanban"
msgstr ""
#: crm/api/doc.py:38 crm/api/doc.py:156 crm/api/doc.py:500
#: crm/api/doc.py:38 crm/api/doc.py:156 crm/api/doc.py:501
msgid "label"
msgstr ""

@ -1 +1 @@
Subproject commit 3423aa5b5c38d3a1b143ae8ab08cbde7360f9a7c
Subproject commit 29307e4fffaacdbb3d9c5d95c5270b2f245a5607

View File

@ -156,6 +156,7 @@ declare module 'vue' {
MobileLayout: typeof import('./src/components/Layouts/MobileLayout.vue')['default']
MobileSidebar: typeof import('./src/components/Mobile/MobileSidebar.vue')['default']
MoneyIcon: typeof import('./src/components/Icons/MoneyIcon.vue')['default']
MultiActionButton: typeof import('./src/components/MultiActionButton.vue')['default']
MultipleAvatar: typeof import('./src/components/MultipleAvatar.vue')['default']
MultiSelectEmailInput: typeof import('./src/components/Controls/MultiSelectEmailInput.vue')['default']
MuteIcon: typeof import('./src/components/Icons/MuteIcon.vue')['default']

View File

@ -11,7 +11,7 @@
"dependencies": {
"@twilio/voice-sdk": "^2.10.2",
"@vueuse/integrations": "^10.3.0",
"frappe-ui": "^0.1.121",
"frappe-ui": "^0.1.123",
"gemoji": "^8.1.0",
"lodash": "^4.17.21",
"mime": "^4.0.1",

View File

@ -373,11 +373,7 @@
>
<component :is="emptyTextIcon" class="h-10 w-10" />
<span>{{ __(emptyText) }}</span>
<Button
v-if="title == 'Calls'"
:label="__('Make a Call')"
@click="makeCall(doc.data.mobile_no)"
/>
<MultiActionButton v-if="title == 'Calls'" :options="callActions" />
<Button
v-else-if="title == 'Notes'"
:label="__('Create Note')"
@ -470,6 +466,7 @@ import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import WhatsAppArea from '@/components/Activities/WhatsAppArea.vue'
import WhatsAppBox from '@/components/Activities/WhatsAppBox.vue'
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
import MultiActionButton from '@/components/MultiActionButton.vue'
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue'
import DotIcon from '@/components/Icons/DotIcon.vue'
@ -487,7 +484,7 @@ import FilesUploader from '@/components/FilesUploader/FilesUploader.vue'
import { timeAgo, formatDate, startCase } from '@/utils'
import { globalStore } from '@/stores/global'
import { usersStore } from '@/stores/users'
import { whatsappEnabled } from '@/composables/settings'
import { whatsappEnabled, callEnabled } from '@/composables/settings'
import { capture } from '@/telemetry'
import { Button, Tooltip, createResource } from 'frappe-ui'
import { useElementVisibility } from '@vueuse/core'
@ -785,5 +782,23 @@ function scroll(hash) {
}, 500)
}
const callActions = computed(() => {
let actions = [
{
label: __('Create Call Log'),
onClick: () => modalRef.value.createCallLog(),
},
{
label: __('Make a Call'),
onClick: () => makeCall(doc.data.mobile_no),
condition: () => callEnabled.value,
},
]
return actions.filter((action) =>
action.condition ? action.condition() : true,
)
})
defineExpose({ emailBox, all_activities })
</script>

View File

@ -26,16 +26,11 @@
</template>
<span>{{ __('New Comment') }}</span>
</Button>
<Button
<MultiActionButton
v-else-if="title == 'Calls'"
variant="solid"
@click="makeCall(doc.data.mobile_no)"
>
<template #prefix>
<PhoneIcon class="h-4 w-4" />
</template>
<span>{{ __('Make a Call') }}</span>
</Button>
:options="callActions"
/>
<Button
v-else-if="title == 'Notes'"
variant="solid"
@ -97,6 +92,7 @@
</div>
</template>
<script setup>
import MultiActionButton from '@/components/MultiActionButton.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue'
import CommentIcon from '@/components/Icons/CommentIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
@ -136,6 +132,11 @@ const defaultActions = computed(() => {
label: __('New Comment'),
onClick: () => (props.emailBox.showComment = true),
},
{
icon: h(PhoneIcon, { class: 'h-4 w-4' }),
label: __('Create Call Log'),
onClick: () => props.modalRef.createCallLog(),
},
{
icon: h(PhoneIcon, { class: 'h-4 w-4' }),
label: __('Make a Call'),
@ -172,4 +173,24 @@ const defaultActions = computed(() => {
function getTabIndex(name) {
return props.tabs.findIndex((tab) => tab.name === name)
}
const callActions = computed(() => {
let actions = [
{
label: __('Create Call Log'),
icon: 'plus',
onClick: () => props.modalRef.createCallLog(),
},
{
label: __('Make a Call'),
icon: h(PhoneIcon, { class: 'h-4 w-4' }),
onClick: () => makeCall(doc.data.mobile_no),
condition: () => callEnabled.value,
},
]
return actions.filter((action) =>
action.condition ? action.condition() : true,
)
})
</script>

View File

@ -15,10 +15,16 @@
:doc="doc.data?.name"
@after="redirect('notes')"
/>
<CallLogModal
v-model="showCallLogModal"
v-model:callLog="callLog"
:options="{ afterInsert: () => activities.reload() }"
/>
</template>
<script setup>
import TaskModal from '@/components/Modals/TaskModal.vue'
import NoteModal from '@/components/Modals/NoteModal.vue'
import CallLogModal from '@/components/Modals/CallLogModal.vue'
import { call } from 'frappe-ui'
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@ -77,6 +83,22 @@ function showNote(n) {
showNoteModal.value = true
}
// Call Logs
const showCallLogModal = ref(false)
const callLog = ref({})
function createCallLog() {
let doctype = props.doctype
let docname = props.doc.data?.name
callLog.value = {
data: {
reference_doctype: doctype,
reference_docname: docname,
},
}
showCallLogModal.value = true
}
// common
const route = useRoute()
const router = useRouter()
@ -95,5 +117,6 @@ defineExpose({
deleteTask,
updateTaskStatus,
showNote,
createCallLog,
})
</script>

View File

@ -100,10 +100,16 @@
:disabled="true"
/>
<Link
v-else-if="field.fieldtype === 'Link'"
v-else-if="
['Link', 'Dynamic Link'].includes(field.fieldtype)
"
class="text-sm text-ink-gray-8"
v-model="row[field.fieldname]"
:doctype="field.options"
:doctype="
field.fieldtype == 'Link'
? field.options
: row[field.options]
"
:filters="field.filters"
/>
<Link

View File

@ -159,6 +159,7 @@ const options = createResource({
})
function reload(val) {
if (!props.doctype) return
if (
options.data?.length &&
val === options.params?.txt &&

View File

@ -59,11 +59,16 @@
<span class="text-ink-red-3" v-if="field.mandatory">*</span>
</label>
</div>
<div class="flex gap-1" v-else-if="field.fieldtype === 'Link'">
<div
class="flex gap-1"
v-else-if="['Link', 'Dynamic Link'].includes(field.fieldtype)"
>
<Link
class="form-control flex-1 truncate"
:value="data[field.fieldname]"
:doctype="field.options"
:doctype="
field.fieldtype == 'Link' ? field.options : data[field.options]
"
:filters="field.filters"
@change="(v) => (data[field.fieldname] = v)"
:placeholder="getPlaceholder(field)"

View File

@ -1,55 +1,36 @@
<template>
<Dialog v-model="show" :options="dialogOptions">
<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 class="px-4 pt-5 pb-6 bg-surface-modal sm:px-6">
<div class="flex items-center justify-between mb-5">
<div>
<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 v-if="isManager() && !isMobileView" variant="ghost" class="w-7" @click="openQuickEntryModal">
<EditIcon class="w-4 h-4" />
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<FeatherIcon name="x" class="h-4 w-4" />
<FeatherIcon name="x" class="w-4 h-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" />
<FieldLayout :tabs="tabs.data" :data="_callLog" doctype="CRM Call Log" />
<ErrorMessage class="mt-8" :message="error" />
</div>
</div>
<div class="px-4 pb-7 pt-4 sm:px-6">
<div class="px-4 pt-4 pb-7 sm:px-6">
<div class="space-y-2">
<Button
class="w-full"
v-for="action in dialogOptions.actions"
:key="action.label"
v-bind="action"
:label="__(action.label)"
:loading="loading"
/>
<Button class="w-full" v-for="action in dialogOptions.actions" :key="action.label" v-bind="action"
:label="__(action.label)" :loading="loading" />
</div>
</div>
</template>
</Dialog>
<QuickEntryModal
v-if="showQuickEntryModal"
v-model="showQuickEntryModal"
doctype="CRM Call Log"
/>
<QuickEntryModal v-if="showQuickEntryModal" v-model="showQuickEntryModal" doctype="CRM Call Log" />
</template>
<script setup>
@ -67,7 +48,7 @@ const props = defineProps({
options: {
type: Object,
default: {
afterInsert: () => {},
afterInsert: () => { },
},
},
})
@ -175,6 +156,13 @@ const createCallLog = createResource({
},
onError(err) {
loading.value = false
if (err.exc_type == 'MandatoryError') {
const errorMessage = err.messages
.map(msg => msg.split('Log:')[1].trim())
.join(', ')
error.value = `These fields are required: ${errorMessage}`
return
}
error.value = err
},
})

View File

@ -1,34 +1,25 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
actions: [
{
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateNote(),
},
],
}"
>
<Dialog v-model="show" :options="{
size: 'xl',
actions: [
{
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateNote(),
},
],
}">
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ editMode ? __('Edit Note') : __('Create Note') }}
</h3>
<Button
v-if="_note?.reference_docname"
size="sm"
:label="
_note.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
"
@click="redirect()"
>
<Button v-if="_note?.reference_docname" size="sm" :label="_note.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
" @click="redirect()">
<template #suffix>
<ArrowUpRightIcon class="h-4 w-4" />
<ArrowUpRightIcon class="w-4 h-4" />
</template>
</Button>
</div>
@ -36,27 +27,17 @@
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<FormControl
ref="title"
:label="__('Title')"
v-model="_note.title"
:placeholder="__('Call with John Doe')"
/>
<FormControl ref="title" :label="__('Title')" v-model="_note.title" :placeholder="__('Call with John Doe')"
required />
</div>
<div>
<div class="mb-1.5 text-xs text-ink-gray-5">{{ __('Content') }}</div>
<TextEditor
variant="outline"
ref="content"
<TextEditor variant="outline" ref="content"
editor-class="!prose-sm overflow-auto min-h-[180px] max-h-80 py-1.5 px-2 rounded border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors"
:bubbleMenu="true"
:content="_note.content"
@change="(val) => (_note.content = val)"
:placeholder="
__('Took a call with John Doe and discussed the new project.')
"
/>
:bubbleMenu="true" :content="_note.content" @change="(val) => (_note.content = val)" :placeholder="__('Took a call with John Doe and discussed the new project.')
" />
</div>
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</div>
</template>
</Dialog>
@ -94,17 +75,12 @@ const router = useRouter()
const { updateOnboardingStep } = useOnboarding('frappecrm')
const error = ref(null)
const title = ref(null)
const editMode = ref(false)
let _note = ref({})
async function updateNote() {
if (
props.note.title === _note.value.title &&
props.note.content === _note.value.content
)
return
if (_note.value.name) {
let d = await call('frappe.client.set_value', {
doctype: 'FCRM Note',
@ -124,6 +100,12 @@ async function updateNote() {
reference_doctype: props.doctype,
reference_docname: props.doc || '',
},
}, {
onError: (err) => {
if (err.error.exc_type == 'MandatoryError') {
error.value = "Title is mandatory"
}
}
})
if (d.name) {
updateOnboardingStep('create_first_note')

View File

@ -1,43 +1,28 @@
<template>
<Dialog v-model="show" :options="{ size: 'xl' }">
<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 class="px-4 pt-5 pb-6 bg-surface-modal sm:px-6">
<div class="flex items-center justify-between mb-5">
<div>
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ __('New Organization') }}
</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 v-if="isManager() && !isMobileView" variant="ghost" class="w-7" @click="openQuickEntryModal">
<EditIcon class="w-4 h-4" />
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<FeatherIcon name="x" class="h-4 w-4" />
<FeatherIcon name="x" class="w-4 h-4" />
</Button>
</div>
</div>
<FieldLayout
v-if="tabs.data?.length"
:tabs="tabs.data"
:data="_organization"
doctype="CRM Organization"
/>
<FieldLayout v-if="tabs.data?.length" :tabs="tabs.data" :data="_organization" doctype="CRM Organization" />
<ErrorMessage class="mt-8" v-if="error" :message="__(error)" />
</div>
<div class="px-4 pb-7 pt-4 sm:px-6">
<div class="px-4 pt-4 pb-7 sm:px-6">
<div class="space-y-2">
<Button
class="w-full"
variant="solid"
:label="__('Create')"
:loading="loading"
@click="createOrganization"
/>
<Button class="w-full" variant="solid" :label="__('Create')" :loading="loading" @click="createOrganization" />
</div>
</div>
</template>
@ -59,7 +44,7 @@ const props = defineProps({
type: Object,
default: {
redirect: true,
afterInsert: () => {},
afterInsert: () => { },
},
},
})
@ -84,6 +69,7 @@ let _organization = ref({
})
let doc = ref({})
const error = ref(null)
async function createOrganization() {
const doc = await call('frappe.client.insert', {
@ -91,6 +77,12 @@ async function createOrganization() {
doctype: 'CRM Organization',
..._organization.value,
},
}, {
onError: (err) => {
if (err.error.exc_type == 'ValidationError') {
error.value = err.error?.messages?.[0]
}
}
})
loading.value = false
if (doc.name) {

View File

@ -1,34 +1,25 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
actions: [
{
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateTask(),
},
],
}"
>
<Dialog v-model="show" :options="{
size: 'xl',
actions: [
{
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateTask(),
},
],
}">
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ editMode ? __('Edit Task') : __('Create Task') }}
</h3>
<Button
v-if="task?.reference_docname"
size="sm"
:label="
task.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
"
@click="redirect()"
>
<Button v-if="task?.reference_docname" size="sm" :label="task.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
" @click="redirect()">
<template #suffix>
<ArrowUpRightIcon class="h-4 w-4" />
<ArrowUpRightIcon class="w-4 h-4" />
</template>
</Button>
</div>
@ -36,74 +27,53 @@
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<FormControl
ref="title"
:label="__('Title')"
v-model="_task.title"
:placeholder="__('Call with John Doe')"
/>
<FormControl ref="title" :label="__('Title')" v-model="_task.title" :placeholder="__('Call with John Doe')"
required />
</div>
<div>
<div class="mb-1.5 text-xs text-ink-gray-5">
{{ __('Description') }}
</div>
<TextEditor
variant="outline"
ref="description"
<TextEditor variant="outline" ref="description"
editor-class="!prose-sm overflow-auto min-h-[180px] max-h-80 py-1.5 px-2 rounded border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors"
:bubbleMenu="true"
:content="_task.description"
@change="(val) => (_task.description = val)"
:placeholder="
__('Took a call with John Doe and discussed the new project.')
"
/>
:bubbleMenu="true" :content="_task.description" @change="(val) => (_task.description = val)" :placeholder="__('Took a call with John Doe and discussed the new project.')
" />
</div>
<div class="flex flex-wrap items-center gap-2">
<Dropdown :options="taskStatusOptions(updateTaskStatus)">
<Button :label="_task.status" class="w-full justify-between">
<Button :label="_task.status" class="justify-between w-full">
<template #prefix>
<TaskStatusIcon :status="_task.status" />
</template>
</Button>
</Dropdown>
<Link
class="form-control"
:value="getUser(_task.assigned_to).full_name"
doctype="User"
@change="(option) => (_task.assigned_to = option)"
:placeholder="__('John Doe')"
:hideMe="true"
>
<template #prefix>
<UserAvatar class="mr-2 !h-4 !w-4" :user="_task.assigned_to" />
</template>
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.value" size="sm" />
</template>
<template #item-label="{ option }">
<Tooltip :text="option.value">
<div class="cursor-pointer text-ink-gray-9">
{{ getUser(option.value).full_name }}
</div>
</Tooltip>
</template>
<Link class="form-control" :value="getUser(_task.assigned_to).full_name" doctype="User"
@change="(option) => (_task.assigned_to = option)" :placeholder="__('John Doe')" :hideMe="true">
<template #prefix>
<UserAvatar class="mr-2 !h-4 !w-4" :user="_task.assigned_to" />
</template>
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.value" size="sm" />
</template>
<template #item-label="{ option }">
<Tooltip :text="option.value">
<div class="cursor-pointer text-ink-gray-9">
{{ getUser(option.value).full_name }}
</div>
</Tooltip>
</template>
</Link>
<DateTimePicker
class="datepicker w-36"
v-model="_task.due_date"
:placeholder="__('01/04/2024 11:30 PM')"
:formatter="(date) => getFormat(date, '', true, true)"
input-class="border-none"
/>
<DateTimePicker class="datepicker w-36" v-model="_task.due_date" :placeholder="__('01/04/2024 11:30 PM')"
:formatter="(date) => getFormat(date, '', true, true)" input-class="border-none" />
<Dropdown :options="taskPriorityOptions(updateTaskPriority)">
<Button :label="_task.priority" class="w-full justify-between">
<Button :label="_task.priority" class="justify-between w-full">
<template #prefix>
<TaskPriorityIcon :priority="_task.priority" />
</template>
</Button>
</Dropdown>
</div>
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</div>
</template>
</Dialog>
@ -147,6 +117,7 @@ const router = useRouter()
const { getUser } = usersStore()
const { updateOnboardingStep } = useOnboarding('frappecrm')
const error = ref(null)
const title = ref(null)
const editMode = ref(false)
const _task = ref({
@ -200,6 +171,12 @@ async function updateTask() {
reference_docname: props.doc || null,
..._task.value,
},
}, {
onError: (err) => {
if (err.error.exc_type == 'MandatoryError') {
error.value = "Title is mandatory"
}
}
})
if (d.name) {
updateOnboardingStep('create_first_task')

View File

@ -0,0 +1,71 @@
<template>
<div class="flex items-center">
<Button
:variant="$attrs.variant"
class="border-0"
:label="activeButton.label"
:size="$attrs.size"
:class="[
$attrs.class,
showDropdown ? 'rounded-br-none rounded-tr-none' : '',
]"
@click="() => activeButton.onClick()"
>
<template #prefix>
<FeatherIcon
v-if="activeButton.icon && typeof activeButton.icon === 'string'"
:name="activeButton.icon"
class="h-4 w-4"
/>
<component
v-else-if="activeButton.icon"
:is="activeButton.icon"
class="h-4 w-4"
/>
</template>
</Button>
<Dropdown
v-show="showDropdown"
:options="parsedOptions"
size="sm"
class="flex-1 [&>div>div>div]:w-full"
placement="right"
>
<template v-slot="{ togglePopover }">
<Button
:variant="$attrs.variant"
@click="togglePopover"
icon="chevron-down"
class="!w-6 justify-start rounded-bl-none rounded-tl-none border-0 pr-0 text-xs"
/>
</template>
</Dropdown>
</div>
</template>
<script setup>
import { Dropdown } from 'frappe-ui'
import { computed, ref } from 'vue'
const props = defineProps({
options: {
type: Array,
default: () => [],
},
})
const showDropdown = ref(props.options?.length > 1)
const activeButton = ref(props.options?.[0] || {})
const parsedOptions = computed(() => {
return (
props.options?.map((option) => {
return {
label: option.label,
onClick: () => {
activeButton.value = option
},
}
}) || []
)
})
</script>

View File

@ -215,10 +215,16 @@
</template>
</Link>
<Link
v-else-if="field.fieldtype === 'Link'"
v-else-if="
['Link', 'Dynamic Link'].includes(field.fieldtype)
"
class="form-control select-text"
:value="data[field.fieldname]"
:doctype="field.options"
:doctype="
field.fieldtype == 'Link'
? field.options
: data[field.options]
"
:filters="field.filters"
:placeholder="field.placeholder"
@change="

View File

@ -545,7 +545,6 @@ const tabs = computed(() => {
name: 'Calls',
label: __('Calls'),
icon: PhoneIcon,
condition: () => callEnabled.value,
},
{
name: 'Tasks',

View File

@ -457,9 +457,6 @@ function parseRows(rows, columns = []) {
}
} else if (row == '_assign') {
let assignees = JSON.parse(deal._assign || '[]')
if (!assignees.length && deal.deal_owner) {
assignees = [deal.deal_owner]
}
_rows[row] = assignees.map((user) => ({
name: user,
image: getUser(user).user_image,

View File

@ -532,7 +532,6 @@ const tabs = computed(() => {
name: 'Calls',
label: __('Calls'),
icon: PhoneIcon,
condition: () => callEnabled.value,
},
{
name: 'Tasks',

View File

@ -480,9 +480,6 @@ function parseRows(rows, columns = []) {
}
} else if (row == '_assign') {
let assignees = JSON.parse(lead._assign || '[]')
if (!assignees.length && lead.lead_owner) {
assignees = [lead.lead_owner]
}
_rows[row] = assignees.map((user) => ({
name: user,
image: getUser(user).user_image,

View File

@ -2542,10 +2542,10 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.121:
version "0.1.121"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.121.tgz#a8d37f300228edfcbb6b4fffb343f0773dcfd933"
integrity sha512-gvtKKZECPD2MU5X4MwPUKr2hSOs1+s1DA9laP3aPnmH0ukJRSFEhDOyjCMfH9k6ZdAe/vZCIbT4XucxLq/fOEA==
frappe-ui@^0.1.123:
version "0.1.123"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.123.tgz#501139a103a03e52648d9ee9ea85aa54bc8102e0"
integrity sha512-WkTnKZ+n82d9xZ9g9ZQXVkFyKU2wlcfT6/9g8/2biJuXMwmo/80I29EKGb9nrM1Liuj0Wtyg9nsqvfvgktdHbw==
dependencies:
"@headlessui/vue" "^1.7.14"
"@popperjs/core" "^2.11.2"