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
{deal_conds}
GROUP BY d.territory
ORDER BY value DESC
ORDER BY deals DESC, value DESC
""",
{"from": from_date, "to": to_date},
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
{deal_conds}
GROUP BY d.deal_owner
ORDER BY value DESC
ORDER BY deals DESC, value DESC
""",
{"from": from_date, "to": to_date},
as_dict=True,

View File

@ -26,8 +26,9 @@ def create_default_manager_dashboard(force=False):
doc.title = "Manager Dashboard"
doc.layout = default_manager_dashboard_layout()
doc.insert(ignore_permissions=True)
elif force:
else:
doc = frappe.get_doc("CRM Dashboard", "Manager Dashboard")
doc.layout = default_manager_dashboard_layout()
doc.save(ignore_permissions=True)
if force:
doc.layout = default_manager_dashboard_layout()
doc.save(ignore_permissions=True)
return doc.layout

View File

@ -129,15 +129,13 @@
"fieldname": "email",
"fieldtype": "Data",
"label": "Primary Email",
"options": "Email",
"read_only": 1
"options": "Email"
},
{
"fieldname": "mobile_no",
"fieldtype": "Data",
"label": "Primary Mobile No",
"options": "Phone",
"read_only": 1
"options": "Phone"
},
{
"default": "Qualification",
@ -251,8 +249,7 @@
"fieldname": "phone",
"fieldtype": "Data",
"label": "Primary Phone",
"options": "Phone",
"read_only": 1
"options": "Phone"
},
{
"fieldname": "log_tab",
@ -435,7 +432,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-07-13 11:54:20.608489",
"modified": "2025-08-26 12:12:56.324245",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Deal",

View File

@ -63,8 +63,7 @@
"fieldname": "twiml_sid",
"fieldtype": "Data",
"label": "TwiML SID",
"permlevel": 1,
"read_only": 1
"permlevel": 1
},
{
"fieldname": "section_break_ssqj",
@ -105,7 +104,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-01-15 19:35:13.406254",
"modified": "2025-08-19 13:36:19.823197",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Twilio Settings",
@ -152,8 +151,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@ -128,14 +128,32 @@ def get_quotation_url(crm_deal, organization):
address = address.get("name") if address else None
if not erpnext_crm_settings.is_erpnext_in_different_site:
quotation_url = get_url_to_list("Quotation")
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}"
base_url = f"{get_url_to_list('Quotation')}/new"
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:
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)
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):

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": {
"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.create_default_scripts # 13-06-2025
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']
GroupByIcon: typeof import('./src/components/Icons/GroupByIcon.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']
HomeActions: typeof import('./src/components/Settings/General/HomeActions.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']
LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['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']
MaximizeIcon: typeof import('./src/components/Icons/MaximizeIcon.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']
MultiSelectUserInput: typeof import('./src/components/Controls/MultiSelectUserInput.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']
NoteArea: typeof import('./src/components/Activities/NoteArea.vue')['default']
NoteIcon: typeof import('./src/components/Icons/NoteIcon.vue')['default']

View File

@ -13,7 +13,7 @@
"@tiptap/extension-paragraph": "^2.12.0",
"@twilio/voice-sdk": "^2.10.2",
"@vueuse/integrations": "^10.3.0",
"frappe-ui": "^0.1.171",
"frappe-ui": "^0.1.189",
"gemoji": "^8.1.0",
"lodash": "^4.17.21",
"mime": "^4.0.1",

View File

@ -238,12 +238,9 @@
<Button
class="!size-4"
variant="ghost"
:icon="SelectIcon"
@click="activity.show_others = !activity.show_others"
>
<template #icon>
<SelectIcon />
</template>
</Button>
/>
</div>
<div
v-else

View File

@ -9,23 +9,17 @@
<Button
v-if="title == 'Emails'"
variant="solid"
:label="__('New Email')"
iconLeft="plus"
@click="emailBox.show = true"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New Email') }}</span>
</Button>
/>
<Button
v-else-if="title == 'Comments'"
variant="solid"
:label="__('New Comment')"
iconLeft="plus"
@click="emailBox.showComment = true"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New Comment') }}</span>
</Button>
/>
<MultiActionButton
v-else-if="title == 'Calls'"
variant="solid"
@ -34,59 +28,45 @@
<Button
v-else-if="title == 'Notes'"
variant="solid"
:label="__('New Note')"
iconLeft="plus"
@click="modalRef.showNote()"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New Note') }}</span>
</Button>
/>
<Button
v-else-if="title == 'Tasks'"
variant="solid"
:label="__('New Task')"
iconLeft="plus"
@click="modalRef.showTask()"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New Task') }}</span>
</Button>
/>
<Button
v-else-if="title == 'Attachments'"
variant="solid"
:label="__('Upload Attachment')"
iconLeft="plus"
@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'">
<Button
:label="__('Send Template')"
@click="showWhatsappTemplates = true"
/>
<Button variant="solid" @click="whatsappBox.show()">
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New Message') }}</span>
</Button>
<Button
variant="solid"
:label="__('New Message')"
iconLeft="plus"
@click="whatsappBox.show()"
/>
</div>
<Dropdown v-else :options="defaultActions" @click.stop>
<template v-slot="{ open }">
<Button variant="solid" class="flex items-center gap-1">
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>{{ __('New') }}</span>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 w-4"
/>
</template>
</Button>
<Button
variant="solid"
class="flex items-center gap-1"
:label="__('New')"
iconLeft="plus"
:iconRight="open ? 'chevron-up' : 'chevron-down'"
/>
</template>
</Dropdown>
</div>

View File

@ -38,42 +38,31 @@
</div>
</Tooltip>
<div class="flex gap-1">
<Tooltip
:text="
<Button
:tooltip="
attachment.is_private ? __('Make public') : __('Make private')
"
class="!size-5"
@click.stop="
togglePrivate(attachment.name, attachment.is_private)
"
>
<div>
<Button
class="!size-5"
@click.stop="
togglePrivate(attachment.name, attachment.is_private)
"
>
<template #icon>
<FeatherIcon
:name="attachment.is_private ? 'lock' : 'unlock'"
class="size-3 text-ink-gray-7"
/>
</template>
</Button>
</div>
</Tooltip>
<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>
<template #icon>
<FeatherIcon
:name="attachment.is_private ? 'lock' : 'unlock'"
class="size-3 text-ink-gray-7"
/>
</template>
</Button>
<Button
:tooltip="__('Delete attachment')"
class="!size-5"
@click.stop="() => deleteAttachment(attachment.name)"
>
<template #icon>
<FeatherIcon name="trash-2" class="size-3 text-ink-gray-7" />
</template>
</Button>
</div>
</div>
</div>

