Merge remote-tracking branch 'upstream/develop' into bulk-delete-leads

This commit is contained in:
Pratik Badhe 2025-09-02 05:26:14 +00:00
commit 26d49d7ae0
122 changed files with 31600 additions and 15192 deletions

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -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
} }

View File

@ -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):

View 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) {
// },
// });

View File

@ -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": []
}

View 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,
)

View File

@ -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

View File

@ -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}]}]',
}, },
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

5836
crm/locale/da.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

5836
crm/locale/nb.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -0,0 +1,5 @@
from crm.install import add_default_lost_reasons
def execute():
add_default_lost_reasons()

View 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

View File

@ -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']

View File

@ -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",

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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="[

View File

@ -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({

View File

@ -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() {

View File

@ -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>

View File

@ -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'

View File

@ -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>

View File

@ -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

View File

@ -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)
} }

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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')" />

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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) {

View File

@ -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>

View File

@ -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'

View File

@ -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>

View 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>

View File

@ -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">

View File

@ -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({

View File

@ -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',

View File

@ -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">

View File

@ -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">

View File

@ -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({

View File

@ -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
} }

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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')

View File

@ -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
} }
}, },
}, },

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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()

View File

@ -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>

View File

@ -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'

View File

@ -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
}, },

View 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>

View File

@ -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>

View File

@ -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(),
}, },

View File

@ -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'

View File

@ -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>

View File

@ -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({

View File

@ -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