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 @@ 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 @@ 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()" > 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 }}
+
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 @@ 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 }}
+ +
+ +
{{ 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] }}
+
+
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 @@ +