View File

@ -1,12 +1,12 @@
<template>
<div class="w-full text-sm text-ink-gray-5">
<div class="flex items-center gap-2">
<Button variant="ghost" @click="playPause">
<template #icon>
<PlayIcon v-if="isPaused" class="size-4 text-ink-gray-5" />
<PauseIcon v-else class="size-4 text-ink-gray-5" />
</template>
</Button>
<Button
variant="ghost"
class="text-ink-gray-5"
:icon="isPaused ? PlayIcon : PauseIcon"
@click="playPause"
/>
<div class="flex gap-2 items-center justify-between flex-1">
<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]"
@ -61,11 +61,11 @@
</Button>
</div>
<Dropdown :options="options">
<Button variant="ghost" @click="showPlaybackSpeed = false">
<template #icon>
<FeatherIcon class="size-4" name="more-horizontal" />
</template>
</Button>
<Button
icon="more-horizontal"
variant="ghost"
@click="showPlaybackSpeed = false"
/>
</Dropdown>
</div>
</div>

View File

@ -14,12 +14,10 @@
<div class="flex gap-1">
<Button
v-if="isManager() && !isMobileView"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
@click="showDataFieldsModal = true"
>
<template #icon>
<EditIcon />
</template>
</Button>
/>
<Button
label="Save"
:disabled="!document.isDirty"

View File

@ -2,7 +2,9 @@
<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"
>
<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">
<span>{{ activity.data.sender_full_name }}</span>
<span class="sm:flex hidden text-sm text-ink-gray-5">
@ -28,32 +30,20 @@
</div>
</Tooltip>
<div class="flex gap-0.5">
<Tooltip :text="__('Reply')">
<div>
<Button
variant="ghost"
class="text-ink-gray-7"
@click="reply(activity.data)"
>
<template #icon>
<ReplyIcon />
</template>
</Button>
</div>
</Tooltip>
<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>
<Button
:tooltip="__('Reply')"
variant="ghost"
class="text-ink-gray-7"
:icon="ReplyIcon"
@click="reply(activity.data)"
/>
<Button
:tooltip="__('Reply All')"
variant="ghost"
:icon="ReplyAllIcon"
class="text-ink-gray-7"
@click="reply(activity.data, true)"
/>
</div>
</div>
</div>

View File

@ -41,13 +41,13 @@
:options="taskStatusOptions(modalRef.updateTaskStatus, task)"
@click.stop
>
<Tooltip :text="__('Change Status')">
<div>
<Button variant="ghosted" class="hover:bg-surface-gray-4">
<TaskStatusIcon :status="task.status" />
</Button>
</div>
</Tooltip>
<Button
:tooltip="__('Change status')"
variant="ghosted"
class="hover:bg-surface-gray-4"
>
<TaskStatusIcon :status="task.status" />
</Button>
</Dropdown>
<Dropdown
:options="[

View File

@ -1,7 +1,7 @@
<template>
<NestedPopover>
<template #target>
<div class="flex items-center">
<Popover placement="bottom-end">
<template #target="{ togglePopover }">
<div class="flex items-center" @click="togglePopover">
<component
v-if="assignees?.length"
:is="assignees?.length == 1 ? 'Button' : 'div'"
@ -11,24 +11,23 @@
<Button v-else :label="__('Assign to')" />
</div>
</template>
<template #body="{ open }">
<template #body="{ isOpen }">
<AssignToBody
v-show="open"
v-show="isOpen"
v-model="assignees"
:docname="docname"
:doctype="doctype"
:open="open"
:open="isOpen"
:onUpdate="ownerField && saveAssignees"
/>
</template>
</NestedPopover>
</Popover>
</template>
<script setup>
import NestedPopover from '@/components/NestedPopover.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue'
import AssignToBody from '@/components/AssignToBody.vue'
import { useDocument } from '@/data/document'
import { toast } from 'frappe-ui'
import { toast, Popover } from 'frappe-ui'
import { computed } from 'vue'
const props = defineProps({

View File

@ -25,23 +25,21 @@
:key="assignee.name"
@click.stop
>
<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"
@click.stop
<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"
@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" />
<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)"
>
<template #icon>
<FeatherIcon name="x" class="h-3 w-3 text-ink-gray-6" />
</template>
</Button>
</div>
<template #icon>
<FeatherIcon name="x" class="h-3 w-3 text-ink-gray-6" />
</template>
</Button>
</div>
</Tooltip>
</div>
@ -74,7 +72,7 @@ import UserAvatar from '@/components/UserAvatar.vue'
import Link from '@/components/Controls/Link.vue'
import { usersStore } from '@/stores/users'
import { capture } from '@/telemetry'
import { Tooltip, Switch, toast, createResource } from 'frappe-ui'
import { Tooltip, Switch, createResource } from 'frappe-ui'
import { ref, watch } from 'vue'
const props = defineProps({
@ -154,6 +152,7 @@ watch(
updateAssignees()
}
},
{ immediate: true },
)
async function updateAssignees() {

View File

@ -5,11 +5,9 @@
:label="label"
theme="gray"
variant="outline"
:iconLeft="getIcon()"
@click="toggleDialog()"
>
<template #prefix>
<component :is="getIcon()" class="h-4 w-4" />
</template>
<template #suffix>
<slot name="suffix" />
</template>

View File

@ -1,7 +1,7 @@
<template>
<NestedPopover>
<template #target>
<Button :label="__('Columns')">
<Popover placement="bottom-end">
<template #target="{ togglePopover }">
<Button :label="__('Columns')" @click="togglePopover">
<template v-if="hideLabel">
<ColumnsIcon class="h-4" />
</template>
@ -65,37 +65,28 @@
<Button
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
@click="togglePopover()"
:label="__('Add Column')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover"
/>
</template>
</Autocomplete>
<Button
v-if="columnsUpdated"
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
@click="reset(close)"
:label="__('Reset Changes')"
>
<template #prefix>
<ReloadIcon class="h-4" />
</template>
</Button>
:iconLeft="ReloadIcon"
@click="reset(close)"
/>
<Button
v-if="!is_default"
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
@click="resetToDefault(close)"
:label="__('Reset to Default')"
>
<template #prefix>
<ReloadIcon class="h-4" />
</template>
</Button>
:iconLeft="ReloadIcon"
@click="resetToDefault(close)"
/>
</div>
</div>
<div v-else>
@ -144,7 +135,7 @@
</div>
</div>
</template>
</NestedPopover>
</Popover>
</template>
<script setup>
@ -152,9 +143,9 @@ import ColumnsIcon from '@/components/Icons/ColumnsIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import DragIcon from '@/components/Icons/DragIcon.vue'
import ReloadIcon from '@/components/Icons/ReloadIcon.vue'
import NestedPopover from '@/components/NestedPopover.vue'
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
import { isTouchScreenDevice } from '@/utils'
import { Popover } from 'frappe-ui'
import Draggable from 'vuedraggable'
import { computed, ref } from 'vue'
import { watchOnce } from '@vueuse/core'
@ -219,6 +210,7 @@ const fields = computed(() => {
})
function addColumn(c) {
if (!c) return
let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type)
? 'right'
: 'left'

View File

@ -45,11 +45,12 @@
v-slot="{ togglePopover }"
@update:modelValue="() => appendEmoji()"
>
<Button variant="ghost" @click="togglePopover()">
<template #icon>
<SmileIcon class="h-4" />
</template>
</Button>
<Button
:tooltip="__('Insert Emoji')"
:icon="SmileIcon"
variant="ghost"
@click="togglePopover()"
/>
</IconPicker>
<FileUploader
:upload-args="{
@ -61,14 +62,11 @@
>
<template #default="{ openFileSelector }">
<Button
theme="gray"
:tooltip="__('Attach a file')"
variant="ghost"
:icon="AttachmentIcon"
@click="openFileSelector()"
>
<template #icon>
<AttachmentIcon class="h-4" />
</template>
</Button>
/>
</template>
</FileUploader>
</div>

View File

@ -8,24 +8,18 @@
showEmailBox ? '!bg-surface-gray-4 hover:!bg-surface-gray-3' : '',
]"
:label="__('Reply')"
:iconLeft="Email2Icon"
@click="toggleEmailBox()"
>
<template #prefix>
<Email2Icon class="h-4" />
</template>
</Button>
/>
<Button
variant="ghost"
:label="__('Comment')"
:class="[
showCommentBox ? '!bg-surface-gray-4 hover:!bg-surface-gray-3' : '',
]"
:iconLeft="CommentIcon"
@click="toggleCommentBox()"
>
<template #prefix>
<CommentIcon class="h-4" />
</template>
</Button>
/>
</div>
</div>
<div

