diff --git a/crm/api/doc.py b/crm/api/doc.py
index 8fc1f705..199c99b1 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -240,7 +240,8 @@ def get_list_data(
"views": get_views(doctype),
"total_count": len(frappe.get_list(doctype, filters=filters)),
"row_count": len(data),
- "form_script": get_form_script(doctype)
+ "form_script": get_form_script(doctype),
+ "list_script": get_form_script(doctype, "List"),
}
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.js b/crm/fcrm/doctype/crm_form_script/crm_form_script.js
index 7f6f150e..9df643d5 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.js
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.js
@@ -1,8 +1,38 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-// frappe.ui.form.on("CRM Form Script", {
-// refresh(frm) {
+frappe.ui.form.on("CRM Form Script", {
+ refresh(frm) {
+ frm.set_query("dt", {
+ filters: {
+ istable: 0,
+ },
+ });
+ },
+ view(frm) {
+ let has_form_boilerplate = frm.doc.script.includes(
+ "function setupForm("
+ );
+ let has_list_boilerplate = frm.doc.script.includes(
+ "function setupList("
+ );
-// },
-// });
+ if (frm.doc.view == "Form" && !has_form_boilerplate) {
+ frm.doc.script = `
+function setupForm({ doc }) {
+ return {
+ actions: [],
+ }
+}`.trim();
+ }
+ if (frm.doc.view == "List" && !has_list_boilerplate) {
+ frm.doc.script = `
+function setupList({ list }) {
+ return {
+ actions: [],
+ bulk_actions: [],
+ }
+}`.trim();
+ }
+ },
+});
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.json b/crm/fcrm/doctype/crm_form_script/crm_form_script.json
index b883e804..9a026098 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.json
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.json
@@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"dt",
+ "view",
"column_break_gboh",
"enabled",
"section_break_xeox",
@@ -36,16 +37,26 @@
"label": "Enabled"
},
{
- "default": "function setupForm({ doc }) {\n return {\n actions: []\n }\n}",
+ "default": "function setupForm({ doc }) {\n return {\n actions: [],\n }\n}",
+ "documentation_url": "https://docs.frappe.io/crm/custom-actions",
"fieldname": "script",
"fieldtype": "Code",
"label": "Script",
"options": "JS"
+ },
+ {
+ "default": "Form",
+ "fieldname": "view",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Apply To",
+ "options": "Form\nList",
+ "set_only_once": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-01-19 21:52:46.078626",
+ "modified": "2024-04-10 18:27:17.617602",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Form Script",
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.py b/crm/fcrm/doctype/crm_form_script/crm_form_script.py
index 32649d62..ff93ff4f 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.py
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.py
@@ -14,6 +14,7 @@ class CRMFormScript(Document):
if self.dt and self.enabled:
filters = {
"dt": self.dt,
+ "view": self.view,
"enabled": 1,
}
if self.name:
@@ -27,13 +28,14 @@ class CRMFormScript(Document):
frappe.DuplicateEntryError,
)
-def get_form_script(dt):
- """Returns the script for the given doctype"""
+def get_form_script(dt, view="Form"):
+ """Returns the form script for the given doctype"""
FormScript = frappe.qb.DocType("CRM Form Script")
query = (
frappe.qb.from_(FormScript)
.select("script")
.where(FormScript.dt == dt)
+ .where(FormScript.view == view)
.where(FormScript.enabled == 1)
.limit(1)
)
diff --git a/frappe-ui b/frappe-ui
index 03115629..05a8eca5 160000
--- a/frappe-ui
+++ b/frappe-ui
@@ -1 +1 @@
-Subproject commit 03115629ffdb3712accd57123fb1d6b882a15e67
+Subproject commit 05a8eca589d23d44f55cfe82ae157fd5de997abb
diff --git a/frontend/package.json b/frontend/package.json
index eb4b7676..43f8e1a9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,7 +13,7 @@
"@vueuse/core": "^10.3.0",
"@vueuse/integrations": "^10.3.0",
"feather-icons": "^4.28.0",
- "frappe-ui": "^0.1.45",
+ "frappe-ui": "^0.1.51",
"mime": "^4.0.1",
"pinia": "^2.0.33",
"socket.io-client": "^4.7.2",
diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue
index 3e731dc9..c0916c64 100644
--- a/frontend/src/components/Activities.vue
+++ b/frontend/src/components/Activities.vue
@@ -162,14 +162,16 @@
>
-
-
+
- {{ dateFormat(task.due_date, 'D MMM, hh:mm a') }}
+
+
+
{{ dateFormat(task.due_date, 'D MMM, hh:mm a') }}
+
@@ -258,11 +260,10 @@
{{ call.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call
-
- {{ timeAgo(call.creation) }}
+
+
+ {{ timeAgo(call.creation) }}
+
@@ -374,10 +375,11 @@
{{ activity.data.sender_full_name }}
·
- {{ timeAgo(activity.creation) }}
+
+ {{ timeAgo(activity.creation) }}
+
@@ -456,11 +458,10 @@
-
- {{ timeAgo(activity.creation) }}
+
+
+ {{ timeAgo(activity.creation) }}
+
@@ -481,11 +482,10 @@
{{ activity.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call
-
- {{ timeAgo(activity.creation) }}
+
+
+ {{ timeAgo(activity.creation) }}
+
@@ -602,11 +602,10 @@
-
- {{ timeAgo(activity.creation) }}
+
+
+ {{ timeAgo(activity.creation) }}
+
@@ -665,11 +664,10 @@
-
- {{ timeAgo(activity.creation) }}
+
+
+ {{ timeAgo(activity.creation) }}
+
diff --git a/frontend/src/components/DropdownItem.vue b/frontend/src/components/DropdownItem.vue
index e142578d..f0e08217 100644
--- a/frontend/src/components/DropdownItem.vue
+++ b/frontend/src/components/DropdownItem.vue
@@ -1,55 +1,65 @@
-
-
{{ option.value }}
-
e.target.blur()"
- />
+
+
+
{{ option.value }}
+
e.target.blur()"
+ />
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
diff --git a/frontend/src/components/ListViews/CallLogsListView.vue b/frontend/src/components/ListViews/CallLogsListView.vue
index f2bf2858..45c34b74 100644
--- a/frontend/src/components/ListViews/CallLogsListView.vue
+++ b/frontend/src/components/ListViews/CallLogsListView.vue
@@ -40,13 +40,14 @@
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
{
if (!list.value?.data) return
- setupBulkActions(list.value.data)
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
})
diff --git a/frontend/src/components/ListViews/ContactsListView.vue b/frontend/src/components/ListViews/ContactsListView.vue
index 9a259f74..64fc550b 100644
--- a/frontend/src/components/ListViews/ContactsListView.vue
+++ b/frontend/src/components/ListViews/ContactsListView.vue
@@ -50,13 +50,14 @@
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
{
@@ -198,6 +202,8 @@ function deleteValues(selections, unselectAll) {
})
}
+const customListActions = ref([])
+
function bulkActions(selections, unselectAll) {
let actions = [
{
@@ -211,4 +217,21 @@ function bulkActions(selections, unselectAll) {
]
return actions
}
+
+onMounted(() => {
+ if (!list.value?.data) return
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
+ // customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
+})
diff --git a/frontend/src/components/ListViews/DealsListView.vue b/frontend/src/components/ListViews/DealsListView.vue
index 22500792..57a93e57 100644
--- a/frontend/src/components/ListViews/DealsListView.vue
+++ b/frontend/src/components/ListViews/DealsListView.vue
@@ -58,8 +58,7 @@
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
{
if (!list.value?.data) return
- setupBulkActions(list.value.data)
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
})
diff --git a/frontend/src/components/ListViews/EmailTemplatesListView.vue b/frontend/src/components/ListViews/EmailTemplatesListView.vue
index 8fc5cd7f..b260d0e3 100644
--- a/frontend/src/components/ListViews/EmailTemplatesListView.vue
+++ b/frontend/src/components/ListViews/EmailTemplatesListView.vue
@@ -26,13 +26,14 @@
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
import EditValueModal from '@/components/Modals/EditValueModal.vue'
import { globalStore } from '@/stores/global'
-import { createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import {
ListView,
ListHeader,
@@ -97,7 +98,8 @@ import {
call,
Tooltip,
} from 'frappe-ui'
-import { ref, watch } from 'vue'
+import { ref, watch, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
const props = defineProps({
rows: {
@@ -131,6 +133,8 @@ const emit = defineEmits([
const pageLengthCount = defineModel()
const list = defineModel('list')
+const router = useRouter()
+
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => {
@@ -180,6 +184,8 @@ function deleteValues(selections, unselectAll) {
})
}
+const customListActions = ref([])
+
function bulkActions(selections, unselectAll) {
let actions = [
{
@@ -193,4 +199,21 @@ function bulkActions(selections, unselectAll) {
]
return actions
}
+
+onMounted(() => {
+ if (!list.value?.data) return
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
+ // customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
+})
diff --git a/frontend/src/components/ListViews/LeadsListView.vue b/frontend/src/components/ListViews/LeadsListView.vue
index 3a18a8d7..a78a62ec 100644
--- a/frontend/src/components/ListViews/LeadsListView.vue
+++ b/frontend/src/components/ListViews/LeadsListView.vue
@@ -67,8 +67,7 @@
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
{
if (!list.value?.data) return
- setupBulkActions(list.value.data)
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
})
diff --git a/frontend/src/components/ListViews/OrganizationsListView.vue b/frontend/src/components/ListViews/OrganizationsListView.vue
index 0c01940e..059f661b 100644
--- a/frontend/src/components/ListViews/OrganizationsListView.vue
+++ b/frontend/src/components/ListViews/OrganizationsListView.vue
@@ -37,13 +37,14 @@
/>
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
import EditValueModal from '@/components/Modals/EditValueModal.vue'
import { globalStore } from '@/stores/global'
-import { createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import {
Avatar,
ListView,
@@ -101,7 +102,8 @@ import {
Dropdown,
call,
} from 'frappe-ui'
-import { ref, watch } from 'vue'
+import { ref, watch, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
const props = defineProps({
rows: {
@@ -134,6 +136,8 @@ const emit = defineEmits([
const pageLengthCount = defineModel()
const list = defineModel('list')
+const router = useRouter()
+
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => {
@@ -183,6 +187,8 @@ function deleteValues(selections, unselectAll) {
})
}
+const customListActions = ref([])
+
function bulkActions(selections, unselectAll) {
let actions = [
{
@@ -196,4 +202,21 @@ function bulkActions(selections, unselectAll) {
]
return actions
}
+
+onMounted(() => {
+ if (!list.value?.data) return
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
+ // customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
+})
diff --git a/frontend/src/components/ListViews/TasksListView.vue b/frontend/src/components/ListViews/TasksListView.vue
index 32e1cb0c..33898cf3 100644
--- a/frontend/src/components/ListViews/TasksListView.vue
+++ b/frontend/src/components/ListViews/TasksListView.vue
@@ -19,18 +19,16 @@
v-slot="{ idx, column, item }"
:row="row"
>
-
-
-
-
-
- {{ dateFormat(item, 'D MMM, hh:mm a') }}
-
-
+
+
+
+
+
+ {{ dateFormat(item, 'D MMM, hh:mm a') }}
+
+
+
+
-
- {{ item.timeAgo }}
-
+
+ {{ item.timeAgo }}
+
+
{
@@ -204,6 +206,8 @@ function deleteValues(selections, unselectAll) {
})
}
+const customListActions = ref([])
+
function bulkActions(selections, unselectAll) {
let actions = [
{
@@ -217,4 +221,21 @@ function bulkActions(selections, unselectAll) {
]
return actions
}
+
+onMounted(() => {
+ if (!list.value?.data) return
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
+ // customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
+})
diff --git a/frontend/src/components/Modals/AssignmentModal.vue b/frontend/src/components/Modals/AssignmentModal.vue
index cafac059..2bafdbcd 100644
--- a/frontend/src/components/Modals/AssignmentModal.vue
+++ b/frontend/src/components/Modals/AssignmentModal.vue
@@ -34,7 +34,9 @@
- {{ getUser(option.value).full_name }}
+
+ {{ getUser(option.value).full_name }}
+
diff --git a/frontend/src/components/Modals/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue
index 554a054f..2024eb94 100644
--- a/frontend/src/components/Modals/ContactModal.vue
+++ b/frontend/src/components/Modals/ContactModal.vue
@@ -92,15 +92,13 @@
-
-
+
-
-
+ :key="option.name"
+ :option="option"
+ />
No {{ field.label }} Available
@@ -166,7 +164,7 @@ import CertificateIcon from '@/components/Icons/CertificateIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import Link from '@/components/Controls/Link.vue'
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
-import { call } from 'frappe-ui'
+import { Tooltip, call } from 'frappe-ui'
import { ref, nextTick, watch, computed, h } from 'vue'
import { createToast } from '@/utils'
import { useRouter } from 'vue-router'
diff --git a/frontend/src/components/Modals/TaskModal.vue b/frontend/src/components/Modals/TaskModal.vue
index 1e6b9be7..e99b4c1a 100644
--- a/frontend/src/components/Modals/TaskModal.vue
+++ b/frontend/src/components/Modals/TaskModal.vue
@@ -21,7 +21,9 @@
v-if="task?.reference_docname"
variant="outline"
size="sm"
- :label="task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'"
+ :label="
+ task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'
+ "
@click="redirect()"
>
@@ -77,7 +79,9 @@
- {{ getUser(option.value).full_name }}
+
+ {{ getUser(option.value).full_name }}
+
diff --git a/frontend/src/components/MultipleAvatar.vue b/frontend/src/components/MultipleAvatar.vue
index adba7bad..4db97f39 100644
--- a/frontend/src/components/MultipleAvatar.vue
+++ b/frontend/src/components/MultipleAvatar.vue
@@ -2,20 +2,20 @@
-
-
- {{ avatars[0].label }}
+
+
+
+
{{ avatars[0].label }}
+
- {{ getUser(option.value).full_name }}
+
+ {{ getUser(option.value).full_name }}
+
diff --git a/frontend/src/components/NewLead.vue b/frontend/src/components/NewLead.vue
index 3b7aae3f..b64bb075 100644
--- a/frontend/src/components/NewLead.vue
+++ b/frontend/src/components/NewLead.vue
@@ -46,7 +46,9 @@
- {{ getUser(option.value).full_name }}
+
+ {{ getUser(option.value).full_name }}
+
diff --git a/frontend/src/components/Notifications.vue b/frontend/src/components/Notifications.vue
index 21f1f03f..fb75a130 100644
--- a/frontend/src/components/Notifications.vue
+++ b/frontend/src/components/Notifications.vue
@@ -7,7 +7,7 @@
'box-shadow': '8px 0px 8px rgba(0, 0, 0, 0.1)',
'max-width': '350px',
'min-width': '350px',
- 'left': 'calc(100% + 1px)'
+ left: 'calc(100% + 1px)',
}"
>
@@ -17,21 +17,25 @@
Notifications
-
+
+
+
-
+
+
+
diff --git a/frontend/src/components/SLASection.vue b/frontend/src/components/SLASection.vue
index 297d0e2b..cf691324 100644
--- a/frontend/src/components/SLASection.vue
+++ b/frontend/src/components/SLASection.vue
@@ -7,19 +7,17 @@
>
{{ s.label }}
-
-
- {{ s.value }}
+
+
{
status = 'less than a minute ago'
}
}
- } else if (["Fulfilled", "Failed"].includes(status)) {
- status = status + " in " + formatTime(data.value.first_response_time)
+ } else if (['Fulfilled', 'Failed'].includes(status)) {
+ status = status + ' in ' + formatTime(data.value.first_response_time)
tooltipText = dateFormat(data.value.first_responded_on, dateTooltipFormat)
}
diff --git a/frontend/src/components/SectionFields.vue b/frontend/src/components/SectionFields.vue
index cde6aa7d..5ef42aef 100644
--- a/frontend/src/components/SectionFields.vue
+++ b/frontend/src/components/SectionFields.vue
@@ -13,13 +13,14 @@
-
- {{ data[field.name] }}
-
+
+ {{ data[field.name] }}
+
+
- {{ getUser(option.value).full_name }}
+
+ {{ getUser(option.value).full_name }}
+
diff --git a/frontend/src/components/SidebarLink.vue b/frontend/src/components/SidebarLink.vue
index f8dae913..7dc6322a 100644
--- a/frontend/src/components/SidebarLink.vue
+++ b/frontend/src/components/SidebarLink.vue
@@ -9,10 +9,14 @@
:class="isCollapsed ? 'p-1' : 'px-2 py-1'"
>
-
+
-
+
diff --git a/frontend/src/pages/CallLog.vue b/frontend/src/pages/CallLog.vue
index 0dc11275..b12d4bbf 100644
--- a/frontend/src/pages/CallLog.vue
+++ b/frontend/src/pages/CallLog.vue
@@ -78,14 +78,11 @@
Duration
{{ callLog.data.duration }}
-
-
+
+
{{ timeAgo(callLog.data.creation) }}
-
-
+
+
diff --git a/frontend/src/pages/CallLogs.vue b/frontend/src/pages/CallLogs.vue
index 1c6b4ace..7d200827 100644
--- a/frontend/src/pages/CallLogs.vue
+++ b/frontend/src/pages/CallLogs.vue
@@ -3,6 +3,12 @@
+
+
+