diff --git a/crm/fcrm/doctype/crm_task/__init__.py b/crm/fcrm/doctype/crm_task/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/fcrm/doctype/crm_task/crm_task.js b/crm/fcrm/doctype/crm_task/crm_task.js new file mode 100644 index 00000000..8357f6e3 --- /dev/null +++ b/crm/fcrm/doctype/crm_task/crm_task.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("CRM Task", { +// refresh(frm) { + +// }, +// }); diff --git a/crm/fcrm/doctype/crm_task/crm_task.json b/crm/fcrm/doctype/crm_task/crm_task.json new file mode 100644 index 00000000..cf7dbe52 --- /dev/null +++ b/crm/fcrm/doctype/crm_task/crm_task.json @@ -0,0 +1,104 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2023-09-28 15:04:28.084159", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "priority", + "start_date", + "lead", + "column_break_cqua", + "assigned_to", + "status", + "due_date", + "section_break_bzhd", + "description" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "priority", + "fieldtype": "Select", + "label": "Priority", + "options": "Low\nMedium\nHigh" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date" + }, + { + "fieldname": "column_break_cqua", + "fieldtype": "Column Break" + }, + { + "fieldname": "assigned_to", + "fieldtype": "Link", + "label": "Assigned To", + "options": "User" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Backlog\nTodo\nIn Progress\nDone\nCanceled" + }, + { + "fieldname": "due_date", + "fieldtype": "Date", + "label": "Due Date" + }, + { + "fieldname": "section_break_bzhd", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + }, + { + "fieldname": "lead", + "fieldtype": "Link", + "label": "Lead", + "options": "CRM Lead" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-09-28 15:05:27.986420", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM Task", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_task/crm_task.py b/crm/fcrm/doctype/crm_task/crm_task.py new file mode 100644 index 00000000..325843cc --- /dev/null +++ b/crm/fcrm/doctype/crm_task/crm_task.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CRMTask(Document): + pass diff --git a/crm/fcrm/doctype/crm_task/test_crm_task.py b/crm/fcrm/doctype/crm_task/test_crm_task.py new file mode 100644 index 00000000..f8316336 --- /dev/null +++ b/crm/fcrm/doctype/crm_task/test_crm_task.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestCRMTask(FrappeTestCase): + pass diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 8f11505f..a456d9a8 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,11 +3,13 @@ + diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index 2ffde9a1..edde8a91 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -8,14 +8,26 @@ variant="solid" @click="makeCall(lead.data.mobile_no)" > - + + Make a call - +
-
+
+
+
+
+
+
+ {{ task.title }} +
+
+
+ + {{ getUser(task.assigned_to).full_name }} +
+
+ +
+
+ + + {{ dateFormat(task.due_date, 'D MMM') }} + +
+
+ +
+
+ + {{ task.priority }} +
+
+
+
+ + + + + + +
+
+
+
+
@@ -468,7 +567,7 @@ v-else-if="title == 'Notes'" variant="solid" label="Create note" - @click="showNote" + @click="showNote()" />
- + + diff --git a/frontend/src/components/Icons/TaskPriorityIcon.vue b/frontend/src/components/Icons/TaskPriorityIcon.vue new file mode 100644 index 00000000..dc2fdf6c --- /dev/null +++ b/frontend/src/components/Icons/TaskPriorityIcon.vue @@ -0,0 +1,23 @@ + + diff --git a/frontend/src/components/Icons/TaskStatusIcon.vue b/frontend/src/components/Icons/TaskStatusIcon.vue new file mode 100644 index 00000000..c7ed07b1 --- /dev/null +++ b/frontend/src/components/Icons/TaskStatusIcon.vue @@ -0,0 +1,54 @@ + + diff --git a/frontend/src/components/NoteModal.vue b/frontend/src/components/NoteModal.vue index 6c0f8aae..6c0eaf25 100644 --- a/frontend/src/components/NoteModal.vue +++ b/frontend/src/components/NoteModal.vue @@ -1,70 +1,111 @@ + + diff --git a/frontend/src/components/UserAvatar.vue b/frontend/src/components/UserAvatar.vue index 0200e15e..eb44bde4 100644 --- a/frontend/src/components/UserAvatar.vue +++ b/frontend/src/components/UserAvatar.vue @@ -1,6 +1,5 @@ -
+
-
+
{{ note.title }}
-
+
@@ -60,17 +63,17 @@
- + No notes
@@ -86,10 +89,9 @@ import { Button, createListResource, TextEditor, - TextInput, call, Dropdown, -Tooltip, + Tooltip, } from 'frappe-ui' import { ref } from 'vue' import { usersStore } from '@/stores/users' @@ -129,30 +131,6 @@ function editNote(note) { showNoteModal.value = true } -async function updateNote(note) { - if (note.name) { - let d = await call('frappe.client.set_value', { - doctype: 'CRM Note', - name: note.name, - fieldname: note, - }) - if (d.name) { - notes.reload() - } - } else { - let d = await call('frappe.client.insert', { - doc: { - doctype: 'CRM Note', - title: note.title, - content: note.content, - }, - }) - if (d.name) { - notes.reload() - } - } -} - async function deleteNote(name) { await call('frappe.client.delete', { doctype: 'CRM Note', diff --git a/frontend/src/utils/dialogs.js b/frontend/src/utils/dialogs.js new file mode 100644 index 00000000..9efdde3b --- /dev/null +++ b/frontend/src/utils/dialogs.js @@ -0,0 +1,31 @@ +import { Dialog, ErrorMessage } from 'frappe-ui' +import { h, reactive, ref } from 'vue' + +let dialogs = ref([]) + +export let Dialogs = { + name: 'Dialogs', + render() { + return dialogs.value.map((dialog) => { + return h( + Dialog, + { + options: dialog, + modelValue: dialog.show, + 'onUpdate:modelValue': (val) => (dialog.show = val), + }, + () => [ + h('p', { class: 'text-p-base text-gray-700' }, dialog.message), + h(ErrorMessage, { class: 'mt-2', message: dialog.error }), + ] + ) + }) + }, +} + +export function createDialog(options) { + let dialog = reactive(options) + dialog.key = `dialog-${Math.random().toString(36).slice(2, 9)}` + dialogs.value.push(dialog) + dialog.show = true +} diff --git a/frontend/src/utils.js b/frontend/src/utils/index.js similarity index 72% rename from frontend/src/utils.js rename to frontend/src/utils/index.js index 37e5eced..5e32abac 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils/index.js @@ -1,7 +1,10 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' +import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue' +import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue' +import { usersStore } from '@/stores/users' import { useDateFormat, useTimeAgo } from '@vueuse/core' import { toast } from 'frappe-ui' -import { h } from 'vue' +import { h, computed } from 'vue' export function createToast(options) { toast({ @@ -91,6 +94,28 @@ export function statusDropdownOptions(data, doctype, action) { return options } +export function taskStatusOptions(action, data) { + return ['Backlog', 'Todo', 'In Progress', 'Done', 'Canceled'].map( + (status) => { + return { + icon: () => h(TaskStatusIcon, { status }), + label: status, + onClick: () => action && action(status, data), + } + } + ) +} + +export function taskPriorityOptions(action, data) { + return ['Low', 'Medium', 'High'].map((priority) => { + return { + label: priority, + icon: () => h(TaskPriorityIcon, { priority }), + onClick: () => action && action(priority, data), + } + }) +} + export function openWebsite(url) { window.open(url, '_blank') } @@ -128,3 +153,19 @@ export function formatNumberIntoCurrency(value) { export function startCase(str) { return str.charAt(0).toUpperCase() + str.slice(1) } + +const { users } = usersStore() + +export const activeAgents = computed(() => { + const nonAgents = ['Administrator', 'Guest'] + return users.data + .filter((user) => !nonAgents.includes(user.name)) + .sort((a, b) => a.full_name - b.full_name) + .map((user) => { + return { + label: user.full_name, + value: user.email, + ...user, + } + }) +})