View File

@ -52,16 +52,14 @@
>
</div>
</div>
<div class="w-12">
<div class="flex items-center justify-center w-12">
<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"
icon="settings"
@click="showGridFieldsEditorModal = true"
>
<template #icon>
<FeatherIcon name="settings" class="size-4 text-ink-gray-7" />
</template>
</Button>
/>
</div>
</div>
<!-- Rows -->
@ -72,6 +70,7 @@
:delay="isTouchScreenDevice() ? 200 : 0"
group="rows"
item-key="name"
@end="reorder"
>
<template #item="{ element: row, index }">
<div
@ -277,16 +276,14 @@
/>
</div>
</div>
<div class="edit-row w-12">
<div class="edit-row flex items-center justify-center w-12">
<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"
:icon="EditIcon"
@click="showRowList[index] = true"
>
<template #icon>
<EditIcon class="text-ink-gray-7" />
</template>
</Button>
/>
</div>
<GridRowModal
v-if="showRowList[index]"
@ -350,7 +347,6 @@ import { usersStore } from '@/stores/users'
import { getMeta } from '@/stores/meta'
import { createDocument } from '@/composables/document'
import {
FeatherIcon,
FormControl,
Checkbox,
DateTimePicker,
@ -520,6 +516,13 @@ const deleteRows = () => {
selectedRows.clear()
}
const reorder = () => {
rows.value.forEach((row, index) => {
row.idx = index + 1
})
}
function fieldChange(value, field, row) {
triggerOnChange(field.fieldname, value, row)
}

View File

@ -54,13 +54,10 @@
<template #target="{ togglePopover }">
<Button
class="w-full mt-2"
@click="togglePopover()"
:label="__('Add Field')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover()"
/>
</template>
<template #item-label="{ option }">
<div class="flex flex-col gap-1 text-ink-gray-9">
@ -75,7 +72,7 @@
</div>
</template>
<template #actions>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2 justify-end">
<Button
v-if="dirty"
class="w-full"

View File

@ -11,19 +11,18 @@
<div class="flex items-center gap-1">
<Button
v-if="isManager()"
:tooltip="__('Edit fields layout')"
variant="ghost"
class="w-7"
:icon="EditIcon"
@click="openGridRowFieldsModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
icon="x"
variant="ghost"
class="w-7"
@click="show = false"
/>
</div>
</div>
<div>

View File

@ -48,24 +48,18 @@
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
iconLeft="plus"
@click="() => attrs.onCreate(value, close)"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
/>
</div>
<div>
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Clear')"
iconLeft="x"
@click="() => clearValue(close)"
>
<template #prefix>
<FeatherIcon name="x" class="h-4" />
</template>
</Button>
/>
</div>
</template>
</Autocomplete>

View File

@ -18,14 +18,10 @@
:key="g.label"
>
<Dropdown :options="g.action" v-slot="{ open }">
<Button :label="g.label">
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4"
/>
</template>
</Button>
<Button
:label="g.label"
:iconRight="open ? 'chevron-up' : 'chevron-down'"
/>
</Dropdown>
</div>
</template>

View File

@ -19,53 +19,36 @@
v-if="editMode"
variant="ghost"
:label="__('Save')"
size="sm"
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="saveOption"
/>
<Tooltip text="Set As Primary" v-if="!isNew && !option.selected">
<div>
<Button
variant="ghost"
size="sm"
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="option.onClick"
>
<template #icon>
<SuccessIcon />
</template>
</Button>
</div>
</Tooltip>
<Tooltip v-if="!editMode" text="Edit">
<div>
<Button
variant="ghost"
size="sm"
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="toggleEditMode"
>
<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>
<Button
v-if="!isNew && !option.selected"
:tooltip="__('Set As Primary')"
variant="ghost"
:icon="SuccessIcon"
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="option.onClick"
/>
<Button
v-if="!editMode"
:tooltip="__('Edit')"
variant="ghost"
:icon="EditIcon"
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="toggleEditMode"
/>
<Button
:tooltip="__('Delete')"
variant="ghost"
icon="x"
class="opacity-0 hover:bg-surface-gray-4 group-hover:opacity-100"
@click="() => option.onDelete(option, isNew)"
/>
</div>
</div>
<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>
</template>

View File

@ -123,11 +123,12 @@
v-slot="{ togglePopover }"
@update:modelValue="() => appendEmoji()"
>
<Button variant="ghost" @click="togglePopover()">
<template #icon>
<SmileIcon class="h-4" />
</template>
</Button>
<Button
:tooltip="__('Insert Emoji')"
:icon="SmileIcon"
variant="ghost"
@click="togglePopover()"
/>
</IconPicker>
<FileUploader
:upload-args="{
@ -138,21 +139,20 @@
@success="(f) => attachments.push(f)"
>
<template #default="{ openFileSelector }">
<Button variant="ghost" @click="openFileSelector()">
<template #icon>
<AttachmentIcon class="h-4" />
</template>
</Button>
<Button
:tooltip="__('Attach a file')"
:icon="AttachmentIcon"
variant="ghost"
@click="openFileSelector()"
/>
</template>
</FileUploader>
<Button
:tooltip="__('Insert Email Template')"
variant="ghost"
:icon="EmailTemplateIcon"
@click="showEmailTemplateSelectorModal = true"
>
<template #icon>
<EmailTemplateIcon class="h-4" />
</template>
</Button>
/>
</div>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}" :label="__('Discard')" />

