Merge remote-tracking branch 'upstream/develop' into bulk-delete-leads
This commit is contained in:
commit
26d49d7ae0
@ -1008,7 +1008,7 @@ def get_deals_by_territory(from_date="", to_date="", user=""):
|
|||||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||||
{deal_conds}
|
{deal_conds}
|
||||||
GROUP BY d.territory
|
GROUP BY d.territory
|
||||||
ORDER BY value DESC
|
ORDER BY deals DESC, value DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
@ -1065,7 +1065,7 @@ def get_deals_by_salesperson(from_date="", to_date="", user=""):
|
|||||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||||
{deal_conds}
|
{deal_conds}
|
||||||
GROUP BY d.deal_owner
|
GROUP BY d.deal_owner
|
||||||
ORDER BY value DESC
|
ORDER BY deals DESC, value DESC
|
||||||
""",
|
""",
|
||||||
{"from": from_date, "to": to_date},
|
{"from": from_date, "to": to_date},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
|
|||||||
@ -26,8 +26,9 @@ def create_default_manager_dashboard(force=False):
|
|||||||
doc.title = "Manager Dashboard"
|
doc.title = "Manager Dashboard"
|
||||||
doc.layout = default_manager_dashboard_layout()
|
doc.layout = default_manager_dashboard_layout()
|
||||||
doc.insert(ignore_permissions=True)
|
doc.insert(ignore_permissions=True)
|
||||||
elif force:
|
else:
|
||||||
doc = frappe.get_doc("CRM Dashboard", "Manager Dashboard")
|
doc = frappe.get_doc("CRM Dashboard", "Manager Dashboard")
|
||||||
doc.layout = default_manager_dashboard_layout()
|
if force:
|
||||||
doc.save(ignore_permissions=True)
|
doc.layout = default_manager_dashboard_layout()
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
return doc.layout
|
return doc.layout
|
||||||
|
|||||||
@ -129,15 +129,13 @@
|
|||||||
"fieldname": "email",
|
"fieldname": "email",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Primary Email",
|
"label": "Primary Email",
|
||||||
"options": "Email",
|
"options": "Email"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "mobile_no",
|
"fieldname": "mobile_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Primary Mobile No",
|
"label": "Primary Mobile No",
|
||||||
"options": "Phone",
|
"options": "Phone"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Qualification",
|
"default": "Qualification",
|
||||||
@ -251,8 +249,7 @@
|
|||||||
"fieldname": "phone",
|
"fieldname": "phone",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Primary Phone",
|
"label": "Primary Phone",
|
||||||
"options": "Phone",
|
"options": "Phone"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "log_tab",
|
"fieldname": "log_tab",
|
||||||
@ -435,7 +432,7 @@
|
|||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-13 11:54:20.608489",
|
"modified": "2025-08-26 12:12:56.324245",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Deal",
|
"name": "CRM Deal",
|
||||||
|
|||||||
@ -63,8 +63,7 @@
|
|||||||
"fieldname": "twiml_sid",
|
"fieldname": "twiml_sid",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "TwiML SID",
|
"label": "TwiML SID",
|
||||||
"permlevel": 1,
|
"permlevel": 1
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_ssqj",
|
"fieldname": "section_break_ssqj",
|
||||||
@ -105,7 +104,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-15 19:35:13.406254",
|
"modified": "2025-08-19 13:36:19.823197",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "CRM Twilio Settings",
|
"name": "CRM Twilio Settings",
|
||||||
@ -152,8 +151,9 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,14 +128,32 @@ def get_quotation_url(crm_deal, organization):
|
|||||||
address = address.get("name") if address else None
|
address = address.get("name") if address else None
|
||||||
|
|
||||||
if not erpnext_crm_settings.is_erpnext_in_different_site:
|
if not erpnext_crm_settings.is_erpnext_in_different_site:
|
||||||
quotation_url = get_url_to_list("Quotation")
|
base_url = f"{get_url_to_list('Quotation')}/new"
|
||||||
return f"{quotation_url}/new?quotation_to=CRM Deal&crm_deal={crm_deal}&party_name={crm_deal}&company={erpnext_crm_settings.erpnext_company}&contact_person={contact}&customer_address={address}"
|
params = {
|
||||||
|
"quotation_to": "CRM Deal",
|
||||||
|
"crm_deal": crm_deal,
|
||||||
|
"party_name": crm_deal,
|
||||||
|
"company": erpnext_crm_settings.erpnext_company,
|
||||||
|
"contact_person": contact,
|
||||||
|
"customer_address": address,
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
site_url = erpnext_crm_settings.get("erpnext_site_url")
|
site_url = erpnext_crm_settings.get("erpnext_site_url")
|
||||||
quotation_url = f"{site_url}/app/quotation"
|
base_url = f"{site_url}/app/quotation/new"
|
||||||
|
|
||||||
prospect = create_prospect_in_remote_site(crm_deal, erpnext_crm_settings)
|
prospect = create_prospect_in_remote_site(crm_deal, erpnext_crm_settings)
|
||||||
return f"{quotation_url}/new?quotation_to=Prospect&crm_deal={crm_deal}&party_name={prospect}&company={erpnext_crm_settings.erpnext_company}&contact_person={contact}&customer_address={address}"
|
params = {
|
||||||
|
"quotation_to": "Prospect",
|
||||||
|
"crm_deal": crm_deal,
|
||||||
|
"party_name": prospect,
|
||||||
|
"company": erpnext_crm_settings.erpnext_company,
|
||||||
|
"contact_person": contact,
|
||||||
|
"customer_address": address,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Filter out None values and build query string
|
||||||
|
query_string = "&".join(f"{key}={value}" for key, value in params.items() if value is not None)
|
||||||
|
|
||||||
|
return f"{base_url}?{query_string}"
|
||||||
|
|
||||||
|
|
||||||
def create_prospect_in_remote_site(crm_deal, erpnext_crm_settings):
|
def create_prospect_in_remote_site(crm_deal, erpnext_crm_settings):
|
||||||
|
|||||||
0
crm/fcrm/doctype/helpdesk_crm_settings/__init__.py
Normal file
0
crm/fcrm/doctype/helpdesk_crm_settings/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Helpdesk CRM Settings", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2025-08-18 17:25:49.638398",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enabled",
|
||||||
|
"column_break_idaw",
|
||||||
|
"is_helpdesk_in_different_site",
|
||||||
|
"helpdesk_site_url",
|
||||||
|
"helpdesk_site_apis_section",
|
||||||
|
"api_key",
|
||||||
|
"column_break_tqsm",
|
||||||
|
"api_secret"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_idaw",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "enabled",
|
||||||
|
"fieldname": "is_helpdesk_in_different_site",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Helpdesk installed on a different site?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.enabled && doc.is_helpdesk_in_different_site",
|
||||||
|
"fieldname": "helpdesk_site_url",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Helpdesk Site URL",
|
||||||
|
"mandatory_depends_on": "is_helpdesk_in_different_site"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "enabled",
|
||||||
|
"fieldname": "helpdesk_site_apis_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Helpdesk Site API's"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.enabled && doc.is_helpdesk_in_different_site",
|
||||||
|
"fieldname": "api_key",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "API Key",
|
||||||
|
"mandatory_depends_on": "is_helpdesk_in_different_site"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_tqsm",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.enabled && doc.is_helpdesk_in_different_site",
|
||||||
|
"fieldname": "api_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"label": "API Secret",
|
||||||
|
"mandatory_depends_on": "is_helpdesk_in_different_site"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-08-18 17:33:38.616328",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "FCRM",
|
||||||
|
"name": "Helpdesk CRM Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Sales Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "creation",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
178
crm/fcrm/doctype/helpdesk_crm_settings/helpdesk_crm_settings.py
Normal file
178
crm/fcrm/doctype/helpdesk_crm_settings/helpdesk_crm_settings.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class HelpdeskCRMSettings(Document):
|
||||||
|
def validate(self):
|
||||||
|
if self.enabled:
|
||||||
|
self.validate_if_helpdesk_installed()
|
||||||
|
self.create_helpdesk_script()
|
||||||
|
|
||||||
|
def validate_if_helpdesk_installed(self):
|
||||||
|
if not self.is_helpdesk_in_different_site:
|
||||||
|
if "helpdesk" not in frappe.get_installed_apps():
|
||||||
|
frappe.throw(_("Helpdesk is not installed in the current site"))
|
||||||
|
|
||||||
|
def create_helpdesk_script(self):
|
||||||
|
if not frappe.db.exists("CRM Form Script", "Helpdesk Integration Script"):
|
||||||
|
script = get_helpdesk_script()
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "CRM Form Script",
|
||||||
|
"name": "Helpdesk Integration Script",
|
||||||
|
"dt": "CRM Deal",
|
||||||
|
"view": "Form",
|
||||||
|
"script": script,
|
||||||
|
"enabled": 1,
|
||||||
|
"is_standard": 1,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_customer_in_helpdesk(name, email):
|
||||||
|
helpdesk_crm_settings = frappe.get_single("Helpdesk CRM Settings")
|
||||||
|
if not helpdesk_crm_settings.enabled:
|
||||||
|
frappe.throw(_("Helpdesk is not integrated with the CRM"))
|
||||||
|
|
||||||
|
if not helpdesk_crm_settings.is_helpdesk_in_different_site:
|
||||||
|
# from helpdesk.integrations.crm.api import create_customer
|
||||||
|
return create_customer(name, email)
|
||||||
|
|
||||||
|
|
||||||
|
def get_helpdesk_script():
|
||||||
|
return """class CRMDeal {
|
||||||
|
onLoad() {
|
||||||
|
this.actions.push(
|
||||||
|
{
|
||||||
|
group: "Helpdesk",
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Create customer in Helpdesk",
|
||||||
|
onClick: () => {
|
||||||
|
call('crm.fcrm.doctype.helpdesk_crm_settings.helpdesk_crm_settings.create_customer_in_helpdesk', {
|
||||||
|
name: this.doc.organization,
|
||||||
|
email: this.doc.email
|
||||||
|
}).then((a) => {
|
||||||
|
toast.success("Customer created successfully, " + a.customer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
# Helpdesk methods TODO: move to helpdesk.integrations.crm.api
|
||||||
|
def create_customer(name, email):
|
||||||
|
customer = frappe.db.exists("HD Customer", name)
|
||||||
|
if not customer:
|
||||||
|
customer = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "HD Customer",
|
||||||
|
"customer_name": name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
customer.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||||
|
else:
|
||||||
|
customer = frappe.get_doc("HD Customer", customer)
|
||||||
|
|
||||||
|
contact = frappe.db.exists("Contact", {"email_id": email})
|
||||||
|
if contact:
|
||||||
|
contact = frappe.get_doc("Contact", contact)
|
||||||
|
contact.append(
|
||||||
|
"links", {"link_doctype": "HD Customer", "link_name": customer.name}
|
||||||
|
)
|
||||||
|
contact.save(ignore_permissions=True)
|
||||||
|
else:
|
||||||
|
contact = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Contact",
|
||||||
|
"first_name": email.split("@")[0],
|
||||||
|
"email_ids": [{"email_id": email, "is_primary": 1}],
|
||||||
|
"links": [{"link_doctype": "HD Customer", "link_name": customer.name}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
contact.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
if not frappe.db.exists("User", contact.email_id):
|
||||||
|
invite_user(contact.name)
|
||||||
|
else:
|
||||||
|
base_url = frappe.utils.get_url() + "/helpdesk"
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=[contact.email_id],
|
||||||
|
subject="Welcome existing user to Helpdesk",
|
||||||
|
message=f"""
|
||||||
|
<h1>Hello,</h1>
|
||||||
|
<button>{base_url}</button>
|
||||||
|
""",
|
||||||
|
now=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"customer": customer.name, "contact": contact.name}
|
||||||
|
|
||||||
|
|
||||||
|
def invite_user(contact: str):
|
||||||
|
contact = frappe.get_doc("Contact", contact)
|
||||||
|
contact.check_permission()
|
||||||
|
|
||||||
|
if not contact.email_id:
|
||||||
|
frappe.throw(_("Please set Email Address"))
|
||||||
|
|
||||||
|
user = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "User",
|
||||||
|
"first_name": contact.first_name,
|
||||||
|
"last_name": contact.last_name,
|
||||||
|
"email": contact.email_id,
|
||||||
|
"user_type": "Website User",
|
||||||
|
"send_welcome_email": 0
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
contact.user = user.name
|
||||||
|
contact.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
send_welcome_mail_to_user(user)
|
||||||
|
return user.name
|
||||||
|
|
||||||
|
|
||||||
|
def send_welcome_mail_to_user(user):
|
||||||
|
from frappe.utils import get_url
|
||||||
|
from frappe.utils.user import get_user_fullname
|
||||||
|
|
||||||
|
link = user.reset_password()
|
||||||
|
|
||||||
|
frappe.cache.hset("redirect_after_login", user.name, "/helpdesk")
|
||||||
|
|
||||||
|
site_url = get_url()
|
||||||
|
subject = _("Welcome to Helpdesk")
|
||||||
|
|
||||||
|
created_by = get_user_fullname(frappe.session["user"])
|
||||||
|
if created_by == "Guest":
|
||||||
|
created_by = "Administrator"
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"first_name": user.first_name or user.last_name or "user",
|
||||||
|
"last_name": user.last_name,
|
||||||
|
"user": user.name,
|
||||||
|
"title": subject,
|
||||||
|
"login_url": get_url(),
|
||||||
|
"created_by": created_by,
|
||||||
|
"site_url": site_url,
|
||||||
|
"link": link
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=[user.email],
|
||||||
|
subject=subject,
|
||||||
|
template="helpdesk_invitation",
|
||||||
|
args=args,
|
||||||
|
now=True,
|
||||||
|
)
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
|
# On IntegrationTestCase, the doctype test records and all
|
||||||
|
# link-field test record dependencies are recursively loaded
|
||||||
|
# Use these module variables to add/remove to/from that list
|
||||||
|
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||||
|
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrationTestHelpdeskCRMSettings(IntegrationTestCase):
|
||||||
|
"""
|
||||||
|
Integration tests for HelpdeskCRMSettings.
|
||||||
|
Use this class for testing interactions between multiple components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
@ -192,7 +192,7 @@ def add_default_fields_layout(force=False):
|
|||||||
},
|
},
|
||||||
"CRM Deal-Data Fields": {
|
"CRM Deal-Data Fields": {
|
||||||
"doctype": "CRM Deal",
|
"doctype": "CRM Deal",
|
||||||
"layout": '[{"label": "Details", "name": "details_section", "opened": true, "columns": [{"name": "column_z9XL", "fields": ["organization", "annual_revenue", "next_step"]}, {"name": "column_gM4w", "fields": ["website", "close_date", "deal_owner"]}, {"name": "column_gWmE", "fields": ["territory", "probability"]}]}]',
|
"layout": '[{"name":"first_tab","sections":[{"label":"Details","name":"details_section","opened":true,"columns":[{"name":"column_z9XL","fields":["organization","annual_revenue","next_step"]},{"name":"column_gM4w","fields":["website","closed_date","deal_owner"]},{"name":"column_gWmE","fields":["territory","probability"]}]},{"label":"Products","name":"section_jHhQ","opened":true,"columns":[{"name":"column_xiNF","fields":["products"]}],"editingLabel":false,"hideLabel":true},{"label":"New Section","name":"section_WNOQ","opened":true,"columns":[{"name":"column_ziBW","fields":["total"]},{"label":"","name":"column_wuwA","fields":["net_total"]}],"hideBorder":true,"hideLabel":true}]}]',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1240
crm/locale/ar.po
1240
crm/locale/ar.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/bs.po
1240
crm/locale/bs.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/cs.po
1240
crm/locale/cs.po
File diff suppressed because it is too large
Load Diff
5836
crm/locale/da.po
Normal file
5836
crm/locale/da.po
Normal file
File diff suppressed because it is too large
Load Diff
1308
crm/locale/de.po
1308
crm/locale/de.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/eo.po
1240
crm/locale/eo.po
File diff suppressed because it is too large
Load Diff
1246
crm/locale/es.po
1246
crm/locale/es.po
File diff suppressed because it is too large
Load Diff
1286
crm/locale/fa.po
1286
crm/locale/fa.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/fr.po
1240
crm/locale/fr.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/hr.po
1240
crm/locale/hr.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/hu.po
1240
crm/locale/hu.po
File diff suppressed because it is too large
Load Diff
1288
crm/locale/id.po
1288
crm/locale/id.po
File diff suppressed because it is too large
Load Diff
1666
crm/locale/it.po
1666
crm/locale/it.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/main.pot
1240
crm/locale/main.pot
File diff suppressed because it is too large
Load Diff
5836
crm/locale/nb.po
Normal file
5836
crm/locale/nb.po
Normal file
File diff suppressed because it is too large
Load Diff
1240
crm/locale/nl.po
1240
crm/locale/nl.po
File diff suppressed because it is too large
Load Diff
1242
crm/locale/pl.po
1242
crm/locale/pl.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/pt.po
1240
crm/locale/pt.po
File diff suppressed because it is too large
Load Diff
1250
crm/locale/pt_BR.po
1250
crm/locale/pt_BR.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/ru.po
1240
crm/locale/ru.po
File diff suppressed because it is too large
Load Diff
1284
crm/locale/sr.po
1284
crm/locale/sr.po
File diff suppressed because it is too large
Load Diff
1296
crm/locale/sr_CS.po
1296
crm/locale/sr_CS.po
File diff suppressed because it is too large
Load Diff
1242
crm/locale/sv.po
1242
crm/locale/sv.po
File diff suppressed because it is too large
Load Diff
1284
crm/locale/th.po
1284
crm/locale/th.po
File diff suppressed because it is too large
Load Diff
1244
crm/locale/tr.po
1244
crm/locale/tr.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/vi.po
1240
crm/locale/vi.po
File diff suppressed because it is too large
Load Diff
1240
crm/locale/zh.po
1240
crm/locale/zh.po
File diff suppressed because it is too large
Load Diff
@ -14,4 +14,5 @@ crm.patches.v1_0.update_layouts_to_new_format
|
|||||||
crm.patches.v1_0.move_twilio_agent_to_telephony_agent
|
crm.patches.v1_0.move_twilio_agent_to_telephony_agent
|
||||||
crm.patches.v1_0.create_default_scripts # 13-06-2025
|
crm.patches.v1_0.create_default_scripts # 13-06-2025
|
||||||
crm.patches.v1_0.update_deal_status_probabilities
|
crm.patches.v1_0.update_deal_status_probabilities
|
||||||
crm.patches.v1_0.update_deal_status_type
|
crm.patches.v1_0.update_deal_status_type
|
||||||
|
crm.patches.v1_0.create_default_lost_reasons
|
||||||
5
crm/patches/v1_0/create_default_lost_reasons.py
Normal file
5
crm/patches/v1_0/create_default_lost_reasons.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from crm.install import add_default_lost_reasons
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
add_default_lost_reasons()
|
||||||
24
crm/templates/emails/helpdesk_invitation.html
Normal file
24
crm/templates/emails/helpdesk_invitation.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<p>
|
||||||
|
{{_("Hello")}} {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},
|
||||||
|
</p>
|
||||||
|
{% set site_link = "<a href='" + site_url + "'>" + site_url + "</a>" %}
|
||||||
|
<p>{{_("A new account has been created for you at {0}").format(site_link)}}.</p>
|
||||||
|
<p>{{_("Your login id is")}}: <b>{{ user }}</b>
|
||||||
|
<p>{{_("Click on the link below to complete your registration and set a new password")}}.</p>
|
||||||
|
|
||||||
|
<p style="margin: 15px 0px;">
|
||||||
|
<a href="{{ link }}" rel="nofollow" class="btn btn-primary">{{ _("Complete Registration") }}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if created_by != "Administrator" %}
|
||||||
|
<br>
|
||||||
|
<p style="margin-top: 15px">
|
||||||
|
{{_("Thanks")}},<br>
|
||||||
|
{{ created_by }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{_("You can also copy-paste following link in your browser")}}<br>
|
||||||
|
<a href="{{ link }}">{{ link }}</a>
|
||||||
|
</p>
|
||||||
@ -1 +1 @@
|
|||||||
Subproject commit b295b54aaa3a2a22f455df819d87433dc6b9ff6a
|
Subproject commit 136f2715c2bd22b7390a2a02f1849a147d16b191
|
||||||
7
frontend/components.d.ts
vendored
7
frontend/components.d.ts
vendored
@ -142,6 +142,8 @@ declare module 'vue' {
|
|||||||
GroupBy: typeof import('./src/components/GroupBy.vue')['default']
|
GroupBy: typeof import('./src/components/GroupBy.vue')['default']
|
||||||
GroupByIcon: typeof import('./src/components/Icons/GroupByIcon.vue')['default']
|
GroupByIcon: typeof import('./src/components/Icons/GroupByIcon.vue')['default']
|
||||||
HeartIcon: typeof import('./src/components/Icons/HeartIcon.vue')['default']
|
HeartIcon: typeof import('./src/components/Icons/HeartIcon.vue')['default']
|
||||||
|
HelpdeskIcon: typeof import('./src/components/Icons/HelpdeskIcon.vue')['default']
|
||||||
|
HelpdeskSettings: typeof import('./src/components/Settings/HelpdeskSettings.vue')['default']
|
||||||
HelpIcon: typeof import('./src/components/Icons/HelpIcon.vue')['default']
|
HelpIcon: typeof import('./src/components/Icons/HelpIcon.vue')['default']
|
||||||
HomeActions: typeof import('./src/components/Settings/General/HomeActions.vue')['default']
|
HomeActions: typeof import('./src/components/Settings/General/HomeActions.vue')['default']
|
||||||
Icon: typeof import('./src/components/Icon.vue')['default']
|
Icon: typeof import('./src/components/Icon.vue')['default']
|
||||||
@ -170,10 +172,6 @@ declare module 'vue' {
|
|||||||
LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default']
|
LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default']
|
||||||
LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default']
|
LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default']
|
||||||
LucideCalendar: typeof import('~icons/lucide/calendar')['default']
|
LucideCalendar: typeof import('~icons/lucide/calendar')['default']
|
||||||
LucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
|
||||||
LucidePenLine: typeof import('~icons/lucide/pen-line')['default']
|
|
||||||
LucideRefreshCcw: typeof import('~icons/lucide/refresh-ccw')['default']
|
|
||||||
LucideX: typeof import('~icons/lucide/x')['default']
|
|
||||||
MarkAsDoneIcon: typeof import('./src/components/Icons/MarkAsDoneIcon.vue')['default']
|
MarkAsDoneIcon: typeof import('./src/components/Icons/MarkAsDoneIcon.vue')['default']
|
||||||
MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.vue')['default']
|
MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.vue')['default']
|
||||||
MenuIcon: typeof import('./src/components/Icons/MenuIcon.vue')['default']
|
MenuIcon: typeof import('./src/components/Icons/MenuIcon.vue')['default']
|
||||||
@ -188,7 +186,6 @@ declare module 'vue' {
|
|||||||
MultiSelectEmailInput: typeof import('./src/components/Controls/MultiSelectEmailInput.vue')['default']
|
MultiSelectEmailInput: typeof import('./src/components/Controls/MultiSelectEmailInput.vue')['default']
|
||||||
MultiSelectUserInput: typeof import('./src/components/Controls/MultiSelectUserInput.vue')['default']
|
MultiSelectUserInput: typeof import('./src/components/Controls/MultiSelectUserInput.vue')['default']
|
||||||
MuteIcon: typeof import('./src/components/Icons/MuteIcon.vue')['default']
|
MuteIcon: typeof import('./src/components/Icons/MuteIcon.vue')['default']
|
||||||
NestedPopover: typeof import('./src/components/NestedPopover.vue')['default']
|
|
||||||
NewEmailTemplate: typeof import('./src/components/Settings/EmailTemplate/NewEmailTemplate.vue')['default']
|
NewEmailTemplate: typeof import('./src/components/Settings/EmailTemplate/NewEmailTemplate.vue')['default']
|
||||||
NoteArea: typeof import('./src/components/Activities/NoteArea.vue')['default']
|
NoteArea: typeof import('./src/components/Activities/NoteArea.vue')['default']
|
||||||
NoteIcon: typeof import('./src/components/Icons/NoteIcon.vue')['default']
|
NoteIcon: typeof import('./src/components/Icons/NoteIcon.vue')['default']
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
"@tiptap/extension-paragraph": "^2.12.0",
|
"@tiptap/extension-paragraph": "^2.12.0",
|
||||||
"@twilio/voice-sdk": "^2.10.2",
|
"@twilio/voice-sdk": "^2.10.2",
|
||||||
"@vueuse/integrations": "^10.3.0",
|
"@vueuse/integrations": "^10.3.0",
|
||||||
"frappe-ui": "^0.1.171",
|
"frappe-ui": "^0.1.189",
|
||||||
"gemoji": "^8.1.0",
|
"gemoji": "^8.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mime": "^4.0.1",
|
"mime": "^4.0.1",
|
||||||
|
|||||||
@ -238,12 +238,9 @@
|
|||||||
<Button
|
<Button
|
||||||
class="!size-4"
|
class="!size-4"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:icon="SelectIcon"
|
||||||
@click="activity.show_others = !activity.show_others"
|
@click="activity.show_others = !activity.show_others"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<SelectIcon />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@ -9,23 +9,17 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="title == 'Emails'"
|
v-if="title == 'Emails'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
:label="__('New Email')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="emailBox.show = true"
|
@click="emailBox.show = true"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
<span>{{ __('New Email') }}</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-else-if="title == 'Comments'"
|
v-else-if="title == 'Comments'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
:label="__('New Comment')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="emailBox.showComment = true"
|
@click="emailBox.showComment = true"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
<span>{{ __('New Comment') }}</span>
|
|
||||||
</Button>
|
|
||||||
<MultiActionButton
|
<MultiActionButton
|
||||||
v-else-if="title == 'Calls'"
|
v-else-if="title == 'Calls'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@ -34,59 +28,45 @@
|
|||||||
<Button
|
<Button
|
||||||
v-else-if="title == 'Notes'"
|
v-else-if="title == 'Notes'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
:label="__('New Note')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="modalRef.showNote()"
|
@click="modalRef.showNote()"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
<span>{{ __('New Note') }}</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-else-if="title == 'Tasks'"
|
v-else-if="title == 'Tasks'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
:label="__('New Task')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="modalRef.showTask()"
|
@click="modalRef.showTask()"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
<span>{{ __('New Task') }}</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-else-if="title == 'Attachments'"
|
v-else-if="title == 'Attachments'"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
:label="__('Upload Attachment')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="showFilesUploader = true"
|
@click="showFilesUploader = true"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
<span>{{ __('Upload Attachment') }}</span>
|
|
||||||
</Button>
|
|
||||||
<div class="flex gap-2 shrink-0" v-else-if="title == 'WhatsApp'">
|
<div class="flex gap-2 shrink-0" v-else-if="title == 'WhatsApp'">
|
||||||
<Button
|
<Button
|
||||||
:label="__('Send Template')"
|
:label="__('Send Template')"
|
||||||
@click="showWhatsappTemplates = true"
|
@click="showWhatsappTemplates = true"
|
||||||
/>
|
/>
|
||||||
<Button variant="solid" @click="whatsappBox.show()">
|
<Button
|
||||||
<template #prefix>
|
variant="solid"
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
:label="__('New Message')"
|
||||||
</template>
|
iconLeft="plus"
|
||||||
<span>{{ __('New Message') }}</span>
|
@click="whatsappBox.show()"
|
||||||
</Button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Dropdown v-else :options="defaultActions" @click.stop>
|
<Dropdown v-else :options="defaultActions" @click.stop>
|
||||||
<template v-slot="{ open }">
|
<template v-slot="{ open }">
|
||||||
<Button variant="solid" class="flex items-center gap-1">
|
<Button
|
||||||
<template #prefix>
|
variant="solid"
|
||||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
class="flex items-center gap-1"
|
||||||
</template>
|
:label="__('New')"
|
||||||
<span>{{ __('New') }}</span>
|
iconLeft="plus"
|
||||||
<template #suffix>
|
:iconRight="open ? 'chevron-up' : 'chevron-down'"
|
||||||
<FeatherIcon
|
/>
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
|
||||||
class="h-4 w-4"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -38,42 +38,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<Tooltip
|
<Button
|
||||||
:text="
|
:tooltip="
|
||||||
attachment.is_private ? __('Make public') : __('Make private')
|
attachment.is_private ? __('Make public') : __('Make private')
|
||||||
"
|
"
|
||||||
|
class="!size-5"
|
||||||
|
@click.stop="
|
||||||
|
togglePrivate(attachment.name, attachment.is_private)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<div>
|
<template #icon>
|
||||||
<Button
|
<FeatherIcon
|
||||||
class="!size-5"
|
:name="attachment.is_private ? 'lock' : 'unlock'"
|
||||||
@click.stop="
|
class="size-3 text-ink-gray-7"
|
||||||
togglePrivate(attachment.name, attachment.is_private)
|
/>
|
||||||
"
|
</template>
|
||||||
>
|
</Button>
|
||||||
<template #icon>
|
<Button
|
||||||
<FeatherIcon
|
:tooltip="__('Delete attachment')"
|
||||||
:name="attachment.is_private ? 'lock' : 'unlock'"
|
class="!size-5"
|
||||||
class="size-3 text-ink-gray-7"
|
@click.stop="() => deleteAttachment(attachment.name)"
|
||||||
/>
|
>
|
||||||
</template>
|
<template #icon>
|
||||||
</Button>
|
<FeatherIcon name="trash-2" class="size-3 text-ink-gray-7" />
|
||||||
</div>
|
</template>
|
||||||
</Tooltip>
|
</Button>
|
||||||
<Tooltip :text="__('Delete attachment')">
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
class="!size-5"
|
|
||||||
@click.stop="() => deleteAttachment(attachment.name)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<FeatherIcon
|
|
||||||
name="trash-2"
|
|
||||||
class="size-3 text-ink-gray-7"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full text-sm text-ink-gray-5">
|
<div class="w-full text-sm text-ink-gray-5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Button variant="ghost" @click="playPause">
|
<Button
|
||||||
<template #icon>
|
variant="ghost"
|
||||||
<PlayIcon v-if="isPaused" class="size-4 text-ink-gray-5" />
|
class="text-ink-gray-5"
|
||||||
<PauseIcon v-else class="size-4 text-ink-gray-5" />
|
:icon="isPaused ? PlayIcon : PauseIcon"
|
||||||
</template>
|
@click="playPause"
|
||||||
</Button>
|
/>
|
||||||
<div class="flex gap-2 items-center justify-between flex-1">
|
<div class="flex gap-2 items-center justify-between flex-1">
|
||||||
<input
|
<input
|
||||||
class="w-full slider !h-[0.5] bg-surface-gray-3 [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb:hover]:outline [&::-webkit-slider-thumb:hover]:outline-[0.5px]"
|
class="w-full slider !h-[0.5] bg-surface-gray-3 [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb:hover]:outline [&::-webkit-slider-thumb:hover]:outline-[0.5px]"
|
||||||
@ -61,11 +61,11 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Dropdown :options="options">
|
<Dropdown :options="options">
|
||||||
<Button variant="ghost" @click="showPlaybackSpeed = false">
|
<Button
|
||||||
<template #icon>
|
icon="more-horizontal"
|
||||||
<FeatherIcon class="size-4" name="more-horizontal" />
|
variant="ghost"
|
||||||
</template>
|
@click="showPlaybackSpeed = false"
|
||||||
</Button>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,12 +14,10 @@
|
|||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<Button
|
<Button
|
||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="showDataFieldsModal = true"
|
@click="showDataFieldsModal = true"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<EditIcon />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
label="Save"
|
label="Save"
|
||||||
:disabled="!document.isDirty"
|
:disabled="!document.isDirty"
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
<div
|
<div
|
||||||
class="cursor-pointer flex flex-col rounded-md shadow bg-surface-cards px-3 py-1.5 text-base transition-all duration-300 ease-in-out"
|
class="cursor-pointer flex flex-col rounded-md shadow bg-surface-cards px-3 py-1.5 text-base transition-all duration-300 ease-in-out"
|
||||||
>
|
>
|
||||||
<div class="-mb-0.5 flex items-center justify-between gap-2 truncate text-ink-gray-9">
|
<div
|
||||||
|
class="-mb-0.5 flex items-center justify-between gap-2 truncate text-ink-gray-9"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2 truncate">
|
<div class="flex items-center gap-2 truncate">
|
||||||
<span>{{ activity.data.sender_full_name }}</span>
|
<span>{{ activity.data.sender_full_name }}</span>
|
||||||
<span class="sm:flex hidden text-sm text-ink-gray-5">
|
<span class="sm:flex hidden text-sm text-ink-gray-5">
|
||||||
@ -28,32 +30,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div class="flex gap-0.5">
|
<div class="flex gap-0.5">
|
||||||
<Tooltip :text="__('Reply')">
|
<Button
|
||||||
<div>
|
:tooltip="__('Reply')"
|
||||||
<Button
|
variant="ghost"
|
||||||
variant="ghost"
|
class="text-ink-gray-7"
|
||||||
class="text-ink-gray-7"
|
:icon="ReplyIcon"
|
||||||
@click="reply(activity.data)"
|
@click="reply(activity.data)"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<ReplyIcon />
|
:tooltip="__('Reply All')"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
:icon="ReplyAllIcon"
|
||||||
</div>
|
class="text-ink-gray-7"
|
||||||
</Tooltip>
|
@click="reply(activity.data, true)"
|
||||||
<Tooltip :text="__('Reply All')">
|
/>
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
class="text-ink-gray-7"
|
|
||||||
@click="reply(activity.data, true)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<ReplyAllIcon />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -41,13 +41,13 @@
|
|||||||
:options="taskStatusOptions(modalRef.updateTaskStatus, task)"
|
:options="taskStatusOptions(modalRef.updateTaskStatus, task)"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<Tooltip :text="__('Change Status')">
|
<Button
|
||||||
<div>
|
:tooltip="__('Change status')"
|
||||||
<Button variant="ghosted" class="hover:bg-surface-gray-4">
|
variant="ghosted"
|
||||||
<TaskStatusIcon :status="task.status" />
|
class="hover:bg-surface-gray-4"
|
||||||
</Button>
|
>
|
||||||
</div>
|
<TaskStatusIcon :status="task.status" />
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
:options="[
|
:options="[
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<NestedPopover>
|
<Popover placement="bottom-end">
|
||||||
<template #target>
|
<template #target="{ togglePopover }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center" @click="togglePopover">
|
||||||
<component
|
<component
|
||||||
v-if="assignees?.length"
|
v-if="assignees?.length"
|
||||||
:is="assignees?.length == 1 ? 'Button' : 'div'"
|
:is="assignees?.length == 1 ? 'Button' : 'div'"
|
||||||
@ -11,24 +11,23 @@
|
|||||||
<Button v-else :label="__('Assign to')" />
|
<Button v-else :label="__('Assign to')" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ open }">
|
<template #body="{ isOpen }">
|
||||||
<AssignToBody
|
<AssignToBody
|
||||||
v-show="open"
|
v-show="isOpen"
|
||||||
v-model="assignees"
|
v-model="assignees"
|
||||||
:docname="docname"
|
:docname="docname"
|
||||||
:doctype="doctype"
|
:doctype="doctype"
|
||||||
:open="open"
|
:open="isOpen"
|
||||||
:onUpdate="ownerField && saveAssignees"
|
:onUpdate="ownerField && saveAssignees"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</NestedPopover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
|
||||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||||
import AssignToBody from '@/components/AssignToBody.vue'
|
import AssignToBody from '@/components/AssignToBody.vue'
|
||||||
import { useDocument } from '@/data/document'
|
import { useDocument } from '@/data/document'
|
||||||
import { toast } from 'frappe-ui'
|
import { toast, Popover } from 'frappe-ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -25,23 +25,21 @@
|
|||||||
:key="assignee.name"
|
:key="assignee.name"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<div>
|
<div
|
||||||
<div
|
class="flex items-center text-sm p-0.5 text-ink-gray-6 border border-outline-gray-1 bg-surface-modal rounded-full cursor-pointer"
|
||||||
class="flex items-center text-sm p-0.5 text-ink-gray-6 border border-outline-gray-1 bg-surface-modal rounded-full cursor-pointer"
|
@click.stop
|
||||||
@click.stop
|
>
|
||||||
|
<UserAvatar :user="assignee.name" size="sm" />
|
||||||
|
<div class="ml-1">{{ getUser(assignee.name).full_name }}</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="rounded-full !size-4 m-1"
|
||||||
|
@click.stop="removeValue(assignee.name)"
|
||||||
>
|
>
|
||||||
<UserAvatar :user="assignee.name" size="sm" />
|
<template #icon>
|
||||||
<div class="ml-1">{{ getUser(assignee.name).full_name }}</div>
|
<FeatherIcon name="x" class="h-3 w-3 text-ink-gray-6" />
|
||||||
<Button
|
</template>
|
||||||
variant="ghost"
|
</Button>
|
||||||
class="rounded-full !size-4 m-1"
|
|
||||||
@click.stop="removeValue(assignee.name)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<FeatherIcon name="x" class="h-3 w-3 text-ink-gray-6" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +72,7 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
|||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { Tooltip, Switch, toast, createResource } from 'frappe-ui'
|
import { Tooltip, Switch, createResource } from 'frappe-ui'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -154,6 +152,7 @@ watch(
|
|||||||
updateAssignees()
|
updateAssignees()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
async function updateAssignees() {
|
async function updateAssignees() {
|
||||||
|
|||||||
@ -5,11 +5,9 @@
|
|||||||
:label="label"
|
:label="label"
|
||||||
theme="gray"
|
theme="gray"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
:iconLeft="getIcon()"
|
||||||
@click="toggleDialog()"
|
@click="toggleDialog()"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
|
||||||
<component :is="getIcon()" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<slot name="suffix" />
|
<slot name="suffix" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<NestedPopover>
|
<Popover placement="bottom-end">
|
||||||
<template #target>
|
<template #target="{ togglePopover }">
|
||||||
<Button :label="__('Columns')">
|
<Button :label="__('Columns')" @click="togglePopover">
|
||||||
<template v-if="hideLabel">
|
<template v-if="hideLabel">
|
||||||
<ColumnsIcon class="h-4" />
|
<ColumnsIcon class="h-4" />
|
||||||
</template>
|
</template>
|
||||||
@ -65,37 +65,28 @@
|
|||||||
<Button
|
<Button
|
||||||
class="w-full !justify-start !text-ink-gray-5"
|
class="w-full !justify-start !text-ink-gray-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Column')"
|
:label="__('Add Column')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
<Button
|
<Button
|
||||||
v-if="columnsUpdated"
|
v-if="columnsUpdated"
|
||||||
class="w-full !justify-start !text-ink-gray-5"
|
class="w-full !justify-start !text-ink-gray-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="reset(close)"
|
|
||||||
:label="__('Reset Changes')"
|
:label="__('Reset Changes')"
|
||||||
>
|
:iconLeft="ReloadIcon"
|
||||||
<template #prefix>
|
@click="reset(close)"
|
||||||
<ReloadIcon class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-if="!is_default"
|
v-if="!is_default"
|
||||||
class="w-full !justify-start !text-ink-gray-5"
|
class="w-full !justify-start !text-ink-gray-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="resetToDefault(close)"
|
|
||||||
:label="__('Reset to Default')"
|
:label="__('Reset to Default')"
|
||||||
>
|
:iconLeft="ReloadIcon"
|
||||||
<template #prefix>
|
@click="resetToDefault(close)"
|
||||||
<ReloadIcon class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@ -144,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NestedPopover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -152,9 +143,9 @@ import ColumnsIcon from '@/components/Icons/ColumnsIcon.vue'
|
|||||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||||
import DragIcon from '@/components/Icons/DragIcon.vue'
|
import DragIcon from '@/components/Icons/DragIcon.vue'
|
||||||
import ReloadIcon from '@/components/Icons/ReloadIcon.vue'
|
import ReloadIcon from '@/components/Icons/ReloadIcon.vue'
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
|
||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
import { isTouchScreenDevice } from '@/utils'
|
import { isTouchScreenDevice } from '@/utils'
|
||||||
|
import { Popover } from 'frappe-ui'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { watchOnce } from '@vueuse/core'
|
import { watchOnce } from '@vueuse/core'
|
||||||
@ -219,6 +210,7 @@ const fields = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function addColumn(c) {
|
function addColumn(c) {
|
||||||
|
if (!c) return
|
||||||
let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type)
|
let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type)
|
||||||
? 'right'
|
? 'right'
|
||||||
: 'left'
|
: 'left'
|
||||||
|
|||||||
@ -45,11 +45,12 @@
|
|||||||
v-slot="{ togglePopover }"
|
v-slot="{ togglePopover }"
|
||||||
@update:modelValue="() => appendEmoji()"
|
@update:modelValue="() => appendEmoji()"
|
||||||
>
|
>
|
||||||
<Button variant="ghost" @click="togglePopover()">
|
<Button
|
||||||
<template #icon>
|
:tooltip="__('Insert Emoji')"
|
||||||
<SmileIcon class="h-4" />
|
:icon="SmileIcon"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
@click="togglePopover()"
|
||||||
|
/>
|
||||||
</IconPicker>
|
</IconPicker>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
:upload-args="{
|
:upload-args="{
|
||||||
@ -61,14 +62,11 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ openFileSelector }">
|
<template #default="{ openFileSelector }">
|
||||||
<Button
|
<Button
|
||||||
theme="gray"
|
:tooltip="__('Attach a file')"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:icon="AttachmentIcon"
|
||||||
@click="openFileSelector()"
|
@click="openFileSelector()"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<AttachmentIcon class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</FileUploader>
|
</FileUploader>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,24 +8,18 @@
|
|||||||
showEmailBox ? '!bg-surface-gray-4 hover:!bg-surface-gray-3' : '',
|
showEmailBox ? '!bg-surface-gray-4 hover:!bg-surface-gray-3' : '',
|
||||||
]"
|
]"
|
||||||
:label="__('Reply')"
|
:label="__('Reply')"
|
||||||
|
:iconLeft="Email2Icon"
|
||||||
@click="toggleEmailBox()"
|
@click="toggleEmailBox()"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<Email2Icon class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
:label="__('Comment')"
|
:label="__('Comment')"
|
||||||
:class="[
|
:class="[
|
||||||
showCommentBox ? '!bg-surface-gray-4 hover:!bg-surface-gray-3' : '',
|
showCommentBox ? '!bg-surface-gray-4 hover:!bg-surface-gray-3' : '',
|
||||||
]"
|
]"
|
||||||
|
:iconLeft="CommentIcon"
|
||||||
@click="toggleCommentBox()"
|
@click="toggleCommentBox()"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<CommentIcon class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -52,16 +52,14 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-12">
|
<div class="flex items-center justify-center w-12">
|
||||||
<Button
|
<Button
|
||||||
class="flex w-full items-center justify-center rounded !bg-surface-gray-2 border-0"
|
:tooltip="__('Edit grid fields')"
|
||||||
|
class="rounded !bg-surface-gray-2 border-0 !text-ink-gray-5"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
icon="settings"
|
||||||
@click="showGridFieldsEditorModal = true"
|
@click="showGridFieldsEditorModal = true"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<FeatherIcon name="settings" class="size-4 text-ink-gray-7" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Rows -->
|
<!-- Rows -->
|
||||||
@ -72,6 +70,7 @@
|
|||||||
:delay="isTouchScreenDevice() ? 200 : 0"
|
:delay="isTouchScreenDevice() ? 200 : 0"
|
||||||
group="rows"
|
group="rows"
|
||||||
item-key="name"
|
item-key="name"
|
||||||
|
@end="reorder"
|
||||||
>
|
>
|
||||||
<template #item="{ element: row, index }">
|
<template #item="{ element: row, index }">
|
||||||
<div
|
<div
|
||||||
@ -277,16 +276,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="edit-row w-12">
|
<div class="edit-row flex items-center justify-center w-12">
|
||||||
<Button
|
<Button
|
||||||
class="flex w-full items-center justify-center rounded border-0"
|
:tooltip="__('Edit row')"
|
||||||
|
class="rounded border-0 !text-ink-gray-7"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="showRowList[index] = true"
|
@click="showRowList[index] = true"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<EditIcon class="text-ink-gray-7" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<GridRowModal
|
<GridRowModal
|
||||||
v-if="showRowList[index]"
|
v-if="showRowList[index]"
|
||||||
@ -350,7 +347,6 @@ import { usersStore } from '@/stores/users'
|
|||||||
import { getMeta } from '@/stores/meta'
|
import { getMeta } from '@/stores/meta'
|
||||||
import { createDocument } from '@/composables/document'
|
import { createDocument } from '@/composables/document'
|
||||||
import {
|
import {
|
||||||
FeatherIcon,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
DateTimePicker,
|
DateTimePicker,
|
||||||
@ -520,6 +516,13 @@ const deleteRows = () => {
|
|||||||
selectedRows.clear()
|
selectedRows.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reorder = () => {
|
||||||
|
rows.value.forEach((row, index) => {
|
||||||
|
row.idx = index + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function fieldChange(value, field, row) {
|
function fieldChange(value, field, row) {
|
||||||
triggerOnChange(field.fieldname, value, row)
|
triggerOnChange(field.fieldname, value, row)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,13 +54,10 @@
|
|||||||
<template #target="{ togglePopover }">
|
<template #target="{ togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
class="w-full mt-2"
|
class="w-full mt-2"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Field')"
|
:label="__('Add Field')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover()"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
<template #item-label="{ option }">
|
<template #item-label="{ option }">
|
||||||
<div class="flex flex-col gap-1 text-ink-gray-9">
|
<div class="flex flex-col gap-1 text-ink-gray-9">
|
||||||
@ -75,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex items-center gap-2 justify-end">
|
||||||
<Button
|
<Button
|
||||||
v-if="dirty"
|
v-if="dirty"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
|||||||
@ -11,19 +11,18 @@
|
|||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
v-if="isManager()"
|
v-if="isManager()"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openGridRowFieldsModal"
|
@click="openGridRowFieldsModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
icon="x"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
class="w-7"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
@click="show = false"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -48,24 +48,18 @@
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full !justify-start"
|
class="w-full !justify-start"
|
||||||
:label="__('Create New')"
|
:label="__('Create New')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="() => attrs.onCreate(value, close)"
|
@click="() => attrs.onCreate(value, close)"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full !justify-start"
|
class="w-full !justify-start"
|
||||||
:label="__('Clear')"
|
:label="__('Clear')"
|
||||||
|
iconLeft="x"
|
||||||
@click="() => clearValue(close)"
|
@click="() => clearValue(close)"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="x" class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
|
|||||||
@ -18,14 +18,10 @@
|
|||||||
:key="g.label"
|
:key="g.label"
|
||||||
>
|
>
|
||||||
<Dropdown :options="g.action" v-slot="{ open }">
|
<Dropdown :options="g.action" v-slot="{ open }">
|
||||||
<Button :label="g.label">
|
<Button
|
||||||
<template #suffix>
|
:label="g.label"
|
||||||
<FeatherIcon
|
:iconRight="open ? 'chevron-up' : 'chevron-down'"
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
/>
|
||||||
class="h-4"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -19,53 +19,36 @@
|
|||||||
v-if="editMode"
|
v-if="editMode"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
:label="__('Save')"
|
:label="__('Save')"
|
||||||
size="sm"
|
|
||||||
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
||||||
@click="saveOption"
|
@click="saveOption"
|
||||||
/>
|
/>
|
||||||
<Tooltip text="Set As Primary" v-if="!isNew && !option.selected">
|
<Button
|
||||||
<div>
|
v-if="!isNew && !option.selected"
|
||||||
<Button
|
:tooltip="__('Set As Primary')"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
:icon="SuccessIcon"
|
||||||
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
||||||
@click="option.onClick"
|
@click="option.onClick"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<SuccessIcon />
|
v-if="!editMode"
|
||||||
</template>
|
:tooltip="__('Edit')"
|
||||||
</Button>
|
variant="ghost"
|
||||||
</div>
|
:icon="EditIcon"
|
||||||
</Tooltip>
|
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
||||||
<Tooltip v-if="!editMode" text="Edit">
|
@click="toggleEditMode"
|
||||||
<div>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
:tooltip="__('Delete')"
|
||||||
size="sm"
|
variant="ghost"
|
||||||
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
icon="x"
|
||||||
@click="toggleEditMode"
|
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
||||||
>
|
@click="() => option.onDelete(option, isNew)"
|
||||||
<template #icon>
|
/>
|
||||||
<EditIcon />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip text="Delete">
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
icon="x"
|
|
||||||
size="sm"
|
|
||||||
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
|
|
||||||
@click="() => option.onDelete(option, isNew)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="option.selected">
|
<div v-if="option.selected">
|
||||||
<FeatherIcon name="check" class="text-ink-gray-5 h-4 w-6" size="sm" />
|
<FeatherIcon name="check" class="text-ink-gray-5 h-4 w-6" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -123,11 +123,12 @@
|
|||||||
v-slot="{ togglePopover }"
|
v-slot="{ togglePopover }"
|
||||||
@update:modelValue="() => appendEmoji()"
|
@update:modelValue="() => appendEmoji()"
|
||||||
>
|
>
|
||||||
<Button variant="ghost" @click="togglePopover()">
|
<Button
|
||||||
<template #icon>
|
:tooltip="__('Insert Emoji')"
|
||||||
<SmileIcon class="h-4" />
|
:icon="SmileIcon"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
@click="togglePopover()"
|
||||||
|
/>
|
||||||
</IconPicker>
|
</IconPicker>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
:upload-args="{
|
:upload-args="{
|
||||||
@ -138,21 +139,20 @@
|
|||||||
@success="(f) => attachments.push(f)"
|
@success="(f) => attachments.push(f)"
|
||||||
>
|
>
|
||||||
<template #default="{ openFileSelector }">
|
<template #default="{ openFileSelector }">
|
||||||
<Button variant="ghost" @click="openFileSelector()">
|
<Button
|
||||||
<template #icon>
|
:tooltip="__('Attach a file')"
|
||||||
<AttachmentIcon class="h-4" />
|
:icon="AttachmentIcon"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
@click="openFileSelector()"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</FileUploader>
|
</FileUploader>
|
||||||
<Button
|
<Button
|
||||||
|
:tooltip="__('Insert Email Template')"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:icon="EmailTemplateIcon"
|
||||||
@click="showEmailTemplateSelectorModal = true"
|
@click="showEmailTemplateSelectorModal = true"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<EmailTemplateIcon class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
|
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
|
||||||
<Button v-bind="discardButtonProps || {}" :label="__('Discard')" />
|
<Button v-bind="discardButtonProps || {}" :label="__('Discard')" />
|
||||||
|
|||||||
@ -89,12 +89,9 @@
|
|||||||
v-if="data[field.fieldname] && field.edit"
|
v-if="data[field.fieldname] && field.edit"
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
:label="__('Edit')"
|
:label="__('Edit')"
|
||||||
|
:iconLeft="EditIcon"
|
||||||
@click="field.edit(data[field.fieldname])"
|
@click="field.edit(data[field.fieldname])"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<EditIcon class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TableMultiselectInput
|
<TableMultiselectInput
|
||||||
|
|||||||
@ -169,13 +169,10 @@
|
|||||||
<Button
|
<Button
|
||||||
class="w-full !h-8 !bg-surface-modal"
|
class="w-full !h-8 !bg-surface-modal"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Field')"
|
:label="__('Add Field')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover()"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #item-label="{ option }">
|
<template #item-label="{ option }">
|
||||||
@ -198,6 +195,7 @@
|
|||||||
class="w-full h-8"
|
class="w-full h-8"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
:label="__('Add Section')"
|
:label="__('Add Section')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="
|
@click="
|
||||||
tabs[tabIndex].sections.push({
|
tabs[tabIndex].sections.push({
|
||||||
label: __('New Section'),
|
label: __('New Section'),
|
||||||
@ -206,11 +204,7 @@
|
|||||||
columns: [{ name: 'column_' + getRandom(), fields: [] }],
|
columns: [{ name: 'column_' + getRandom(), fields: [] }],
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
filesUploaderArea?.showWebLink || filesUploaderArea?.showCamera
|
filesUploaderArea?.showWebLink || filesUploaderArea?.showCamera
|
||||||
"
|
"
|
||||||
:label="isMobileView ? __('Back') : __('Back to file upload')"
|
:label="isMobileView ? __('Back') : __('Back to file upload')"
|
||||||
|
iconLeft="arrow-left"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
filesUploaderArea.showWebLink = false
|
filesUploaderArea.showWebLink = false
|
||||||
@ -37,11 +38,7 @@
|
|||||||
filesUploaderArea.cameraImage = null
|
filesUploaderArea.cameraImage = null
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="arrow-left" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-if="
|
v-if="
|
||||||
filesUploaderArea?.showCamera && !filesUploaderArea?.cameraImage
|
filesUploaderArea?.showCamera && !filesUploaderArea?.cameraImage
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<NestedPopover>
|
<Popover placement="bottom-end">
|
||||||
<template #target>
|
<template #target="{ togglePopover, close }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Button
|
<Button
|
||||||
:label="__('Filter')"
|
:label="__('Filter')"
|
||||||
:class="filters?.size ? 'rounded-r-none' : ''"
|
:class="filters?.size ? 'rounded-r-none' : ''"
|
||||||
|
:iconLeft="FilterIcon"
|
||||||
|
@click="togglePopover"
|
||||||
>
|
>
|
||||||
<template #prefix><FilterIcon class="h-4" /></template>
|
|
||||||
<template v-if="filters?.size" #suffix>
|
<template v-if="filters?.size" #suffix>
|
||||||
<div
|
<div
|
||||||
class="flex h-5 w-5 items-center justify-center rounded-[5px] bg-surface-white pt-px text-xs font-medium text-ink-gray-8 shadow-sm"
|
class="flex h-5 w-5 items-center justify-center rounded-[5px] bg-surface-white pt-px text-xs font-medium text-ink-gray-8 shadow-sm"
|
||||||
@ -15,15 +16,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
|
<Button
|
||||||
<div>
|
v-if="filters?.size"
|
||||||
<Button
|
:tooltip="__('Clear all Filter')"
|
||||||
class="rounded-l-none border-l"
|
class="rounded-l-none border-l"
|
||||||
icon="x"
|
icon="x"
|
||||||
@click.stop="clearfilter(false)"
|
@click.stop="clearfilter(close)"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ close }">
|
<template #body="{ close }">
|
||||||
@ -134,13 +133,10 @@
|
|||||||
<Button
|
<Button
|
||||||
class="!text-ink-gray-5"
|
class="!text-ink-gray-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Filter')"
|
:label="__('Add Filter')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover()"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
<Button
|
<Button
|
||||||
@ -154,17 +150,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NestedPopover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
|
||||||
import FilterIcon from '@/components/Icons/FilterIcon.vue'
|
import FilterIcon from '@/components/Icons/FilterIcon.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
createResource,
|
createResource,
|
||||||
Tooltip,
|
Popover,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
DateTimePicker,
|
DateTimePicker,
|
||||||
DateRangePicker,
|
DateRangePicker,
|
||||||
@ -485,7 +480,7 @@ function removeFilter(index) {
|
|||||||
function clearfilter(close) {
|
function clearfilter(close) {
|
||||||
filters.value.clear()
|
filters.value.clear()
|
||||||
apply()
|
apply()
|
||||||
close && close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateValue(value, filter) {
|
function updateValue(value, filter) {
|
||||||
|
|||||||
@ -7,18 +7,10 @@
|
|||||||
? groupByValue?.label
|
? groupByValue?.label
|
||||||
: __('Group By: ') + groupByValue?.label
|
: __('Group By: ') + groupByValue?.label
|
||||||
"
|
"
|
||||||
|
:iconLeft="DetailsIcon"
|
||||||
|
:iconRight="isOpen ? 'chevron-up' : 'chevron-down'"
|
||||||
@click="togglePopover()"
|
@click="togglePopover()"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<DetailsIcon />
|
|
||||||
</template>
|
|
||||||
<template #suffix>
|
|
||||||
<FeatherIcon
|
|
||||||
:name="isOpen ? 'chevron-up' : 'chevron-down'"
|
|
||||||
class="h-4"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -69,7 +69,7 @@
|
|||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import Popover from '@/components/frappe-ui/Popover.vue'
|
import { Popover } from 'frappe-ui'
|
||||||
import { gemoji } from 'gemoji'
|
import { gemoji } from 'gemoji'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M1 5C1 2.79086 2.79086 1 5 1H13C15.2091 1 17 2.79086 17 5V13C17 15.2091 15.2091 17 13 17H5C2.79086 17 1 15.2091 1 13V5Z"
|
d="M11.6611 8.2289V9.77773H7.88672V11.9066H11.999V13.4545H6V8.2289H11.6611ZM11.9512 4.6V6.14883H6V4.6H11.9512Z"
|
||||||
stroke="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
<path
|
<rect x="1.5" y="1.5" width="15" height="15" rx="3.5" stroke="currentColor" />
|
||||||
fill-rule="evenodd"
|
</svg>
|
||||||
clip-rule="evenodd"
|
</template>
|
||||||
d="M11.7819 6.27142H11.5136H8.02453H6.28001V4.84002H11.7819V6.27142ZM8.02451 9.62623V11.5944H11.8267V13.0258H6.27999V8.19484H8.02451H11.5135V9.62623H8.02451Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|||||||
15
frontend/src/components/Icons/HelpdeskIcon.vue
Normal file
15
frontend/src/components/Icons/HelpdeskIcon.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect x="1.5" y="1.5" width="15" height="15" rx="3.5" stroke="currentColor" />
|
||||||
|
<path
|
||||||
|
d="M13.7928 8.0619V5H4.29999V6.39494H12.3621V7.72014C11.787 7.88056 11.37 8.39669 11.37 9.00349C11.37 9.61029 11.787 10.1194 12.3621 10.2799V11.6051L5.79999 11.6051V7.96425H4.29999V13H13.8V9.9381L12.9444 9.34525V8.66173L13.8 8.06888L13.7928 8.0619Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -3,11 +3,8 @@
|
|||||||
:label="__('Kanban Settings')"
|
:label="__('Kanban Settings')"
|
||||||
@click="showDialog = true"
|
@click="showDialog = true"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
>
|
:iconLeft="KanbanIcon"
|
||||||
<template #prefix>
|
/>
|
||||||
<KanbanIcon class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Dialog v-model="showDialog" :options="{ title: __('Kanban Settings') }">
|
<Dialog v-model="showDialog" :options="{ title: __('Kanban Settings') }">
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
<div>
|
<div>
|
||||||
@ -23,8 +20,8 @@
|
|||||||
<template #target="{ togglePopover }">
|
<template #target="{ togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
class="w-full !justify-start"
|
class="w-full !justify-start"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="columnField.label"
|
:label="columnField.label"
|
||||||
|
@click="togglePopover()"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
@ -80,13 +77,10 @@
|
|||||||
<template #target="{ togglePopover }">
|
<template #target="{ togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
class="w-full mt-2"
|
class="w-full mt-2"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Field')"
|
:label="__('Add Field')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover()"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
<template #item-label="{ option }">
|
<template #item-label="{ option }">
|
||||||
<div class="flex flex-col gap-1 text-ink-gray-9">
|
<div class="flex flex-col gap-1 text-ink-gray-9">
|
||||||
|
|||||||
@ -15,17 +15,18 @@
|
|||||||
>
|
>
|
||||||
<div class="flex gap-2 items-center group justify-between">
|
<div class="flex gap-2 items-center group justify-between">
|
||||||
<div class="flex items-center text-base">
|
<div class="flex items-center text-base">
|
||||||
<NestedPopover>
|
<Popover>
|
||||||
<template #target>
|
<template #target="{ togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="hover:!bg-surface-gray-2"
|
class="hover:!bg-surface-gray-2"
|
||||||
|
@click="togglePopover"
|
||||||
>
|
>
|
||||||
<IndicatorIcon :class="parseColor(column.column.color)" />
|
<IndicatorIcon :class="parseColor(column.column.color)" />
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ close }">
|
<template #body>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-3 px-3 py-2.5 min-w-40 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
|
class="flex flex-col gap-3 px-3 py-2.5 min-w-40 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
>
|
>
|
||||||
@ -48,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NestedPopover>
|
</Popover>
|
||||||
<div class="text-ink-gray-9">{{ column.column.name }}</div>
|
<div class="text-ink-gray-9">{{ column.column.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@ -153,13 +154,10 @@
|
|||||||
<template #target="{ togglePopover }">
|
<template #target="{ togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
class="w-full mt-2.5 mb-1 mr-5"
|
class="w-full mt-2.5 mb-1 mr-5"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Column')"
|
:label="__('Add Column')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover()"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
</div>
|
</div>
|
||||||
@ -167,11 +165,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
|
||||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||||
import { isTouchScreenDevice, colors, parseColor } from '@/utils'
|
import { isTouchScreenDevice, colors, parseColor } from '@/utils'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import { Dropdown } from 'frappe-ui'
|
import { Dropdown, Popover } from 'frappe-ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
class="relative flex h-full flex-col justify-between transition-all duration-300 ease-in-out"
|
class="relative flex h-full flex-col justify-between transition-all duration-300 ease-in-out"
|
||||||
:class="isSidebarCollapsed ? 'w-12' : 'w-[220px]'"
|
:class="isSidebarCollapsed ? 'w-12' : 'w-[220px]'"
|
||||||
>
|
>
|
||||||
<div>
|
<div class="p-2">
|
||||||
<UserDropdown class="p-2" :isCollapsed="isSidebarCollapsed" />
|
<UserDropdown :isCollapsed="isSidebarCollapsed" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-y-auto">
|
<div class="flex-1 overflow-y-auto">
|
||||||
<div class="mb-3 flex flex-col">
|
<div class="mb-3 flex flex-col">
|
||||||
@ -197,51 +197,50 @@ const isSidebarCollapsed = useStorage('isSidebarCollapsed', false)
|
|||||||
const isFCSite = ref(window.is_fc_site)
|
const isFCSite = ref(window.is_fc_site)
|
||||||
const isDemoSite = ref(window.is_demo_site)
|
const isDemoSite = ref(window.is_demo_site)
|
||||||
|
|
||||||
const allViews = computed(() => {
|
const links = [
|
||||||
const links = [
|
{
|
||||||
{
|
label: 'Dashboard',
|
||||||
label: 'Dashboard',
|
icon: LucideLayoutDashboard,
|
||||||
icon: LucideLayoutDashboard,
|
to: 'Dashboard',
|
||||||
to: 'Dashboard',
|
},
|
||||||
condition: () => isManager(),
|
{
|
||||||
},
|
label: 'Leads',
|
||||||
{
|
icon: LeadsIcon,
|
||||||
label: 'Leads',
|
to: 'Leads',
|
||||||
icon: LeadsIcon,
|
},
|
||||||
to: 'Leads',
|
{
|
||||||
},
|
label: 'Deals',
|
||||||
{
|
icon: DealsIcon,
|
||||||
label: 'Deals',
|
to: 'Deals',
|
||||||
icon: DealsIcon,
|
},
|
||||||
to: 'Deals',
|
{
|
||||||
},
|
label: 'Contacts',
|
||||||
{
|
icon: ContactsIcon,
|
||||||
label: 'Contacts',
|
to: 'Contacts',
|
||||||
icon: ContactsIcon,
|
},
|
||||||
to: 'Contacts',
|
{
|
||||||
},
|
label: 'Organizations',
|
||||||
{
|
icon: OrganizationsIcon,
|
||||||
label: 'Organizations',
|
to: 'Organizations',
|
||||||
icon: OrganizationsIcon,
|
},
|
||||||
to: 'Organizations',
|
{
|
||||||
},
|
label: 'Notes',
|
||||||
{
|
icon: NoteIcon,
|
||||||
label: 'Notes',
|
to: 'Notes',
|
||||||
icon: NoteIcon,
|
},
|
||||||
to: 'Notes',
|
{
|
||||||
},
|
label: 'Tasks',
|
||||||
{
|
icon: TaskIcon,
|
||||||
label: 'Tasks',
|
to: 'Tasks',
|
||||||
icon: TaskIcon,
|
},
|
||||||
to: 'Tasks',
|
{
|
||||||
},
|
label: 'Call Logs',
|
||||||
{
|
icon: PhoneIcon,
|
||||||
label: 'Call Logs',
|
to: 'Call Logs',
|
||||||
icon: PhoneIcon,
|
},
|
||||||
to: 'Call Logs',
|
]
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
const allViews = computed(() => {
|
||||||
let _views = [
|
let _views = [
|
||||||
{
|
{
|
||||||
name: 'All Views',
|
name: 'All Views',
|
||||||
|
|||||||
@ -11,19 +11,18 @@
|
|||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:icon="EditIcon"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
icon="x"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
class="w-7"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
@click="show = false"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tabs.data && _address.doc">
|
<div v-if="tabs.data && _address.doc">
|
||||||
|
|||||||
@ -36,18 +36,17 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="!isMobileView"
|
v-if="!isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:tooltip="__('Edit call log')"
|
||||||
|
:icon="EditIcon"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
@click="openCallLogModal"
|
@click="openCallLogModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
icon="x"
|
||||||
</template>
|
variant="ghost"
|
||||||
</Button>
|
class="w-7"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
@click="show = false"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3.5">
|
<div class="flex flex-col gap-3.5">
|
||||||
|
|||||||
@ -13,18 +13,17 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
variant="ghost"
|
||||||
</template>
|
class="w-7"
|
||||||
</Button>
|
@click="show = false"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
icon="x"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tabs.data">
|
<div v-if="tabs.data">
|
||||||
@ -37,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 pt-4 pb-7 sm:px-6">
|
<div class="px-4 pt-4 pb-7 sm:px-6">
|
||||||
<div class="space-y-2">
|
<div class="flex justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
class="w-full"
|
class="w-full"
|
||||||
v-for="action in dialogOptions.actions"
|
v-for="action in dialogOptions.actions"
|
||||||
@ -61,7 +60,7 @@ import { showQuickEntryModal, quickEntryProps } from '@/composables/modals'
|
|||||||
import { getRandom } from '@/utils'
|
import { getRandom } from '@/utils'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { useDocument } from '@/data/document'
|
import { useDocument } from '@/data/document'
|
||||||
import { FeatherIcon, createResource, ErrorMessage, Badge } from 'frappe-ui'
|
import { createResource, ErrorMessage, Badge } from 'frappe-ui'
|
||||||
import { ref, nextTick, computed, onMounted } from 'vue'
|
import { ref, nextTick, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -13,17 +13,16 @@
|
|||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
variant="ghost"
|
||||||
</template>
|
class="w-7"
|
||||||
</Button>
|
@click="show = false"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
icon="x"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FieldLayout
|
<FieldLayout
|
||||||
@ -90,12 +89,16 @@ const { document: _contact, triggerOnBeforeCreate } = useDocument('Contact')
|
|||||||
|
|
||||||
async function createContact() {
|
async function createContact() {
|
||||||
if (_contact.doc.email_id) {
|
if (_contact.doc.email_id) {
|
||||||
_contact.doc.email_ids = [{ email_id: _contact.doc.email_id, is_primary: 1 }]
|
_contact.doc.email_ids = [
|
||||||
|
{ email_id: _contact.doc.email_id, is_primary: 1 },
|
||||||
|
]
|
||||||
delete _contact.doc.email_id
|
delete _contact.doc.email_id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_contact.doc.mobile_no) {
|
if (_contact.doc.mobile_no) {
|
||||||
_contact.doc.phone_nos = [{ phone: _contact.doc.mobile_no, is_primary_mobile_no: 1 }]
|
_contact.doc.phone_nos = [
|
||||||
|
{ phone: _contact.doc.mobile_no, is_primary_mobile_no: 1 },
|
||||||
|
]
|
||||||
delete _contact.doc.mobile_no
|
delete _contact.doc.mobile_no
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog v-model="show" :options="{ size: 'xl' }">
|
||||||
v-model="show"
|
|
||||||
:options="{
|
|
||||||
size: 'xl',
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: __('Convert'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick: convertToDeal,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #body-header>
|
<template #body-header>
|
||||||
<div class="mb-6 flex items-center justify-between">
|
<div class="mb-6 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -23,12 +11,10 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
:tooltip="__('Edit deal\'s mandatory fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<EditIcon class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button icon="x" variant="ghost" @click="show = false" />
|
<Button icon="x" variant="ghost" @click="show = false" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -92,6 +78,11 @@
|
|||||||
/>
|
/>
|
||||||
<ErrorMessage class="mt-4" :message="error" />
|
<ErrorMessage class="mt-4" :message="error" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<Button :label="__('Convert')" variant="solid" @click="convertToDeal" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@ -13,17 +13,16 @@
|
|||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
variant="ghost"
|
||||||
</template>
|
class="w-7"
|
||||||
</Button>
|
icon="x"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
@click="show = false"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tabs.data">
|
<div v-if="tabs.data">
|
||||||
|
|||||||
@ -13,17 +13,16 @@
|
|||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
variant="ghost"
|
||||||
</template>
|
class="w-7"
|
||||||
</Button>
|
icon="x"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
@click="show = false"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -13,17 +13,16 @@
|
|||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
variant="ghost"
|
||||||
</template>
|
class="w-7"
|
||||||
</Button>
|
@click="show = false"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
icon="x"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,45 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="show" :options="{
|
<Dialog v-model="show" :options="{ size: 'xl' }">
|
||||||
size: 'xl',
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: editMode ? __('Update') : __('Create'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick: () => updateNote(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}">
|
|
||||||
<template #body-title>
|
<template #body-title>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||||
{{ editMode ? __('Edit Note') : __('Create Note') }}
|
{{ editMode ? __('Edit Note') : __('Create Note') }}
|
||||||
</h3>
|
</h3>
|
||||||
<Button v-if="_note?.reference_docname" size="sm" :label="_note.reference_doctype == 'CRM Deal'
|
<Button
|
||||||
? __('Open Deal')
|
v-if="_note?.reference_docname"
|
||||||
: __('Open Lead')
|
size="sm"
|
||||||
" @click="redirect()">
|
:label="
|
||||||
<template #suffix>
|
_note.reference_doctype == 'CRM Deal'
|
||||||
<ArrowUpRightIcon class="w-4 h-4" />
|
? __('Open Deal')
|
||||||
</template>
|
: __('Open Lead')
|
||||||
</Button>
|
"
|
||||||
|
:iconRight="ArrowUpRightIcon"
|
||||||
|
@click="redirect()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<FormControl ref="title" :label="__('Title')" v-model="_note.title" :placeholder="__('Call with John Doe')"
|
<FormControl
|
||||||
required />
|
ref="title"
|
||||||
|
:label="__('Title')"
|
||||||
|
v-model="_note.title"
|
||||||
|
:placeholder="__('Call with John Doe')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-1.5 text-xs text-ink-gray-5">{{ __('Content') }}</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"
|
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>
|
</div>
|
||||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<Button
|
||||||
|
:label="editMode ? __('Update') : __('Create')"
|
||||||
|
variant="solid"
|
||||||
|
@click="updateNote"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -92,21 +107,25 @@ async function updateNote() {
|
|||||||
emit('after', d)
|
emit('after', d)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let d = await call('frappe.client.insert', {
|
let d = await call(
|
||||||
doc: {
|
'frappe.client.insert',
|
||||||
doctype: 'FCRM Note',
|
{
|
||||||
title: _note.value.title,
|
doc: {
|
||||||
content: _note.value.content,
|
doctype: 'FCRM Note',
|
||||||
reference_doctype: props.doctype,
|
title: _note.value.title,
|
||||||
reference_docname: props.doc || '',
|
content: _note.value.content,
|
||||||
|
reference_doctype: props.doctype,
|
||||||
|
reference_docname: props.doc || '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
if (err.error.exc_type == 'MandatoryError') {
|
if (err.error.exc_type == 'MandatoryError') {
|
||||||
error.value = "Title is mandatory"
|
error.value = 'Title is mandatory'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if (d.name) {
|
if (d.name) {
|
||||||
updateOnboardingStep('create_first_note')
|
updateOnboardingStep('create_first_note')
|
||||||
capture('note_created')
|
capture('note_created')
|
||||||
|
|||||||
@ -13,17 +13,16 @@
|
|||||||
v-if="isManager() && !isMobileView"
|
v-if="isManager() && !isMobileView"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7"
|
class="w-7"
|
||||||
|
:tooltip="__('Edit fields layout')"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="openQuickEntryModal"
|
@click="openQuickEntryModal"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
<Button
|
||||||
<EditIcon />
|
variant="ghost"
|
||||||
</template>
|
class="w-7"
|
||||||
</Button>
|
@click="show = false"
|
||||||
<Button variant="ghost" class="w-7" @click="show = false">
|
icon="x"
|
||||||
<template #icon>
|
/>
|
||||||
<FeatherIcon name="x" class="size-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FieldLayout
|
<FieldLayout
|
||||||
@ -109,6 +108,7 @@ async function createOrganization() {
|
|||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
if (err.error.exc_type == 'ValidationError') {
|
if (err.error.exc_type == 'ValidationError') {
|
||||||
error.value = err.error?.messages?.[0]
|
error.value = err.error?.messages?.[0]
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,17 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog v-model="show" :options="{ size: 'xl' }">
|
||||||
v-model="show"
|
|
||||||
:options="{
|
|
||||||
size: 'xl',
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: editMode ? __('Update') : __('Create'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick: () => updateTask(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #body-title>
|
<template #body-title>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||||
@ -25,12 +13,9 @@
|
|||||||
? __('Open Deal')
|
? __('Open Deal')
|
||||||
: __('Open Lead')
|
: __('Open Lead')
|
||||||
"
|
"
|
||||||
|
:iconRight="ArrowUpRightIcon"
|
||||||
@click="redirect()"
|
@click="redirect()"
|
||||||
>
|
/>
|
||||||
<template #suffix>
|
|
||||||
<ArrowUpRightIcon class="w-4 h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
@ -93,13 +78,15 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</template>
|
</template>
|
||||||
</Link>
|
</Link>
|
||||||
<DateTimePicker
|
<div class="w-36">
|
||||||
class="datepicker w-36"
|
<DateTimePicker
|
||||||
v-model="_task.due_date"
|
class="datepicker"
|
||||||
:placeholder="__('01/04/2024 11:30 PM')"
|
v-model="_task.due_date"
|
||||||
:formatter="(date) => getFormat(date, '', true, true)"
|
:placeholder="__('01/04/2024 11:30 PM')"
|
||||||
input-class="border-none"
|
:formatter="(date) => getFormat(date, '', true, true)"
|
||||||
/>
|
input-class="border-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Dropdown :options="taskPriorityOptions(updateTaskPriority)">
|
<Dropdown :options="taskPriorityOptions(updateTaskPriority)">
|
||||||
<Button :label="_task.priority" class="justify-between w-full">
|
<Button :label="_task.priority" class="justify-between w-full">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@ -111,6 +98,15 @@
|
|||||||
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<Button
|
||||||
|
:label="editMode ? __('Update') : __('Create')"
|
||||||
|
variant="solid"
|
||||||
|
@click="updateTask"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -7,17 +7,6 @@
|
|||||||
: duplicateMode
|
: duplicateMode
|
||||||
? __('Duplicate View')
|
? __('Duplicate View')
|
||||||
: __('Create View'),
|
: __('Create View'),
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: editMode
|
|
||||||
? __('Save Changes')
|
|
||||||
: duplicateMode
|
|
||||||
? __('Duplicate')
|
|
||||||
: __('Create'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick: () => (editMode ? update() : create()),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
@ -42,6 +31,21 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
:label="
|
||||||
|
editMode
|
||||||
|
? __('Save Changes')
|
||||||
|
: duplicateMode
|
||||||
|
? __('Duplicate')
|
||||||
|
: __('Create')
|
||||||
|
"
|
||||||
|
@click="() => (editMode ? update() : create())"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -9,21 +9,9 @@
|
|||||||
$attrs.class,
|
$attrs.class,
|
||||||
showDropdown ? 'rounded-br-none rounded-tr-none' : '',
|
showDropdown ? 'rounded-br-none rounded-tr-none' : '',
|
||||||
]"
|
]"
|
||||||
|
:iconLeft="activeButton.icon"
|
||||||
@click="() => activeButton.onClick()"
|
@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
|
<Dropdown
|
||||||
v-if="showDropdown"
|
v-if="showDropdown"
|
||||||
:options="parsedOptions"
|
:options="parsedOptions"
|
||||||
@ -54,7 +42,6 @@ const showDropdown = ref(props.options?.length > 1)
|
|||||||
const activeButton = ref(props.options?.[0] || {})
|
const activeButton = ref(props.options?.[0] || {})
|
||||||
|
|
||||||
const parsedOptions = computed(() => {
|
const parsedOptions = computed(() => {
|
||||||
debugger
|
|
||||||
return (
|
return (
|
||||||
props.options?.map((option) => {
|
props.options?.map((option) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Popover v-slot="{ open }">
|
|
||||||
<PopoverButton
|
|
||||||
as="div"
|
|
||||||
ref="reference"
|
|
||||||
@click="updatePosition"
|
|
||||||
@focusin="updatePosition"
|
|
||||||
@keydown="updatePosition"
|
|
||||||
v-slot="{ open }"
|
|
||||||
>
|
|
||||||
<slot name="target" v-bind="{ open }" />
|
|
||||||
</PopoverButton>
|
|
||||||
<div v-show="open">
|
|
||||||
<PopoverPanel
|
|
||||||
v-slot="{ open, close }"
|
|
||||||
ref="popover"
|
|
||||||
static
|
|
||||||
class="z-[100]"
|
|
||||||
>
|
|
||||||
<slot name="body" v-bind="{ open, close }" />
|
|
||||||
</PopoverPanel>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
|
||||||
import { createPopper } from '@popperjs/core'
|
|
||||||
import { nextTick, ref, onBeforeUnmount } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
placement: {
|
|
||||||
type: String,
|
|
||||||
default: 'bottom-start',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const reference = ref(null)
|
|
||||||
const popover = ref(null)
|
|
||||||
|
|
||||||
let popper = ref(null)
|
|
||||||
|
|
||||||
function setupPopper() {
|
|
||||||
if (!popper.value) {
|
|
||||||
popper.value = createPopper(reference.value.el, popover.value.el, {
|
|
||||||
placement: props.placement,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
popper.value.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePosition() {
|
|
||||||
nextTick(() => setupPopper())
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
popper.value?.destroy()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@ -16,24 +16,18 @@
|
|||||||
>
|
>
|
||||||
<div class="text-base font-medium">{{ __('Notifications') }}</div>
|
<div class="text-base font-medium">{{ __('Notifications') }}</div>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<Tooltip :text="__('Mark all as read')">
|
<Button
|
||||||
<div>
|
:tooltip="__('Mark all as read')"
|
||||||
<Button variant="ghost" @click="() => markAllAsRead()">
|
:icon="MarkAsDoneIcon"
|
||||||
<template #icon>
|
variant="ghost"
|
||||||
<MarkAsDoneIcon class="h-4 w-4" />
|
@click="markAllAsRead"
|
||||||
</template>
|
/>
|
||||||
</Button>
|
<Button
|
||||||
</div>
|
:tooltip="__('Close')"
|
||||||
</Tooltip>
|
icon="x"
|
||||||
<Tooltip :text="__('Close')">
|
variant="ghost"
|
||||||
<div>
|
@click="() => toggle()"
|
||||||
<Button variant="ghost" @click="() => toggle()">
|
/>
|
||||||
<template #icon>
|
|
||||||
<FeatherIcon name="x" class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -100,7 +94,6 @@ import { globalStore } from '@/stores/global'
|
|||||||
import { timeAgo } from '@/utils'
|
import { timeAgo } from '@/utils'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { Tooltip } from 'frappe-ui'
|
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
const { $socket } = globalStore()
|
const { $socket } = globalStore()
|
||||||
|
|||||||
@ -21,20 +21,13 @@
|
|||||||
<div v-else>{{ s.value }}</div>
|
<div v-else>{{ s.value }}</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Dropdown
|
<Dropdown v-if="s.type == 'Select'" :options="s.options">
|
||||||
class="form-control"
|
|
||||||
v-if="s.type == 'Select'"
|
|
||||||
:options="s.options"
|
|
||||||
>
|
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<Button :label="s.value">
|
<Button
|
||||||
<template #suffix>
|
class="form-control bg-surface-white hover:bg-surface-white"
|
||||||
<FeatherIcon
|
:label="s.value"
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
:iconRight="open ? 'chevron-up' : 'chevron-down'"
|
||||||
class="h-4"
|
/>
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -131,7 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ErrorMessage } from 'frappe-ui'
|
import { ErrorMessage, toast } from 'frappe-ui'
|
||||||
import { getSettings } from '@/stores/settings'
|
import { getSettings } from '@/stores/settings'
|
||||||
import { globalStore } from '@/stores/global'
|
import { globalStore } from '@/stores/global'
|
||||||
import { showSettings } from '@/composables/settings'
|
import { showSettings } from '@/composables/settings'
|
||||||
|
|||||||
@ -17,8 +17,8 @@
|
|||||||
:label="__('Update')"
|
:label="__('Update')"
|
||||||
icon-left="plus"
|
icon-left="plus"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
:disabled="!settings.isDirty"
|
:disabled="!document.isDirty"
|
||||||
:loading="settings.loading"
|
:loading="document.loading"
|
||||||
@click="updateSettings"
|
@click="updateSettings"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<!-- Fields -->
|
<!-- Fields -->
|
||||||
<div class="flex flex-1 flex-col gap-4 overflow-y-auto">
|
<div class="flex flex-1 flex-col gap-4 overflow-y-auto">
|
||||||
<Grid
|
<Grid
|
||||||
v-model="settings.doc.dropdown_items"
|
v-model="document.doc.dropdown_items"
|
||||||
doctype="CRM Dropdown Item"
|
doctype="CRM Dropdown Item"
|
||||||
parentDoctype="FCRM Settings"
|
parentDoctype="FCRM Settings"
|
||||||
parentFieldname="dropdown_items"
|
parentFieldname="dropdown_items"
|
||||||
@ -41,17 +41,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Grid from '@/components/Controls/Grid.vue'
|
import Grid from '@/components/Controls/Grid.vue'
|
||||||
import { ErrorMessage } from 'frappe-ui'
|
import { ErrorMessage } from 'frappe-ui'
|
||||||
import { getSettings } from '@/stores/settings'
|
|
||||||
import { showSettings } from '@/composables/settings'
|
import { showSettings } from '@/composables/settings'
|
||||||
import { ref } from 'vue'
|
import { useDocument } from '@/data/document'
|
||||||
|
import { ref, provide } from 'vue'
|
||||||
|
|
||||||
const { _settings: settings } = getSettings()
|
const { document, triggerOnChange } = useDocument(
|
||||||
|
'FCRM Settings',
|
||||||
|
'FCRM Settings',
|
||||||
|
)
|
||||||
|
|
||||||
|
provide('triggerOnChange', triggerOnChange)
|
||||||
|
|
||||||
const emit = defineEmits(['updateStep'])
|
const emit = defineEmits(['updateStep'])
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
function updateSettings() {
|
function updateSettings() {
|
||||||
settings.save.submit(null, {
|
document.save.submit(null, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSettings.value = false
|
showSettings.value = false
|
||||||
},
|
},
|
||||||
|
|||||||
11
frontend/src/components/Settings/HelpdeskSettings.vue
Normal file
11
frontend/src/components/Settings/HelpdeskSettings.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<SettingsPage
|
||||||
|
doctype="Helpdesk CRM Settings"
|
||||||
|
:title="__('Helpdesk settings')"
|
||||||
|
:successMessage="__('Helpdesk settings updated')"
|
||||||
|
class="p-8"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import SettingsPage from '@/components/Settings/SettingsPage.vue'
|
||||||
|
</script>
|
||||||
@ -80,19 +80,16 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Tooltip text="Delete Invitation">
|
<Button
|
||||||
<div>
|
:tooltip="__('Delete invitation')"
|
||||||
<Button
|
icon="x"
|
||||||
icon="x"
|
variant="ghost"
|
||||||
variant="ghost"
|
:loading="
|
||||||
:loading="
|
pendingInvitations.delete.loading &&
|
||||||
pendingInvitations.delete.loading &&
|
pendingInvitations.delete.params.name === user.name
|
||||||
pendingInvitations.delete.params.name === user.name
|
"
|
||||||
"
|
@click="pendingInvitations.delete.submit(user.name)"
|
||||||
@click="pendingInvitations.delete.submit(user.name)"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
||||||
import ERPNextIcon from '@/components/Icons/ERPNextIcon.vue'
|
import ERPNextIcon from '@/components/Icons/ERPNextIcon.vue'
|
||||||
|
import HelpdeskIcon from '@/components/Icons/HelpdeskIcon.vue'
|
||||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||||
import EmailTemplateIcon from '@/components/Icons/EmailTemplateIcon.vue'
|
import EmailTemplateIcon from '@/components/Icons/EmailTemplateIcon.vue'
|
||||||
@ -52,6 +53,7 @@ import InviteUserPage from '@/components/Settings/InviteUserPage.vue'
|
|||||||
import ProfileSettings from '@/components/Settings/ProfileSettings.vue'
|
import ProfileSettings from '@/components/Settings/ProfileSettings.vue'
|
||||||
import WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue'
|
import WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue'
|
||||||
import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue'
|
import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue'
|
||||||
|
import HelpdeskSettings from '@/components/Settings/HelpdeskSettings.vue'
|
||||||
import EmailTemplatePage from '@/components/Settings/EmailTemplate/EmailTemplatePage.vue'
|
import EmailTemplatePage from '@/components/Settings/EmailTemplate/EmailTemplatePage.vue'
|
||||||
import TelephonySettings from '@/components/Settings/TelephonySettings.vue'
|
import TelephonySettings from '@/components/Settings/TelephonySettings.vue'
|
||||||
import EmailConfig from '@/components/Settings/EmailConfig.vue'
|
import EmailConfig from '@/components/Settings/EmailConfig.vue'
|
||||||
@ -137,6 +139,12 @@ const tabs = computed(() => {
|
|||||||
component: markRaw(ERPNextSettings),
|
component: markRaw(ERPNextSettings),
|
||||||
condition: () => isManager(),
|
condition: () => isManager(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: __('Helpdesk'),
|
||||||
|
icon: HelpdeskIcon,
|
||||||
|
component: markRaw(HelpdeskSettings),
|
||||||
|
condition: () => isManager(),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
condition: () => isManager() || isTelephonyAgent(),
|
condition: () => isManager() || isTelephonyAgent(),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -20,12 +20,9 @@
|
|||||||
v-if="section.showEditButton"
|
v-if="section.showEditButton"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-7 mr-2"
|
class="w-7 mr-2"
|
||||||
|
:icon="EditIcon"
|
||||||
@click="showSidePanelModal = true"
|
@click="showSidePanelModal = true"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<EditIcon />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<slot v-bind="{ section }">
|
<slot v-bind="{ section }">
|
||||||
@ -83,11 +80,12 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="field.fieldtype === 'Dropdown'">
|
<div v-else-if="field.fieldtype === 'Dropdown'">
|
||||||
<NestedPopover>
|
<Popover>
|
||||||
<template #target="{ open }">
|
<template #target="{ isOpen, togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
:label="doc[field.fieldname]"
|
:label="doc[field.fieldname]"
|
||||||
class="dropdown-button flex w-full items-center justify-between rounded border border-gray-100 bg-surface-gray-2 px-2 py-1.5 text-base text-ink-gray-8 placeholder-ink-gray-4 transition-colors hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:border-outline-gray-4 focus:bg-surface-white focus:shadow-sm focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3"
|
class="dropdown-button flex items-center justify-between bg-surface-white !px-2.5 py-1.5 text-base text-ink-gray-8 placeholder-ink-gray-4 transition-colors hover:bg-surface-white focus:bg-surface-white focus:shadow-sm focus:outline-none focus:ring-0"
|
||||||
|
@click="togglePopover"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="doc[field.fieldname]"
|
v-if="doc[field.fieldname]"
|
||||||
@ -103,7 +101,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
:name="
|
||||||
|
isOpen ? 'chevron-up' : 'chevron-down'
|
||||||
|
"
|
||||||
class="h-4 text-ink-gray-5"
|
class="h-4 text-ink-gray-5"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -135,16 +135,13 @@
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full !justify-start"
|
class="w-full !justify-start"
|
||||||
:label="__('Create New')"
|
:label="__('Create New')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="field.create()"
|
@click="field.create()"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NestedPopover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-else-if="field.fieldtype == 'Check'"
|
v-else-if="field.fieldtype == 'Check'"
|
||||||
@ -369,7 +366,6 @@
|
|||||||
import Password from '@/components/Controls/Password.vue'
|
import Password from '@/components/Controls/Password.vue'
|
||||||
import FormattedInput from '@/components/Controls/FormattedInput.vue'
|
import FormattedInput from '@/components/Controls/FormattedInput.vue'
|
||||||
import Section from '@/components/Section.vue'
|
import Section from '@/components/Section.vue'
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
|
||||||
import DropdownItem from '@/components/DropdownItem.vue'
|
import DropdownItem from '@/components/DropdownItem.vue'
|
||||||
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
|
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
|
||||||
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
|
||||||
@ -382,7 +378,7 @@ import { usersStore } from '@/stores/users'
|
|||||||
import { isMobileView } from '@/composables/settings'
|
import { isMobileView } from '@/composables/settings'
|
||||||
import { getFormat, evaluateDependsOnValue } from '@/utils'
|
import { getFormat, evaluateDependsOnValue } from '@/utils'
|
||||||
import { flt } from '@/utils/numberFormat.js'
|
import { flt } from '@/utils/numberFormat.js'
|
||||||
import { Tooltip, DateTimePicker, DatePicker } from 'frappe-ui'
|
import { Tooltip, DateTimePicker, DatePicker, Popover } from 'frappe-ui'
|
||||||
import { useDocument } from '@/data/document'
|
import { useDocument } from '@/data/document'
|
||||||
import { ref, computed, getCurrentInstance } from 'vue'
|
import { ref, computed, getCurrentInstance } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@ -94,13 +94,10 @@
|
|||||||
<Button
|
<Button
|
||||||
class="w-full h-8 mt-1.5 !bg-surface-gray-1"
|
class="w-full h-8 mt-1.5 !bg-surface-gray-1"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Field')"
|
:label="__('Add Field')"
|
||||||
>
|
iconLeft="plus"
|
||||||
<template #prefix>
|
@click="togglePopover()"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
/>
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
<template #item-label="{ option }">
|
<template #item-label="{ option }">
|
||||||
<div class="flex flex-col gap-1 text-ink-gray-9">
|
<div class="flex flex-col gap-1 text-ink-gray-9">
|
||||||
@ -128,6 +125,7 @@
|
|||||||
class="w-full h-8"
|
class="w-full h-8"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
:label="__('Add Section')"
|
:label="__('Add Section')"
|
||||||
|
iconLeft="plus"
|
||||||
@click="
|
@click="
|
||||||
sections.push({
|
sections.push({
|
||||||
label: __('New Section'),
|
label: __('New Section'),
|
||||||
@ -136,11 +134,7 @@
|
|||||||
columns: [{ name: 'column_' + getRandom(), fields: [] }],
|
columns: [{ name: 'column_' + getRandom(), fields: [] }],
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
/>
|
||||||
<template #prefix>
|
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -17,13 +17,15 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
<NestedPopover v-else>
|
<Popover placement="bottom-end" v-else>
|
||||||
<template #target="{ open }">
|
<template #target="{ isOpen, togglePopover }">
|
||||||
<Button v-if="sortValues.size > 1" :label="__('Sort')">
|
<Button
|
||||||
<template v-if="hideLabel">
|
v-if="sortValues.size > 1"
|
||||||
<SortIcon class="h-4" />
|
:label="__('Sort')"
|
||||||
</template>
|
:icon="hideLabel && SortIcon"
|
||||||
<template v-if="!hideLabel" #prefix><SortIcon class="h-4" /></template>
|
:iconLeft="!hideLabel && SortIcon"
|
||||||
|
@click="togglePopover"
|
||||||
|
>
|
||||||
<template v-if="sortValues?.size" #suffix>
|
<template v-if="sortValues?.size" #suffix>
|
||||||
<div
|
<div
|
||||||
class="flex h-5 w-5 items-center justify-center rounded-[5px] bg-surface-white pt-px text-xs font-medium text-ink-gray-8 shadow-sm"
|
class="flex h-5 w-5 items-center justify-center rounded-[5px] bg-surface-white pt-px text-xs font-medium text-ink-gray-8 shadow-sm"
|
||||||
@ -36,6 +38,11 @@
|
|||||||
<Button
|
<Button
|
||||||
v-if="sortValues.size"
|
v-if="sortValues.size"
|
||||||
class="rounded-r-none border-r"
|
class="rounded-r-none border-r"
|
||||||
|
:icon="
|
||||||
|
Array.from(sortValues)[0].direction == 'asc'
|
||||||
|
? AscendingIcon
|
||||||
|
: DesendingIcon
|
||||||
|
"
|
||||||
@click.stop="
|
@click.stop="
|
||||||
() => {
|
() => {
|
||||||
Array.from(sortValues)[0].direction =
|
Array.from(sortValues)[0].direction =
|
||||||
@ -43,28 +50,17 @@
|
|||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
/>
|
||||||
<AscendingIcon
|
|
||||||
v-if="Array.from(sortValues)[0].direction == 'asc'"
|
|
||||||
class="h-4"
|
|
||||||
/>
|
|
||||||
<DesendingIcon v-else class="h-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
:label="getSortLabel()"
|
:label="getSortLabel()"
|
||||||
class="shrink-0"
|
class="shrink-0 [&_svg]:text-ink-gray-5"
|
||||||
|
:iconLeft="!hideLabel && !sortValues?.size && SortIcon"
|
||||||
|
:iconRight="
|
||||||
|
sortValues?.size && (isOpen ? 'chevron-up' : 'chevron-down')
|
||||||
|
"
|
||||||
:class="sortValues.size ? 'rounded-l-none' : ''"
|
:class="sortValues.size ? 'rounded-l-none' : ''"
|
||||||
>
|
@click.stop="togglePopover"
|
||||||
<template v-if="!hideLabel && !sortValues?.size" #prefix>
|
/>
|
||||||
<SortIcon class="h-4" />
|
|
||||||
</template>
|
|
||||||
<template v-if="sortValues?.size" #suffix>
|
|
||||||
<FeatherIcon
|
|
||||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
|
||||||
class="h-4 text-ink-gray-5"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ close }">
|
<template #body="{ close }">
|
||||||
@ -85,42 +81,42 @@
|
|||||||
<div class="handle flex h-7 w-7 items-center justify-center">
|
<div class="handle flex h-7 w-7 items-center justify-center">
|
||||||
<DragIcon class="h-4 w-4 cursor-grab text-ink-gray-5" />
|
<DragIcon class="h-4 w-4 cursor-grab text-ink-gray-5" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 [&>_div]:w-full">
|
<div class="flex flex-1">
|
||||||
<Button
|
<Button
|
||||||
size="md"
|
size="md"
|
||||||
class="rounded-r-none border-r"
|
class="rounded-r-none border-r"
|
||||||
|
:icon="
|
||||||
|
sort.direction == 'asc' ? AscendingIcon : DesendingIcon
|
||||||
|
"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
sort.direction = sort.direction == 'asc' ? 'desc' : 'asc'
|
sort.direction = sort.direction == 'asc' ? 'desc' : 'asc'
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
/>
|
||||||
<AscendingIcon v-if="sort.direction == 'asc'" class="h-4" />
|
|
||||||
<DesendingIcon v-else class="h-4" />
|
|
||||||
</Button>
|
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
class="[&>_div]:w-full"
|
||||||
:value="sort.fieldname"
|
:value="sort.fieldname"
|
||||||
:options="sortOptions.data"
|
:options="sortOptions.data"
|
||||||
@change="(e) => updateSort(e, i)"
|
@change="(e) => updateSort(e, i)"
|
||||||
:placeholder="__('First Name')"
|
:placeholder="__('First Name')"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
#target="{ togglePopover, selectedValue, displayValue }"
|
#target="{
|
||||||
|
open,
|
||||||
|
togglePopover,
|
||||||
|
selectedValue,
|
||||||
|
displayValue,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
class="flex w-full items-center justify-between rounded-l-none !text-ink-gray-5"
|
class="flex w-full items-center justify-between rounded-l-none !text-ink-gray-5"
|
||||||
size="md"
|
size="md"
|
||||||
|
:label="displayValue(selectedValue)"
|
||||||
|
:iconRight="open ? 'chevron-down' : 'chevron-up'"
|
||||||
@click="togglePopover()"
|
@click="togglePopover()"
|
||||||
>
|
/>
|
||||||
{{ displayValue(selectedValue) }}
|
|
||||||
<template #suffix>
|
|
||||||
<FeatherIcon
|
|
||||||
name="chevron-down"
|
|
||||||
class="h-4 text-ink-gray-5"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
</div>
|
</div>
|
||||||
@ -143,14 +139,11 @@
|
|||||||
<template #target="{ togglePopover }">
|
<template #target="{ togglePopover }">
|
||||||
<Button
|
<Button
|
||||||
class="!text-ink-gray-5"
|
class="!text-ink-gray-5"
|
||||||
variant="ghost"
|
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Sort')"
|
:label="__('Add Sort')"
|
||||||
>
|
variant="ghost"
|
||||||
<template #prefix>
|
iconLeft="plus"
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
@click="togglePopover()"
|
||||||
</template>
|
/>
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
<Button
|
<Button
|
||||||
@ -164,18 +157,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NestedPopover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import AscendingIcon from '@/components/Icons/AscendingIcon.vue'
|
import AscendingIcon from '@/components/Icons/AscendingIcon.vue'
|
||||||
import DesendingIcon from '@/components/Icons/DesendingIcon.vue'
|
import DesendingIcon from '@/components/Icons/DesendingIcon.vue'
|
||||||
import NestedPopover from '@/components/NestedPopover.vue'
|
|
||||||
import SortIcon from '@/components/Icons/SortIcon.vue'
|
import SortIcon from '@/components/Icons/SortIcon.vue'
|
||||||
import DragIcon from '@/components/Icons/DragIcon.vue'
|
import DragIcon from '@/components/Icons/DragIcon.vue'
|
||||||
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
|
||||||
import { useSortable } from '@vueuse/integrations/useSortable'
|
import { useSortable } from '@vueuse/integrations/useSortable'
|
||||||
import { createResource } from 'frappe-ui'
|
import { createResource, Popover } from 'frappe-ui'
|
||||||
import { computed, nextTick, onMounted } from 'vue'
|
import { computed, nextTick, onMounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@ -123,13 +123,11 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<Button
|
<Button
|
||||||
@click="toggleCallPopup"
|
@click="toggleCallPopup"
|
||||||
class="bg-surface-gray-7 text-ink-white hover:bg-surface-gray-6 shrink-0"
|
class="bg-surface-gray-7 text-ink-white hover:bg-surface-gray-6 shrink-0 cursor-pointer"
|
||||||
|
:tooltip="__('Minimize')"
|
||||||
|
:icon="MinimizeIcon"
|
||||||
size="md"
|
size="md"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<MinimizeIcon class="h-4 w-4 cursor-pointer" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-if="callStatus == 'Call ended' || callStatus == 'No answer'"
|
v-if="callStatus == 'Call ended' || callStatus == 'No answer'"
|
||||||
@click="closeCallPopup"
|
@click="closeCallPopup"
|
||||||
@ -182,33 +180,26 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
|
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
|
||||||
|
:tooltip="__('Add a note')"
|
||||||
size="md"
|
size="md"
|
||||||
|
:icon="NoteIcon"
|
||||||
@click="showNoteWindow"
|
@click="showNoteWindow"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<NoteIcon class="w-4 h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
|
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
|
||||||
size="md"
|
size="md"
|
||||||
|
:tooltip="__('Add a task')"
|
||||||
|
:icon="TaskIcon"
|
||||||
@click="showTaskWindow"
|
@click="showTaskWindow"
|
||||||
>
|
/>
|
||||||
<template #icon>
|
|
||||||
<TaskIcon class="w-4 h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
v-if="contact.deal || contact.lead"
|
v-if="contact.deal || contact.lead"
|
||||||
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
|
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
|
||||||
size="md"
|
size="md"
|
||||||
|
:iconRight="ArrowUpRightIcon"
|
||||||
:label="contact.deal ? __('Deal') : __('Lead')"
|
:label="contact.deal ? __('Deal') : __('Lead')"
|
||||||
@click="openDealOrLead"
|
@click="openDealOrLead"
|
||||||
>
|
/>
|
||||||
<template #suffix>
|
|
||||||
<ArrowUpRightIcon class="w-4 h-4" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user