diff --git a/crm/api/session.py b/crm/api/session.py index e40a769b..c6c5e3dd 100644 --- a/crm/api/session.py +++ b/crm/api/session.py @@ -30,4 +30,18 @@ def get_contacts(): distinct=True, ).run(as_dict=1) - return contacts \ No newline at end of file + return contacts + +@frappe.whitelist() +def get_organizations(): + if frappe.session.user == "Guest": + frappe.throw("Authentication failed", exc=frappe.AuthenticationError) + + organizations = frappe.qb.get_query( + "CRM Organization", + fields=['name', 'organization_logo', 'website'], + order_by="name asc", + distinct=True, + ).run(as_dict=1) + + return organizations diff --git a/crm/fcrm/doctype/crm_organization/__init__.py b/crm/fcrm/doctype/crm_organization/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.js b/crm/fcrm/doctype/crm_organization/crm_organization.js new file mode 100644 index 00000000..763952d1 --- /dev/null +++ b/crm/fcrm/doctype/crm_organization/crm_organization.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 Organization", { +// refresh(frm) { + +// }, +// }); diff --git a/crm/fcrm/doctype/crm_organization/crm_organization.json b/crm/fcrm/doctype/crm_organization/crm_organization.json new file mode 100644 index 00000000..d58e76aa --- /dev/null +++ b/crm/fcrm/doctype/crm_organization/crm_organization.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:organization_name", + "creation": "2023-11-03 16:23:59.341751", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "organization_name", + "website", + "organization_logo" + ], + "fields": [ + { + "fieldname": "organization_name", + "fieldtype": "Data", + "label": "Organization Name", + "unique": 1 + }, + { + "fieldname": "website", + "fieldtype": "Data", + "label": "Website" + }, + { + "fieldname": "organization_logo", + "fieldtype": "Attach Image", + "label": "Organization Logo" + } + ], + "image_field": "organization_logo", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-11-03 16:25:25.366741", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM Organization", + "naming_rule": "By fieldname", + "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_organization/crm_organization.py b/crm/fcrm/doctype/crm_organization/crm_organization.py new file mode 100644 index 00000000..5933eabf --- /dev/null +++ b/crm/fcrm/doctype/crm_organization/crm_organization.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 CRMOrganization(Document): + pass diff --git a/crm/fcrm/doctype/crm_organization/test_crm_organization.py b/crm/fcrm/doctype/crm_organization/test_crm_organization.py new file mode 100644 index 00000000..e11be575 --- /dev/null +++ b/crm/fcrm/doctype/crm_organization/test_crm_organization.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 TestCRMOrganization(FrappeTestCase): + pass diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index bca4adb0..ab059e89 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -619,8 +619,8 @@ import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue' import InboundCallIcon from '@/components/Icons/InboundCallIcon.vue' import OutboundCallIcon from '@/components/Icons/OutboundCallIcon.vue' import CommunicationArea from '@/components/CommunicationArea.vue' -import NoteModal from '@/components/NoteModal.vue' -import TaskModal from '@/components/TaskModal.vue' +import NoteModal from '@/components/Modals/NoteModal.vue' +import TaskModal from '@/components/Modals/TaskModal.vue' import { timeAgo, dateFormat, diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index 7fc0af60..a0217a1e 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -37,6 +37,7 @@ import UserDropdown from '@/components/UserDropdown.vue' import LeadsIcon from '@/components/Icons/LeadsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue' +import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue' @@ -59,6 +60,11 @@ const links = [ icon: ContactsIcon, to: 'Contacts', }, + { + label: 'Organizations', + icon: OrganizationsIcon, + to: 'Organizations', + }, { label: 'Notes', icon: NoteIcon, diff --git a/frontend/src/components/CallUI.vue b/frontend/src/components/CallUI.vue index 55cf9596..2de01dff 100644 --- a/frontend/src/components/CallUI.vue +++ b/frontend/src/components/CallUI.vue @@ -180,12 +180,12 @@ import MinimizeIcon from '@/components/Icons/MinimizeIcon.vue' import DialpadIcon from '@/components/Icons/DialpadIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import CountUpTimer from '@/components/CountUpTimer.vue' +import NoteModal from '@/components/Modals/NoteModal.vue' import { Device } from '@twilio/voice-sdk' import { useDraggable, useWindowSize } from '@vueuse/core' import { contactsStore } from '@/stores/contacts' import { Avatar, call } from 'frappe-ui' import { onMounted, ref, watch, getCurrentInstance } from 'vue' -import NoteModal from './NoteModal.vue' const { getContact } = contactsStore() diff --git a/frontend/src/components/Icons/EditIcon.vue b/frontend/src/components/Icons/EditIcon.vue new file mode 100644 index 00000000..8edb183b --- /dev/null +++ b/frontend/src/components/Icons/EditIcon.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/frontend/src/components/Icons/OrganizationsIcon.vue b/frontend/src/components/Icons/OrganizationsIcon.vue new file mode 100644 index 00000000..cea9172b --- /dev/null +++ b/frontend/src/components/Icons/OrganizationsIcon.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/frontend/src/components/Icons/WebsiteIcon.vue b/frontend/src/components/Icons/WebsiteIcon.vue new file mode 100644 index 00000000..6b7f367c --- /dev/null +++ b/frontend/src/components/Icons/WebsiteIcon.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/frontend/src/components/ListViews/ContactsListView.vue b/frontend/src/components/ListViews/ContactsListView.vue new file mode 100644 index 00000000..782fb295 --- /dev/null +++ b/frontend/src/components/ListViews/ContactsListView.vue @@ -0,0 +1,23 @@ + + + + diff --git a/frontend/src/components/ListViews/DealsListView.vue b/frontend/src/components/ListViews/DealsListView.vue new file mode 100644 index 00000000..aa53fbc4 --- /dev/null +++ b/frontend/src/components/ListViews/DealsListView.vue @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + {{ item.timeAgo }} + + + + + + + + + diff --git a/frontend/src/components/ListViews/LeadsListView.vue b/frontend/src/components/ListViews/LeadsListView.vue new file mode 100644 index 00000000..a0b005da --- /dev/null +++ b/frontend/src/components/ListViews/LeadsListView.vue @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + {{ item.timeAgo }} + + + + + + + + + diff --git a/frontend/src/components/NoteModal.vue b/frontend/src/components/Modals/NoteModal.vue similarity index 100% rename from frontend/src/components/NoteModal.vue rename to frontend/src/components/Modals/NoteModal.vue diff --git a/frontend/src/components/Modals/OrganizationModal.vue b/frontend/src/components/Modals/OrganizationModal.vue new file mode 100644 index 00000000..66f395e1 --- /dev/null +++ b/frontend/src/components/Modals/OrganizationModal.vue @@ -0,0 +1,144 @@ + + + + + + Organization name + + + + Website + + + + + + + + diff --git a/frontend/src/components/TaskModal.vue b/frontend/src/components/Modals/TaskModal.vue similarity index 100% rename from frontend/src/components/TaskModal.vue rename to frontend/src/components/Modals/TaskModal.vue diff --git a/frontend/src/pages/CallLog.vue b/frontend/src/pages/CallLog.vue index 2475e61e..9208de92 100644 --- a/frontend/src/pages/CallLog.vue +++ b/frontend/src/pages/CallLog.vue @@ -142,7 +142,7 @@ diff --git a/frontend/src/pages/Organizations.vue b/frontend/src/pages/Organizations.vue new file mode 100644 index 00000000..a0d61642 --- /dev/null +++ b/frontend/src/pages/Organizations.vue @@ -0,0 +1,107 @@ + + + + + + + + + Create organization + + + + + + + + + + + {{ organization.name }} + + {{ + website(organization.website) + }} + + + + + + + + + No organization selected + + + + + + diff --git a/frontend/src/router.js b/frontend/src/router.js index 44da837f..c841ccb1 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -39,6 +39,19 @@ const routes = [ name: 'Contacts', component: () => import('@/pages/Contacts.vue'), }, + { + path: '/organizations', + name: 'Organizations', + component: () => import('@/pages/Organizations.vue'), + children: [ + { + path: '/organizations/:organizationId?', + name: 'Organization', + component: () => import('@/pages/Organization.vue'), + props: true, + }, + ], + }, { path: '/call-logs', name: 'Call Logs', diff --git a/frontend/src/stores/organizations.js b/frontend/src/stores/organizations.js new file mode 100644 index 00000000..dc8ada9b --- /dev/null +++ b/frontend/src/stores/organizations.js @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia' +import { createResource } from 'frappe-ui' +import { reactive } from 'vue' + +export const organizationsStore = defineStore('crm-organizations', () => { + let organizationsByName = reactive({}) + + const organizations = createResource({ + url: 'crm.api.session.get_organizations', + cache: 'organizations', + initialData: [], + transform(organizations) { + for (let organization of organizations) { + organizationsByName[organization.name] = organization + } + return organizations + }, + onError(error) { + if (error && error.exc_type === 'AuthenticationError') { + router.push('/login') + } + }, + }) + organizations.fetch() + + function getOrganization(name) { + return organizationsByName[name] + } + + return { + organizations, + getOrganization, + } +})