View File

@ -89,12 +89,9 @@
v-if="data[field.fieldname] && field.edit"
class="shrink-0"
:label="__('Edit')"
:iconLeft="EditIcon"
@click="field.edit(data[field.fieldname])"
>
<template #prefix>
<EditIcon class="h-4 w-4" />
</template>
</Button>
/>
</div>
<TableMultiselectInput

View File

@ -169,13 +169,10 @@
<Button
class="w-full !h-8 !bg-surface-modal"
variant="outline"
@click="togglePopover()"
:label="__('Add Field')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover()"
/>
</div>
</template>
<template #item-label="{ option }">
@ -198,6 +195,7 @@
class="w-full h-8"
variant="subtle"
:label="__('Add Section')"
iconLeft="plus"
@click="
tabs[tabIndex].sections.push({
label: __('New Section'),
@ -206,11 +204,7 @@
columns: [{ name: 'column_' + getRandom(), fields: [] }],
})
"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
/>
</div>
</div>
</div>

View File

@ -29,6 +29,7 @@
filesUploaderArea?.showWebLink || filesUploaderArea?.showCamera
"
:label="isMobileView ? __('Back') : __('Back to file upload')"
iconLeft="arrow-left"
@click="
() => {
filesUploaderArea.showWebLink = false
@ -37,11 +38,7 @@
filesUploaderArea.cameraImage = null
}
"
>
<template #prefix>
<FeatherIcon name="arrow-left" class="size-4" />
</template>
</Button>
/>
<Button
v-if="
filesUploaderArea?.showCamera && !filesUploaderArea?.cameraImage

View File

@ -1,12 +1,13 @@
<template>
<NestedPopover>
<template #target>
<Popover placement="bottom-end">
<template #target="{ togglePopover, close }">
<div class="flex items-center">
<Button
:label="__('Filter')"
:class="filters?.size ? 'rounded-r-none' : ''"
:iconLeft="FilterIcon"
@click="togglePopover"
>
<template #prefix><FilterIcon class="h-4" /></template>
<template v-if="filters?.size" #suffix>
<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"
@ -15,15 +16,13 @@
</div>
</template>
</Button>
<Tooltip v-if="filters?.size" :text="__('Clear all Filter')">
<div>
<Button
class="rounded-l-none border-l"
icon="x"
@click.stop="clearfilter(false)"
/>
</div>
</Tooltip>
<Button
v-if="filters?.size"
:tooltip="__('Clear all Filter')"
class="rounded-l-none border-l"
icon="x"
@click.stop="clearfilter(close)"
/>
</div>
</template>
<template #body="{ close }">
@ -134,13 +133,10 @@
<Button
class="!text-ink-gray-5"
variant="ghost"
@click="togglePopover()"
:label="__('Add Filter')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover()"
/>
</template>
</Autocomplete>
<Button
@ -154,17 +150,16 @@
</div>
</div>
</template>
</NestedPopover>
</Popover>
</template>
<script setup>
import NestedPopover from '@/components/NestedPopover.vue'
import FilterIcon from '@/components/Icons/FilterIcon.vue'
import Link from '@/components/Controls/Link.vue'
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
import {
FormControl,
createResource,
Tooltip,
Popover,
DatePicker,
DateTimePicker,
DateRangePicker,
@ -485,7 +480,7 @@ function removeFilter(index) {
function clearfilter(close) {
filters.value.clear()
apply()
close && close()
close()
}
function updateValue(value, filter) {

View File

@ -7,18 +7,10 @@
? groupByValue?.label
: __('Group By: ') + groupByValue?.label
"
:iconLeft="DetailsIcon"
:iconRight="isOpen ? 'chevron-up' : 'chevron-down'"
@click="togglePopover()"
>
<template #prefix>
<DetailsIcon />
</template>
<template #suffix>
<FeatherIcon
:name="isOpen ? 'chevron-up' : 'chevron-down'"
class="h-4"
/>
</template>
</Button>
/>
</template>
</Autocomplete>
</template>

View File

@ -69,7 +69,7 @@
</Popover>
</template>
<script setup>
import Popover from '@/components/frappe-ui/Popover.vue'
import { Popover } from 'frappe-ui'
import { gemoji } from 'gemoji'
import { ref, computed } from 'vue'

View File

@ -1,20 +1,15 @@
<template>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<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"
stroke="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
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>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6611 8.2289V9.77773H7.88672V11.9066H11.999V13.4545H6V8.2289H11.6611ZM11.9512 4.6V6.14883H6V4.6H11.9512Z"
fill="currentColor"
/>
<rect x="1.5" y="1.5" width="15" height="15" rx="3.5" stroke="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')"
@click="showDialog = true"
v-bind="$attrs"
>
<template #prefix>
<KanbanIcon class="h-4" />
</template>
</Button>
:iconLeft="KanbanIcon"
/>
<Dialog v-model="showDialog" :options="{ title: __('Kanban Settings') }">
<template #body-content>
<div>
@ -23,8 +20,8 @@
<template #target="{ togglePopover }">
<Button
class="w-full !justify-start"
@click="togglePopover()"
:label="columnField.label"
@click="togglePopover()"
/>
</template>
</Autocomplete>
@ -80,13 +77,10 @@
<template #target="{ togglePopover }">
<Button
class="w-full mt-2"
@click="togglePopover()"
:label="__('Add Field')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover()"
/>
</template>
<template #item-label="{ option }">
<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 items-center text-base">
<NestedPopover>
<template #target>
<Popover>
<template #target="{ togglePopover }">
<Button
variant="ghost"
size="sm"
class="hover:!bg-surface-gray-2"
@click="togglePopover"
>
<IndicatorIcon :class="parseColor(column.column.color)" />
</Button>
</template>
<template #body="{ close }">
<template #body>
<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"
>
@ -48,7 +49,7 @@
</div>
</div>
</template>
</NestedPopover>
</Popover>
<div class="text-ink-gray-9">{{ column.column.name }}</div>
</div>
<div class="flex">
@ -153,13 +154,10 @@
<template #target="{ togglePopover }">
<Button
class="w-full mt-2.5 mb-1 mr-5"
@click="togglePopover()"
:label="__('Add Column')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover()"
/>
</template>
</Autocomplete>
</div>
@ -167,11 +165,10 @@
</template>
<script setup>
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
import NestedPopover from '@/components/NestedPopover.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import { isTouchScreenDevice, colors, parseColor } from '@/utils'
import Draggable from 'vuedraggable'
import { Dropdown } from 'frappe-ui'
import { Dropdown, Popover } from 'frappe-ui'
import { computed } from 'vue'
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="isSidebarCollapsed ? 'w-12' : 'w-[220px]'"
>
<div>
<UserDropdown class="p-2" :isCollapsed="isSidebarCollapsed" />
<div class="p-2">
<UserDropdown :isCollapsed="isSidebarCollapsed" />
</div>
<div class="flex-1 overflow-y-auto">
<div class="mb-3 flex flex-col">
@ -197,51 +197,50 @@ const isSidebarCollapsed = useStorage('isSidebarCollapsed', false)
const isFCSite = ref(window.is_fc_site)
const isDemoSite = ref(window.is_demo_site)
const allViews = computed(() => {
const links = [
{
label: 'Dashboard',
icon: LucideLayoutDashboard,
to: 'Dashboard',
condition: () => isManager(),
},
{
label: 'Leads',
icon: LeadsIcon,
to: 'Leads',
},
{
label: 'Deals',
icon: DealsIcon,
to: 'Deals',
},
{
label: 'Contacts',
icon: ContactsIcon,
to: 'Contacts',
},
{
label: 'Organizations',
icon: OrganizationsIcon,
to: 'Organizations',
},
{
label: 'Notes',
icon: NoteIcon,
to: 'Notes',
},
{
label: 'Tasks',
icon: TaskIcon,
to: 'Tasks',
},
{
label: 'Call Logs',
icon: PhoneIcon,
to: 'Call Logs',
},
]
const links = [
{
label: 'Dashboard',
icon: LucideLayoutDashboard,
to: 'Dashboard',
},
{
label: 'Leads',
icon: LeadsIcon,
to: 'Leads',
},
{
label: 'Deals',
icon: DealsIcon,
to: 'Deals',
},
{
label: 'Contacts',
icon: ContactsIcon,
to: 'Contacts',
},
{
label: 'Organizations',
icon: OrganizationsIcon,
to: 'Organizations',
},
{
label: 'Notes',
icon: NoteIcon,
to: 'Notes',
},
{
label: 'Tasks',
icon: TaskIcon,
to: 'Tasks',
},
{
label: 'Call Logs',
icon: PhoneIcon,
to: 'Call Logs',
},
]
const allViews = computed(() => {
let _views = [
{
name: 'All Views',

View File

@ -11,19 +11,18 @@
<div class="flex items-center gap-1">
<Button
v-if="isManager() && !isMobileView"
:tooltip="__('Edit fields layout')"
variant="ghost"
:icon="EditIcon"
class="w-7"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
icon="x"
variant="ghost"
class="w-7"
@click="show = false"
/>
</div>
</div>
<div v-if="tabs.data && _address.doc">

View File

@ -36,18 +36,17 @@
<Button
v-if="!isMobileView"
variant="ghost"
:tooltip="__('Edit call log')"
:icon="EditIcon"
class="w-7"
@click="openCallLogModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
icon="x"
variant="ghost"
class="w-7"
@click="show = false"
/>
</div>
</div>
<div class="flex flex-col gap-3.5">

View File

@ -13,18 +13,17 @@
<Button
v-if="isManager() && !isMobileView"
variant="ghost"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
class="w-7"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
variant="ghost"
class="w-7"
@click="show = false"
icon="x"
/>
</div>
</div>
<div v-if="tabs.data">
@ -37,7 +36,7 @@
</div>
</div>
<div class="px-4 pt-4 pb-7 sm:px-6">
<div class="space-y-2">
<div class="flex justify-end gap-2">
<Button
class="w-full"
v-for="action in dialogOptions.actions"
@ -61,7 +60,7 @@ import { showQuickEntryModal, quickEntryProps } from '@/composables/modals'
import { getRandom } from '@/utils'
import { capture } from '@/telemetry'
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'
const props = defineProps({

View File

@ -13,17 +13,16 @@
v-if="isManager() && !isMobileView"
variant="ghost"
class="w-7"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
variant="ghost"
class="w-7"
@click="show = false"
icon="x"
/>
</div>
</div>
<FieldLayout
@ -90,12 +89,16 @@ const { document: _contact, triggerOnBeforeCreate } = useDocument('Contact')
async function createContact() {
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
}
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
}

View File

@ -1,17 +1,5 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
actions: [
{
label: __('Convert'),
variant: 'solid',
onClick: convertToDeal,
},
],
}"
>
<Dialog v-model="show" :options="{ size: 'xl' }">
<template #body-header>
<div class="mb-6 flex items-center justify-between">
<div>
@ -23,12 +11,10 @@
<Button
v-if="isManager() && !isMobileView"
variant="ghost"
:tooltip="__('Edit deal\'s mandatory fields layout')"
:icon="EditIcon"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon class="h-4 w-4" />
</template>
</Button>
/>
<Button icon="x" variant="ghost" @click="show = false" />
</div>
</div>
@ -92,6 +78,11 @@
/>
<ErrorMessage class="mt-4" :message="error" />
</template>
<template #actions>
<div class="flex justify-end">
<Button :label="__('Convert')" variant="solid" @click="convertToDeal" />
</div>
</template>
</Dialog>
</template>
<script setup>

View File

@ -13,17 +13,16 @@
v-if="isManager() && !isMobileView"
variant="ghost"
class="w-7"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
variant="ghost"
class="w-7"
icon="x"
@click="show = false"
/>
</div>
</div>
<div v-if="tabs.data">

View File

@ -13,17 +13,16 @@
v-if="isManager() && !isMobileView"
variant="ghost"
class="w-7"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
variant="ghost"
class="w-7"
icon="x"
@click="show = false"
/>
</div>
</div>
<div>

View File

@ -13,17 +13,16 @@
v-if="isManager() && !isMobileView"
variant="ghost"
class="w-7"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
variant="ghost"
class="w-7"
@click="show = false"
icon="x"
/>
</div>
</div>
<div>

View File

@ -1,45 +1,60 @@
<template>
<Dialog v-model="show" :options="{
size: 'xl',
actions: [
{
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateNote(),
},
],
}">
<Dialog v-model="show" :options="{ size: 'xl' }">
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ editMode ? __('Edit Note') : __('Create Note') }}
</h3>
<Button v-if="_note?.reference_docname" size="sm" :label="_note.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
" @click="redirect()">
<template #suffix>
<ArrowUpRightIcon class="w-4 h-4" />
</template>
</Button>
<Button
v-if="_note?.reference_docname"
size="sm"
:label="
_note.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
"
:iconRight="ArrowUpRightIcon"
@click="redirect()"
/>
</div>
</template>
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<FormControl ref="title" :label="__('Title')" v-model="_note.title" :placeholder="__('Call with John Doe')"
required />
<FormControl
ref="title"
:label="__('Title')"
v-model="_note.title"
:placeholder="__('Call with John Doe')"
required
/>
</div>
<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"
: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>
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</div>
</template>
<template #actions>
<div class="flex justify-end">
<Button
:label="editMode ? __('Update') : __('Create')"
variant="solid"
@click="updateNote"
/>
</div>
</template>
</Dialog>
</template>
@ -92,21 +107,25 @@ async function updateNote() {
emit('after', d)
}
} else {
let d = await call('frappe.client.insert', {
doc: {
doctype: 'FCRM Note',
title: _note.value.title,
content: _note.value.content,
reference_doctype: props.doctype,
reference_docname: props.doc || '',
let d = await call(
'frappe.client.insert',
{
doc: {
doctype: 'FCRM Note',
title: _note.value.title,
content: _note.value.content,
reference_doctype: props.doctype,
reference_docname: props.doc || '',
},
},
}, {
onError: (err) => {
if (err.error.exc_type == 'MandatoryError') {
error.value = "Title is mandatory"
}
}
})
{
onError: (err) => {
if (err.error.exc_type == 'MandatoryError') {
error.value = 'Title is mandatory'
}
},
},
)
if (d.name) {
updateOnboardingStep('create_first_note')
capture('note_created')

View File

@ -13,17 +13,16 @@
v-if="isManager() && !isMobileView"
variant="ghost"
class="w-7"
:tooltip="__('Edit fields layout')"
:icon="EditIcon"
@click="openQuickEntryModal"
>
<template #icon>
<EditIcon />
</template>
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<template #icon>
<FeatherIcon name="x" class="size-4" />
</template>
</Button>
/>
<Button
variant="ghost"
class="w-7"
@click="show = false"
icon="x"
/>
</div>
</div>
<FieldLayout
@ -109,6 +108,7 @@ async function createOrganization() {
onError: (err) => {
if (err.error.exc_type == 'ValidationError') {
error.value = err.error?.messages?.[0]
loading.value = false
}
},
},

View File

@ -1,17 +1,5 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
actions: [
{
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateTask(),
},
],
}"
>
<Dialog v-model="show" :options="{ size: 'xl' }">
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
@ -25,12 +13,9 @@
? __('Open Deal')
: __('Open Lead')
"
:iconRight="ArrowUpRightIcon"
@click="redirect()"
>
<template #suffix>
<ArrowUpRightIcon class="w-4 h-4" />
</template>
</Button>
/>
</div>
</template>
<template #body-content>
@ -93,13 +78,15 @@
</Tooltip>
</template>
</Link>
<DateTimePicker
class="datepicker w-36"
v-model="_task.due_date"
:placeholder="__('01/04/2024 11:30 PM')"
:formatter="(date) => getFormat(date, '', true, true)"
input-class="border-none"
/>
<div class="w-36">
<DateTimePicker
class="datepicker"
v-model="_task.due_date"
:placeholder="__('01/04/2024 11:30 PM')"
:formatter="(date) => getFormat(date, '', true, true)"
input-class="border-none"
/>
</div>
<Dropdown :options="taskPriorityOptions(updateTaskPriority)">
<Button :label="_task.priority" class="justify-between w-full">
<template #prefix>
@ -111,6 +98,15 @@
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</div>
</template>
<template #actions>
<div class="flex justify-end">
<Button
:label="editMode ? __('Update') : __('Create')"
variant="solid"
@click="updateTask"
/>
</div>
</template>
</Dialog>
</template>

View File

@ -7,17 +7,6 @@
: duplicateMode
? __('Duplicate View')
: __('Create View'),
actions: [
{
label: editMode
? __('Save Changes')
: duplicateMode
? __('Duplicate')
: __('Create'),
variant: 'solid',
onClick: () => (editMode ? update() : create()),
},
],
}"
>
<template #body-content>
@ -42,6 +31,21 @@
/>
</div>
</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>
</template>

View File

@ -9,21 +9,9 @@
$attrs.class,
showDropdown ? 'rounded-br-none rounded-tr-none' : '',
]"
:iconLeft="activeButton.icon"
@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
v-if="showDropdown"
:options="parsedOptions"
@ -54,7 +42,6 @@ const showDropdown = ref(props.options?.length > 1)
const activeButton = ref(props.options?.[0] || {})
const parsedOptions = computed(() => {
debugger
return (
props.options?.map((option) => {
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="flex gap-1">
<Tooltip :text="__('Mark all as read')">
<div>
<Button variant="ghost" @click="() => markAllAsRead()">
<template #icon>
<MarkAsDoneIcon class="h-4 w-4" />
</template>
</Button>
</div>
</Tooltip>
<Tooltip :text="__('Close')">
<div>
<Button variant="ghost" @click="() => toggle()">
<template #icon>
<FeatherIcon name="x" class="h-4 w-4" />
</template>
</Button>
</div>
</Tooltip>
<Button
:tooltip="__('Mark all as read')"
:icon="MarkAsDoneIcon"
variant="ghost"
@click="markAllAsRead"
/>
<Button
:tooltip="__('Close')"
icon="x"
variant="ghost"
@click="() => toggle()"
/>
</div>
</div>
<div
@ -100,7 +94,6 @@ import { globalStore } from '@/stores/global'
import { timeAgo } from '@/utils'
import { onClickOutside } from '@vueuse/core'
import { capture } from '@/telemetry'
import { Tooltip } from 'frappe-ui'
import { ref, onMounted, onBeforeUnmount } from 'vue'
const { $socket } = globalStore()

View File

@ -21,20 +21,13 @@
<div v-else>{{ s.value }}</div>
</div>
</Tooltip>
<Dropdown
class="form-control"
v-if="s.type == 'Select'"
:options="s.options"
>
<Dropdown v-if="s.type == 'Select'" :options="s.options">
<template #default="{ open }">
<Button :label="s.value">
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4"
/>
</template>
</Button>
<Button
class="form-control bg-surface-white hover:bg-surface-white"
:label="s.value"
:iconRight="open ? 'chevron-up' : 'chevron-down'"
/>
</template>
</Dropdown>
</div>

View File

@ -131,7 +131,7 @@
</div>
</template>
<script setup>
import { ErrorMessage } from 'frappe-ui'
import { ErrorMessage, toast } from 'frappe-ui'
import { getSettings } from '@/stores/settings'
import { globalStore } from '@/stores/global'
import { showSettings } from '@/composables/settings'

View File

@ -17,8 +17,8 @@
:label="__('Update')"
icon-left="plus"
variant="solid"
:disabled="!settings.isDirty"
:loading="settings.loading"
:disabled="!document.isDirty"
:loading="document.loading"
@click="updateSettings"
/>
</div>
@ -27,7 +27,7 @@
<!-- Fields -->
<div class="flex flex-1 flex-col gap-4 overflow-y-auto">
<Grid
v-model="settings.doc.dropdown_items"
v-model="document.doc.dropdown_items"
doctype="CRM Dropdown Item"
parentDoctype="FCRM Settings"
parentFieldname="dropdown_items"
@ -41,17 +41,22 @@
<script setup>
import Grid from '@/components/Controls/Grid.vue'
import { ErrorMessage } from 'frappe-ui'
import { getSettings } from '@/stores/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 errorMessage = ref('')
function updateSettings() {
settings.save.submit(null, {
document.save.submit(null, {
onSuccess: () => {
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>
</div>
<div>
<Tooltip text="Delete Invitation">
<div>
<Button
icon="x"
variant="ghost"
:loading="
pendingInvitations.delete.loading &&
pendingInvitations.delete.params.name === user.name
"
@click="pendingInvitations.delete.submit(user.name)"
/>
</div>
</Tooltip>
<Button
:tooltip="__('Delete invitation')"
icon="x"
variant="ghost"
:loading="
pendingInvitations.delete.loading &&
pendingInvitations.delete.params.name === user.name
"
@click="pendingInvitations.delete.submit(user.name)"
/>
</div>
</li>
</ul>

View File

@ -43,6 +43,7 @@
<script setup>
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import ERPNextIcon from '@/components/Icons/ERPNextIcon.vue'
import HelpdeskIcon from '@/components/Icons/HelpdeskIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.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 WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue'
import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue'
import HelpdeskSettings from '@/components/Settings/HelpdeskSettings.vue'
import EmailTemplatePage from '@/components/Settings/EmailTemplate/EmailTemplatePage.vue'
import TelephonySettings from '@/components/Settings/TelephonySettings.vue'
import EmailConfig from '@/components/Settings/EmailConfig.vue'
@ -137,6 +139,12 @@ const tabs = computed(() => {
component: markRaw(ERPNextSettings),
condition: () => isManager(),
},
{
label: __('Helpdesk'),
icon: HelpdeskIcon,
component: markRaw(HelpdeskSettings),
condition: () => isManager(),
},
],
condition: () => isManager() || isTelephonyAgent(),
},

View File

@ -20,12 +20,9 @@
v-if="section.showEditButton"
variant="ghost"
class="w-7 mr-2"
:icon="EditIcon"
@click="showSidePanelModal = true"
>
<template #icon>
<EditIcon />
</template>
</Button>
/>
</slot>
</template>
<slot v-bind="{ section }">
@ -83,11 +80,12 @@
</Tooltip>
</div>
<div v-else-if="field.fieldtype === 'Dropdown'">
<NestedPopover>
<template #target="{ open }">
<Popover>
<template #target="{ isOpen, togglePopover }">
<Button
: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
v-if="doc[field.fieldname]"
@ -103,7 +101,9 @@
</div>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
:name="
isOpen ? 'chevron-up' : 'chevron-down'
"
class="h-4 text-ink-gray-5"
/>
</template>
@ -135,16 +135,13 @@
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
iconLeft="plus"
@click="field.create()"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
/>
</div>
</div>
</template>
</NestedPopover>
</Popover>
</div>
<FormControl
v-else-if="field.fieldtype == 'Check'"
@ -369,7 +366,6 @@
import Password from '@/components/Controls/Password.vue'
import FormattedInput from '@/components/Controls/FormattedInput.vue'
import Section from '@/components/Section.vue'
import NestedPopover from '@/components/NestedPopover.vue'
import DropdownItem from '@/components/DropdownItem.vue'
import FadedScrollableDiv from '@/components/FadedScrollableDiv.vue'
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
@ -382,7 +378,7 @@ import { usersStore } from '@/stores/users'
import { isMobileView } from '@/composables/settings'
import { getFormat, evaluateDependsOnValue } from '@/utils'
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 { ref, computed, getCurrentInstance } from 'vue'

View File

@ -94,13 +94,10 @@
<Button
class="w-full h-8 mt-1.5 !bg-surface-gray-1"
variant="outline"
@click="togglePopover()"
:label="__('Add Field')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
iconLeft="plus"
@click="togglePopover()"
/>
</template>
<template #item-label="{ option }">
<div class="flex flex-col gap-1 text-ink-gray-9">
@ -128,6 +125,7 @@
class="w-full h-8"
variant="subtle"
:label="__('Add Section')"
iconLeft="plus"
@click="
sections.push({
label: __('New Section'),
@ -136,11 +134,7 @@
columns: [{ name: 'column_' + getRandom(), fields: [] }],
})
"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
/>
</div>
</div>
</template>

View File

@ -17,13 +17,15 @@
</Button>
</template>
</Autocomplete>
<NestedPopover v-else>
<template #target="{ open }">
<Button v-if="sortValues.size > 1" :label="__('Sort')">
<template v-if="hideLabel">
<SortIcon class="h-4" />
</template>
<template v-if="!hideLabel" #prefix><SortIcon class="h-4" /></template>
<Popover placement="bottom-end" v-else>
<template #target="{ isOpen, togglePopover }">
<Button
v-if="sortValues.size > 1"
:label="__('Sort')"
:icon="hideLabel && SortIcon"
:iconLeft="!hideLabel && SortIcon"
@click="togglePopover"
>
<template v-if="sortValues?.size" #suffix>
<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"
@ -36,6 +38,11 @@
<Button
v-if="sortValues.size"
class="rounded-r-none border-r"
:icon="
Array.from(sortValues)[0].direction == 'asc'
? AscendingIcon
: DesendingIcon
"
@click.stop="
() => {
Array.from(sortValues)[0].direction =
@ -43,28 +50,17 @@
apply()
}
"
>
<AscendingIcon
v-if="Array.from(sortValues)[0].direction == 'asc'"
class="h-4"
/>
<DesendingIcon v-else class="h-4" />
</Button>
/>
<Button
: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' : ''"
>
<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>
@click.stop="togglePopover"
/>
</div>
</template>
<template #body="{ close }">
@ -85,42 +81,42 @@
<div class="handle flex h-7 w-7 items-center justify-center">
<DragIcon class="h-4 w-4 cursor-grab text-ink-gray-5" />
</div>
<div class="flex flex-1 [&>_div]:w-full">
<div class="flex flex-1">
<Button
size="md"
class="rounded-r-none border-r"
:icon="
sort.direction == 'asc' ? AscendingIcon : DesendingIcon
"
@click="
() => {
sort.direction = sort.direction == 'asc' ? 'desc' : 'asc'
apply()
}
"
>
<AscendingIcon v-if="sort.direction == 'asc'" class="h-4" />
<DesendingIcon v-else class="h-4" />
</Button>
/>
<Autocomplete
class="[&>_div]:w-full"
:value="sort.fieldname"
:options="sortOptions.data"
@change="(e) => updateSort(e, i)"
:placeholder="__('First Name')"
>
<template
#target="{ togglePopover, selectedValue, displayValue }"
#target="{
open,
togglePopover,
selectedValue,
displayValue,
}"
>
<Button
class="flex w-full items-center justify-between rounded-l-none !text-ink-gray-5"
size="md"
:label="displayValue(selectedValue)"
:iconRight="open ? 'chevron-down' : 'chevron-up'"
@click="togglePopover()"
>
{{ displayValue(selectedValue) }}
<template #suffix>
<FeatherIcon
name="chevron-down"
class="h-4 text-ink-gray-5"
/>
</template>
</Button>
/>
</template>
</Autocomplete>
</div>
@ -143,14 +139,11 @@
<template #target="{ togglePopover }">
<Button
class="!text-ink-gray-5"
variant="ghost"
@click="togglePopover()"
:label="__('Add Sort')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
variant="ghost"
iconLeft="plus"
@click="togglePopover()"
/>
</template>
</Autocomplete>
<Button
@ -164,18 +157,17 @@
</div>
</div>
</template>
</NestedPopover>
</Popover>
</template>
<script setup>
import AscendingIcon from '@/components/Icons/AscendingIcon.vue'
import DesendingIcon from '@/components/Icons/DesendingIcon.vue'
import NestedPopover from '@/components/NestedPopover.vue'
import SortIcon from '@/components/Icons/SortIcon.vue'
import DragIcon from '@/components/Icons/DragIcon.vue'
import Autocomplete from '@/components/frappe-ui/Autocomplete.vue'
import { useSortable } from '@vueuse/integrations/useSortable'
import { createResource } from 'frappe-ui'
import { createResource, Popover } from 'frappe-ui'
import { computed, nextTick, onMounted } from 'vue'
const props = defineProps({

View File

@ -123,13 +123,11 @@
<div class="flex">
<Button
@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"
>
<template #icon>
<MinimizeIcon class="h-4 w-4 cursor-pointer" />
</template>
</Button>
/>
<Button
v-if="callStatus == 'Call ended' || callStatus == 'No answer'"
@click="closeCallPopup"
@ -182,33 +180,26 @@
<div class="flex gap-2">
<Button
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
:tooltip="__('Add a note')"
size="md"
:icon="NoteIcon"
@click="showNoteWindow"
>
<template #icon>
<NoteIcon class="w-4 h-4" />
</template>
</Button>
/>
<Button
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
size="md"
:tooltip="__('Add a task')"
:icon="TaskIcon"
@click="showTaskWindow"
>
<template #icon>
<TaskIcon class="w-4 h-4" />
</template>
</Button>
/>
<Button
v-if="contact.deal || contact.lead"
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
size="md"
:iconRight="ArrowUpRightIcon"
:label="contact.deal ? __('Deal') : __('Lead')"
@click="openDealOrLead"
>
<template #suffix>
<ArrowUpRightIcon class="w-4 h-4" />
</template>
</Button>
/>
</div>
<Button

Some files were not shown because too many files have changed in this diff Show More