Merge pull request #136 from frappe/develop

chore: Merge develop to main
This commit is contained in:
Shariq Ansari 2024-04-15 21:46:16 +05:30 committed by GitHub
commit c4d4419a4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 808 additions and 659 deletions

12
crm/api/__init__.py Normal file
View File

@ -0,0 +1,12 @@
import frappe
from frappe.translate import get_all_translations
@frappe.whitelist(allow_guest=True)
def get_translations():
if frappe.session.user != "Guest":
language = frappe.db.get_value("User", frappe.session.user, "language")
else:
language = frappe.db.get_single_value("System Settings", "language")
return get_all_translations(language)

View File

@ -1,4 +1,5 @@
import frappe import frappe
from frappe import _
from frappe.model.document import get_controller from frappe.model.document import get_controller
from frappe.model import no_value_fields from frappe.model import no_value_fields
from pypika import Criterion from pypika import Criterion
@ -13,7 +14,7 @@ def sort_options(doctype: str):
fields = [field for field in fields if field.fieldtype not in no_value_fields] fields = [field for field in fields if field.fieldtype not in no_value_fields]
fields = [ fields = [
{ {
"label": field.label, "label": _(field.label),
"value": field.fieldname, "value": field.fieldname,
} }
for field in fields for field in fields
@ -29,6 +30,7 @@ def sort_options(doctype: str):
] ]
for field in standard_fields: for field in standard_fields:
field["label"] = _(field["label"])
fields.append(field) fields.append(field)
return fields return fields
@ -101,6 +103,9 @@ def get_filterable_fields(doctype: str):
field["name"] = field.get("fieldname") field["name"] = field.get("fieldname")
res.append(field) res.append(field)
for field in res:
field["label"] = _(field.get("label"))
return res return res
def get_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields): def get_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fields):
@ -184,6 +189,7 @@ def get_list_data(
for column in columns: for column in columns:
if column.get("key") not in rows: if column.get("key") not in rows:
rows.append(column.get("key")) rows.append(column.get("key"))
column["label"] = _(column.get("label"))
data = frappe.get_list( data = frappe.get_list(
doctype, doctype,
@ -197,7 +203,7 @@ def get_list_data(
fields = [field for field in fields if field.fieldtype not in no_value_fields] fields = [field for field in fields if field.fieldtype not in no_value_fields]
fields = [ fields = [
{ {
"label": field.label, "label": _(field.label),
"type": field.fieldtype, "type": field.fieldtype,
"value": field.fieldname, "value": field.fieldname,
"options": field.options, "options": field.options,
@ -224,6 +230,7 @@ def get_list_data(
if field.get('value') not in rows: if field.get('value') not in rows:
rows.append(field.get('value')) rows.append(field.get('value'))
if field not in fields: if field not in fields:
field["label"] = _(field["label"])
fields.append(field) fields.append(field)
if not is_default and custom_view_name: if not is_default and custom_view_name:

View File

@ -4,9 +4,6 @@ from frappe.query_builder import Order
@frappe.whitelist() @frappe.whitelist()
def get_notifications(): def get_notifications():
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
Notification = frappe.qb.DocType("CRM Notification") Notification = frappe.qb.DocType("CRM Notification")
query = ( query = (
frappe.qb.from_(Notification) frappe.qb.from_(Notification)
@ -46,9 +43,6 @@ def get_notifications():
@frappe.whitelist() @frappe.whitelist()
def mark_as_read(user=None, comment=None): def mark_as_read(user=None, comment=None):
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
user = user or frappe.session.user user = user or frappe.session.user
filters = {"to_user": user, "read": False} filters = {"to_user": user, "read": False}
if comment: if comment:

View File

@ -1,11 +1,8 @@
import frappe import frappe
@frappe.whitelist(allow_guest=True) @frappe.whitelist()
def get_users(): def get_users():
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
users = frappe.qb.get_query( users = frappe.qb.get_query(
"User", "User",
fields=["name", "email", "enabled", "user_image", "full_name", "user_type"], fields=["name", "email", "enabled", "user_image", "full_name", "user_type"],
@ -24,9 +21,6 @@ def get_users():
@frappe.whitelist() @frappe.whitelist()
def get_contacts(): def get_contacts():
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
contacts = frappe.get_all( contacts = frappe.get_all(
"Contact", "Contact",
fields=[ fields=[
@ -66,9 +60,6 @@ def get_contacts():
@frappe.whitelist() @frappe.whitelist()
def get_lead_contacts(): def get_lead_contacts():
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
lead_contacts = frappe.get_all( lead_contacts = frappe.get_all(
"CRM Lead", "CRM Lead",
fields=[ fields=[
@ -88,9 +79,6 @@ def get_lead_contacts():
@frappe.whitelist() @frappe.whitelist()
def get_organizations(): def get_organizations():
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
organizations = frappe.qb.get_query( organizations = frappe.qb.get_query(
"CRM Organization", "CRM Organization",
fields=['*'], fields=['*'],

View File

@ -4,9 +4,6 @@ from pypika import Criterion
@frappe.whitelist() @frappe.whitelist()
def get_views(doctype): def get_views(doctype):
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)
View = frappe.qb.DocType("CRM View Settings") View = frappe.qb.DocType("CRM View Settings")
query = ( query = (
frappe.qb.from_(View) frappe.qb.from_(View)

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="flex items-center justify-between px-10 py-5 text-lg font-medium"> <div class="flex items-center justify-between px-10 py-5 text-lg font-medium">
<div class="flex h-7 items-center text-xl font-semibold text-gray-800"> <div class="flex h-7 items-center text-xl font-semibold text-gray-800">
{{ title }} {{ __(title) }}
</div> </div>
<Button <Button
v-if="title == 'Emails'" v-if="title == 'Emails'"
@ -11,7 +11,7 @@
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" /> <FeatherIcon name="plus" class="h-4 w-4" />
</template> </template>
<span>New Email</span> <span>{{ __('New Email') }}</span>
</Button> </Button>
<Button <Button
v-else-if="title == 'Calls'" v-else-if="title == 'Calls'"
@ -21,41 +21,41 @@
<template #prefix> <template #prefix>
<PhoneIcon class="h-4 w-4" /> <PhoneIcon class="h-4 w-4" />
</template> </template>
<span>Make a Call</span> <span>{{ __('Make a Call') }}</span>
</Button> </Button>
<Button v-else-if="title == 'Notes'" variant="solid" @click="showNote()"> <Button v-else-if="title == 'Notes'" variant="solid" @click="showNote()">
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" /> <FeatherIcon name="plus" class="h-4 w-4" />
</template> </template>
<span>New Note</span> <span>{{ __('New Note') }}</span>
</Button> </Button>
<Button v-else-if="title == 'Tasks'" variant="solid" @click="showTask()"> <Button v-else-if="title == 'Tasks'" variant="solid" @click="showTask()">
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" /> <FeatherIcon name="plus" class="h-4 w-4" />
</template> </template>
<span>New Task</span> <span>{{ __('New Task') }}</span>
</Button> </Button>
<Dropdown <Dropdown
v-else v-else
:options="[ :options="[
{ {
icon: h(EmailIcon, { class: 'h-4 w-4' }), icon: h(EmailIcon, { class: 'h-4 w-4' }),
label: 'New Email', label: __('New Email'),
onClick: () => ($refs.emailBox.show = true), onClick: () => ($refs.emailBox.show = true),
}, },
{ {
icon: h(PhoneIcon, { class: 'h-4 w-4' }), icon: h(PhoneIcon, { class: 'h-4 w-4' }),
label: 'Make a Call', label: __('Make a Call'),
onClick: () => makeCall(doc.data.mobile_no), onClick: () => makeCall(doc.data.mobile_no),
}, },
{ {
icon: h(NoteIcon, { class: 'h-4 w-4' }), icon: h(NoteIcon, { class: 'h-4 w-4' }),
label: 'New Note', label: __('New Note'),
onClick: () => showNote(), onClick: () => showNote(),
}, },
{ {
icon: h(TaskIcon, { class: 'h-4 w-4' }), icon: h(TaskIcon, { class: 'h-4 w-4' }),
label: 'New Task', label: __('New Task'),
onClick: () => showTask(), onClick: () => showTask(),
}, },
]" ]"
@ -66,7 +66,7 @@
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" /> <FeatherIcon name="plus" class="h-4 w-4" />
</template> </template>
<span>New</span> <span>{{ __('New') }}</span>
<template #suffix> <template #suffix>
<FeatherIcon <FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'" :name="open ? 'chevron-up' : 'chevron-down'"
@ -82,7 +82,7 @@
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500" class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
> >
<LoadingIndicator class="h-6 w-6" /> <LoadingIndicator class="h-6 w-6" />
<span>Loading...</span> <span>{{ __('Loading...') }}</span>
</div> </div>
<div v-else-if="activities?.length" class="activities flex-1 overflow-y-auto"> <div v-else-if="activities?.length" class="activities flex-1 overflow-y-auto">
<div <div
@ -101,8 +101,8 @@
<Dropdown <Dropdown
:options="[ :options="[
{ {
label: __('Delete'),
icon: 'trash-2', icon: 'trash-2',
label: 'Delete',
onClick: () => deleteNote(note.name), onClick: () => deleteNote(note.name),
}, },
]" ]"
@ -135,7 +135,7 @@
</div> </div>
<Tooltip :text="dateFormat(note.modified, dateTooltipFormat)"> <Tooltip :text="dateFormat(note.modified, dateTooltipFormat)">
<div class="truncate text-sm text-gray-700"> <div class="truncate text-sm text-gray-700">
{{ timeAgo(note.modified) }} {{ __(timeAgo(note.modified)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -188,7 +188,7 @@
:options="taskStatusOptions(updateTaskStatus, task)" :options="taskStatusOptions(updateTaskStatus, task)"
@click.stop @click.stop
> >
<Tooltip text="Change Status"> <Tooltip :text="__('Change Status')">
<Button variant="ghosted" class="hover:bg-gray-300"> <Button variant="ghosted" class="hover:bg-gray-300">
<TaskStatusIcon :status="task.status" /> <TaskStatusIcon :status="task.status" />
</Button> </Button>
@ -197,15 +197,15 @@
<Dropdown <Dropdown
:options="[ :options="[
{ {
label: __('Delete'),
icon: 'trash-2', icon: 'trash-2',
label: 'Delete',
onClick: () => { onClick: () => {
$dialog({ $dialog({
title: 'Delete Task', title: __('Delete Task'),
message: 'Are you sure you want to delete this task?', message: __('Are you sure you want to delete this task?'),
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
theme: 'red', theme: 'red',
variant: 'solid', variant: 'solid',
onClick(close) { onClick(close) {
@ -257,12 +257,16 @@
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
{{ call.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call {{
call.type == 'Incoming'
? __('Inbound Call')
: __('Outbound Call')
}}
</div> </div>
<div> <div>
<Tooltip :text="dateFormat(call.creation, dateTooltipFormat)"> <Tooltip :text="dateFormat(call.creation, dateTooltipFormat)">
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ timeAgo(call.creation) }} {{ __(timeAgo(call.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -270,25 +274,28 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<DurationIcon class="h-4 w-4 text-gray-600" /> <DurationIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">Duration</div> <div class="text-sm text-gray-600">{{ __('Duration') }}</div>
<div class="text-sm"> <div class="text-sm">
{{ call.duration }} {{ call.duration }}
</div> </div>
</div> </div>
<div <div
v-if="call.recording_url"
class="flex cursor-pointer select-none items-center gap-1" class="flex cursor-pointer select-none items-center gap-1"
@click="call.show_recording = !call.show_recording" @click="call.show_recording = !call.show_recording"
> >
<PlayIcon class="h-4 w-4 text-gray-600" /> <PlayIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ {{
call.show_recording ? 'Hide Recording' : 'Listen to Call' call.show_recording
? __('Hide Recording')
: __('Listen to Call')
}} }}
</div> </div>
</div> </div>
</div> </div>
<div <div
v-if="call.show_recording" v-if="call.show_recording && call.recording_url"
class="flex items-center justify-between rounded border" class="flex items-center justify-between rounded border"
> >
<audio class="audio-control" controls :src="call.recording_url" /> <audio class="audio-control" controls :src="call.recording_url" />
@ -302,7 +309,7 @@
/> />
<div class="ml-1 flex flex-col gap-1"> <div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium"> <div class="text-base font-medium">
{{ call.caller.label }} {{ __(call.caller.label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ call.from }} {{ call.from }}
@ -319,7 +326,7 @@
/> />
<div class="ml-1 flex flex-col gap-1"> <div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium"> <div class="text-base font-medium">
{{ call.receiver.label }} {{ __(call.receiver.label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ call.to }} {{ call.to }}
@ -378,12 +385,12 @@
:text="dateFormat(activity.creation, dateTooltipFormat)" :text="dateFormat(activity.creation, dateTooltipFormat)"
> >
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ timeAgo(activity.creation) }} {{ __(timeAgo(activity.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
<div class="flex gap-0.5"> <div class="flex gap-0.5">
<Tooltip text="Reply"> <Tooltip :text="__('Reply')">
<Button <Button
variant="ghost" variant="ghost"
class="text-gray-700" class="text-gray-700"
@ -392,7 +399,7 @@
<ReplyIcon class="h-4 w-4" /> <ReplyIcon class="h-4 w-4" />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip text="Reply All"> <Tooltip :text="__('Reply All')">
<Button <Button
variant="ghost" variant="ghost"
class="text-gray-700" class="text-gray-700"
@ -407,14 +414,16 @@
{{ activity.data.subject }} {{ activity.data.subject }}
</div> </div>
<div class="mb-3 text-sm leading-5 text-gray-600"> <div class="mb-3 text-sm leading-5 text-gray-600">
<span class="mr-1 text-2xs font-bold text-gray-500">TO:</span> <span class="mr-1 text-2xs font-bold text-gray-500">
{{ __('TO') }}:
</span>
<span>{{ activity.data.recipients }}</span> <span>{{ activity.data.recipients }}</span>
<span v-if="activity.data.cc">, </span> <span v-if="activity.data.cc">, </span>
<span <span
v-if="activity.data.cc" v-if="activity.data.cc"
class="mr-1 text-2xs font-bold text-gray-500" class="mr-1 text-2xs font-bold text-gray-500"
> >
CC: {{ __('CC') }}:
</span> </span>
<span v-if="activity.data.cc">{{ activity.data.cc }}</span> <span v-if="activity.data.cc">{{ activity.data.cc }}</span>
<span v-if="activity.data.bcc">, </span> <span v-if="activity.data.bcc">, </span>
@ -422,7 +431,7 @@
v-if="activity.data.bcc" v-if="activity.data.bcc"
class="mr-1 text-2xs font-bold text-gray-500" class="mr-1 text-2xs font-bold text-gray-500"
> >
BCC: {{ __('BCC') }}:
</span> </span>
<span v-if="activity.data.bcc">{{ activity.data.bcc }}</span> <span v-if="activity.data.bcc">{{ activity.data.bcc }}</span>
</div> </div>
@ -452,15 +461,15 @@
<span class="font-medium text-gray-800"> <span class="font-medium text-gray-800">
{{ activity.owner_name }} {{ activity.owner_name }}
</span> </span>
<span>added a</span> <span>{{ __('added a') }}</span>
<span class="max-w-xs truncate font-medium text-gray-800"> <span class="max-w-xs truncate font-medium text-gray-800">
comment {{ __('comment') }}
</span> </span>
</div> </div>
<div class="ml-auto whitespace-nowrap"> <div class="ml-auto whitespace-nowrap">
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)"> <Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-gray-600"> <div class="text-gray-600">
{{ timeAgo(activity.creation) }} {{ __(timeAgo(activity.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -479,12 +488,16 @@
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
{{ activity.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call {{
activity.type == 'Incoming'
? __('Inbound Call')
: __('Outbound Call')
}}
</div> </div>
<div> <div>
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)"> <Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ timeAgo(activity.creation) }} {{ __(timeAgo(activity.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -492,25 +505,28 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<DurationIcon class="h-4 w-4 text-gray-600" /> <DurationIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">Duration</div> <div class="text-sm text-gray-600">{{ __('Duration') }}</div>
<div class="text-sm"> <div class="text-sm">
{{ activity.duration }} {{ activity.duration }}
</div> </div>
</div> </div>
<div <div
v-if="activity.recording_url"
class="flex cursor-pointer select-none items-center gap-1" class="flex cursor-pointer select-none items-center gap-1"
@click="activity.show_recording = !activity.show_recording" @click="activity.show_recording = !activity.show_recording"
> >
<PlayIcon class="h-4 w-4 text-gray-600" /> <PlayIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ {{
activity.show_recording ? 'Hide Recording' : 'Listen to Call' activity.show_recording
? __('Hide Recording')
: __('Listen to Call')
}} }}
</div> </div>
</div> </div>
</div> </div>
<div <div
v-if="activity.show_recording" v-if="activity.show_recording && activity.recording_url"
class="flex items-center justify-between rounded border" class="flex items-center justify-between rounded border"
> >
<audio <audio
@ -528,7 +544,7 @@
/> />
<div class="ml-1 flex flex-col gap-1"> <div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium"> <div class="text-base font-medium">
{{ activity.caller.label }} {{ __(activity.caller.label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ activity.from }} {{ activity.from }}
@ -545,7 +561,7 @@
/> />
<div class="ml-1 flex flex-col gap-1"> <div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium"> <div class="text-base font-medium">
{{ activity.receiver.label }} {{ __(activity.receiver.label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ activity.to }} {{ activity.to }}
@ -557,17 +573,17 @@
<div v-else class="mb-4 flex flex-col gap-5 py-1.5"> <div v-else class="mb-4 flex flex-col gap-5 py-1.5">
<div class="flex items-start justify-stretch gap-2 text-base"> <div class="flex items-start justify-stretch gap-2 text-base">
<div class="inline-flex flex-wrap gap-1 text-gray-600"> <div class="inline-flex flex-wrap gap-1 text-gray-600">
<span class="font-medium text-gray-800">{{ <span class="font-medium text-gray-800">
activity.owner_name {{ activity.owner_name }}
}}</span> </span>
<span v-if="activity.type">{{ activity.type }}</span> <span v-if="activity.type">{{ __(activity.type) }}</span>
<span <span
v-if="activity.data.field_label" v-if="activity.data.field_label"
class="max-w-xs truncate font-medium text-gray-800" class="max-w-xs truncate font-medium text-gray-800"
> >
{{ activity.data.field_label }} {{ __(activity.data.field_label) }}
</span> </span>
<span v-if="activity.value">{{ activity.value }}</span> <span v-if="activity.value">{{ __(activity.value) }}</span>
<span <span
v-if="activity.data.old_value" v-if="activity.data.old_value"
class="max-w-xs font-medium text-gray-800" class="max-w-xs font-medium text-gray-800"
@ -583,7 +599,7 @@
{{ activity.data.old_value }} {{ activity.data.old_value }}
</div> </div>
</span> </span>
<span v-if="activity.to">to</span> <span v-if="activity.to">{{ __('to') }}</span>
<span <span
v-if="activity.data.value" v-if="activity.data.value"
class="max-w-xs font-medium text-gray-800" class="max-w-xs font-medium text-gray-800"
@ -604,7 +620,7 @@
<div class="ml-auto whitespace-nowrap"> <div class="ml-auto whitespace-nowrap">
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)"> <Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-gray-600"> <div class="text-gray-600">
{{ timeAgo(activity.creation) }} {{ __(timeAgo(activity.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -620,7 +636,7 @@
v-if="activity.data.field_label" v-if="activity.data.field_label"
class="max-w-xs truncate text-gray-600" class="max-w-xs truncate text-gray-600"
> >
{{ activity.data.field_label }} {{ __(activity.data.field_label) }}
</span> </span>
<FeatherIcon <FeatherIcon
name="arrow-right" name="arrow-right"
@ -628,7 +644,9 @@
/> />
</div> </div>
<div class="flex flex-wrap items-center gap-1"> <div class="flex flex-wrap items-center gap-1">
<span v-if="activity.type">{{ startCase(activity.type) }}</span> <span v-if="activity.type">{{
startCase(__(activity.type))
}}</span>
<span <span
v-if="activity.data.old_value" v-if="activity.data.old_value"
class="max-w-xs font-medium text-gray-800" class="max-w-xs font-medium text-gray-800"
@ -644,7 +662,7 @@
{{ activity.data.old_value }} {{ activity.data.old_value }}
</div> </div>
</span> </span>
<span v-if="activity.to">to</span> <span v-if="activity.to">{{ __('to') }}</span>
<span <span
v-if="activity.data.value" v-if="activity.data.value"
class="max-w-xs font-medium text-gray-800" class="max-w-xs font-medium text-gray-800"
@ -666,7 +684,7 @@
<div class="ml-auto whitespace-nowrap"> <div class="ml-auto whitespace-nowrap">
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)"> <Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-gray-600"> <div class="text-gray-600">
{{ timeAgo(activity.creation) }} {{ __(timeAgo(activity.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -674,7 +692,9 @@
<div v-if="activity.other_versions"> <div v-if="activity.other_versions">
<Button <Button
:label=" :label="
activity.show_others ? 'Hide all Changes' : 'Show all Changes' activity.show_others
? __('Hide all Changes')
: __('Show all Changes')
" "
variant="outline" variant="outline"
@click="activity.show_others = !activity.show_others" @click="activity.show_others = !activity.show_others"
@ -696,25 +716,25 @@
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500" class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
> >
<component :is="emptyTextIcon" class="h-10 w-10" /> <component :is="emptyTextIcon" class="h-10 w-10" />
<span>{{ emptyText }}</span> <span>{{ __(emptyText) }}</span>
<Button <Button
v-if="title == 'Calls'" v-if="title == 'Calls'"
label="Make a Call" :label="__('Make a Call')"
@click="makeCall(doc.data.mobile_no)" @click="makeCall(doc.data.mobile_no)"
/> />
<Button <Button
v-else-if="title == 'Notes'" v-else-if="title == 'Notes'"
label="Create Note" :label="__('Create Note')"
@click="showNote()" @click="showNote()"
/> />
<Button <Button
v-else-if="title == 'Emails'" v-else-if="title == 'Emails'"
label="New Email" :label="__('New Email')"
@click="$refs.emailBox.show = true" @click="$refs.emailBox.show = true"
/> />
<Button <Button
v-else-if="title == 'Tasks'" v-else-if="title == 'Tasks'"
label="Create Task" :label="__('Create Task')"
@click="showTask()" @click="showTask()"
/> />
</div> </div>
@ -785,11 +805,10 @@ import {
TextEditor, TextEditor,
Avatar, Avatar,
createResource, createResource,
createListResource,
call, call,
} from 'frappe-ui' } from 'frappe-ui'
import { useElementVisibility } from '@vueuse/core' import { useElementVisibility } from '@vueuse/core'
import { ref, computed, h, markRaw, watch, nextTick, onMounted } from 'vue' import { ref, computed, h, markRaw, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const { makeCall } = globalStore() const { makeCall } = globalStore()

View File

@ -32,12 +32,12 @@
<div v-if="!onCall" class="my-1 text-base"> <div v-if="!onCall" class="my-1 text-base">
{{ {{
callStatus == 'initiating' callStatus == 'initiating'
? 'Initiating call...' ? __('Initiating call...')
: callStatus == 'ringing' : callStatus == 'ringing'
? 'Ringing...' ? __('Ringing...')
: calling : calling
? 'Calling...' ? __('Calling...')
: 'Incoming call...' : __('Incoming call...')
}} }}
</div> </div>
<div v-if="onCall" class="flex gap-2"> <div v-if="onCall" class="flex gap-2">
@ -73,7 +73,7 @@
size="md" size="md"
variant="solid" variant="solid"
theme="red" theme="red"
label="Cancel" :label="__('Cancel')"
@click="cancelCall" @click="cancelCall"
class="rounded-lg" class="rounded-lg"
:disabled="callStatus == 'initiating'" :disabled="callStatus == 'initiating'"
@ -88,7 +88,7 @@
size="md" size="md"
variant="solid" variant="solid"
theme="green" theme="green"
label="Accept" :label="__('Accept')"
class="rounded-lg" class="rounded-lg"
@click="acceptIncomingCall" @click="acceptIncomingCall"
> >
@ -100,7 +100,7 @@
size="md" size="md"
variant="solid" variant="solid"
theme="red" theme="red"
label="Reject" :label="__('Reject')"
class="rounded-lg" class="rounded-lg"
@click="rejectIncomingCall" @click="rejectIncomingCall"
> >
@ -142,7 +142,7 @@
</div> </div>
<div v-else-if="calling" class="flex items-center gap-3"> <div v-else-if="calling" class="flex items-center gap-3">
<div class="my-1"> <div class="my-1">
{{ callStatus == 'ringing' ? 'Ringing...' : 'Calling...' }} {{ callStatus == 'ringing' ? __('Ringing...') : __('Calling...') }}
</div> </div>
<Button <Button
variant="solid" variant="solid"
@ -312,7 +312,7 @@ function handleIncomingCall(call) {
if (!contact.value) { if (!contact.value) {
contact.value = { contact.value = {
full_name: 'Unknown', full_name: __('Unknown'),
mobile_no: call.parameters.From, mobile_no: call.parameters.From,
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<NestedPopover> <NestedPopover>
<template #target> <template #target>
<Button label="Columns"> <Button :label="__('Columns')">
<template #prefix> <template #prefix>
<ColumnsIcon class="h-4" /> <ColumnsIcon class="h-4" />
</template> </template>
@ -24,7 +24,7 @@
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<DragIcon class="h-3.5" /> <DragIcon class="h-3.5" />
<div>{{ element.label }}</div> <div>{{ __(element.label) }}</div>
</div> </div>
<div class="flex cursor-pointer items-center gap-1"> <div class="flex cursor-pointer items-center gap-1">
<Button <Button
@ -56,7 +56,7 @@
class="w-full !justify-start !text-gray-600" class="w-full !justify-start !text-gray-600"
variant="ghost" variant="ghost"
@click="togglePopover()" @click="togglePopover()"
label="Add Column" :label="__('Add Column')"
> >
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4" /> <FeatherIcon name="plus" class="h-4" />
@ -69,7 +69,7 @@
class="w-full !justify-start !text-gray-600" class="w-full !justify-start !text-gray-600"
variant="ghost" variant="ghost"
@click="reset(close)" @click="reset(close)"
label="Reset Changes" :label="__('Reset Changes')"
> >
<template #prefix> <template #prefix>
<ReloadIcon class="h-4" /> <ReloadIcon class="h-4" />
@ -80,7 +80,7 @@
class="w-full !justify-start !text-gray-600" class="w-full !justify-start !text-gray-600"
variant="ghost" variant="ghost"
@click="resetToDefault(close)" @click="resetToDefault(close)"
label="Reset to Default" :label="__('Reset to Default')"
> >
<template #prefix> <template #prefix>
<ReloadIcon class="h-4" /> <ReloadIcon class="h-4" />
@ -96,32 +96,32 @@
<FormControl <FormControl
type="text" type="text"
size="md" size="md"
label="Label" :label="__('Label')"
v-model="column.label" v-model="column.label"
class="w-full" class="w-full"
placeholder="Column Label" :placeholder="__('First Name')"
/> />
<FormControl <FormControl
type="text" type="text"
size="md" size="md"
label="Width" :label="__('Width')"
class="w-full" class="w-full"
v-model="column.width" v-model="column.width"
placeholder="Column Width" placeholder="10rem"
description="Width can be in number, pixel or rem (eg. 3, 30px, 10rem)" :description="__('Width can be in number, pixel or rem (eg. 3, 30px, 10rem)')"
:debounce="500" :debounce="500"
/> />
</div> </div>
<div class="flex w-full gap-2 border-t pt-2"> <div class="flex w-full gap-2 border-t pt-2">
<Button <Button
variant="subtle" variant="subtle"
label="Cancel" :label="__('Cancel')"
class="w-full flex-1" class="w-full flex-1"
@click="cancelUpdate" @click="cancelUpdate"
/> />
<Button <Button
variant="solid" variant="solid"
label="Update" :label="__('Update')"
class="w-full flex-1" class="w-full flex-1"
@click="updateColumn(column)" @click="updateColumn(column)"
/> />

View File

@ -64,11 +64,11 @@
</FileUploader> </FileUploader>
</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')" />
<Button <Button
variant="solid" variant="solid"
v-bind="submitButtonProps || {}" v-bind="submitButtonProps || {}"
label="Submit" :label="__('Comment')"
/> />
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@
ref="sendEmailRef" ref="sendEmailRef"
variant="ghost" variant="ghost"
:class="[showEmailBox ? '!bg-gray-300 hover:!bg-gray-200' : '']" :class="[showEmailBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
label="Reply" :label="__('Reply')"
@click="toggleEmailBox()" @click="toggleEmailBox()"
> >
<template #prefix> <template #prefix>
@ -14,7 +14,7 @@
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
label="Comment" :label="__('Comment')"
:class="[showCommentBox ? '!bg-gray-300 hover:!bg-gray-200' : '']" :class="[showCommentBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
@click="toggleCommentBox()" @click="toggleCommentBox()"
> >
@ -25,12 +25,12 @@
</div> </div>
<div v-if="showEmailBox" class="flex gap-1.5"> <div v-if="showEmailBox" class="flex gap-1.5">
<Button <Button
label="CC" :label="__('CC')"
@click="toggleCC()" @click="toggleCC()"
:class="[newEmailEditor.cc ? 'bg-gray-300 hover:bg-gray-200' : '']" :class="[newEmailEditor.cc ? 'bg-gray-300 hover:bg-gray-200' : '']"
/> />
<Button <Button
label="BCC" :label="__('BCC')"
@click="toggleBCC()" @click="toggleBCC()"
:class="[newEmailEditor.bcc ? 'bg-gray-300 hover:bg-gray-200' : '']" :class="[newEmailEditor.bcc ? 'bg-gray-300 hover:bg-gray-200' : '']"
/> />
@ -66,7 +66,9 @@
v-model:attachments="attachments" v-model:attachments="attachments"
:doctype="doctype" :doctype="doctype"
:subject="subject" :subject="subject"
placeholder="Add a reply..." :placeholder="
__('Hi John, \n\nCan you please provide more details on this...')
"
/> />
</div> </div>
<div v-show="showCommentBox"> <div v-show="showCommentBox">
@ -88,7 +90,7 @@
v-model="doc.data" v-model="doc.data"
v-model:attachments="attachments" v-model:attachments="attachments"
:doctype="doctype" :doctype="doctype"
placeholder="Add a comment..." :placeholder="__('@John, can you please check this?')"
/> />
</div> </div>
</template> </template>

View File

@ -40,10 +40,12 @@
class="text-sm" class="text-sm"
type="text" type="text"
:value="value" :value="value"
@change="selectDate(getDate($event.target.value)) || togglePopover()" @change="
selectDate(getDate($event.target.value)) || togglePopover()
"
/> />
<Button <Button
label="Today" :label="__('Today')"
class="text-sm" class="text-sm"
@click="selectDate(getDate()) || togglePopover()" @click="selectDate(getDate()) || togglePopover()"
/> />
@ -57,7 +59,7 @@
v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']" v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']"
:key="i" :key="i"
> >
{{ d }} {{ __(d) }}
</div> </div>
</div> </div>
<div <div
@ -83,13 +85,13 @@
} }
" "
> >
{{ date.getDate() }} {{ __(date.getDate()) }}
</div> </div>
</div> </div>
</div> </div>
<div class="flex justify-end p-1"> <div class="flex justify-end p-1">
<Button <Button
label="Clear" :label="__('Clear')"
class="text-sm" class="text-sm"
@click=" @click="
() => { () => {
@ -166,10 +168,12 @@ export default {
}, },
formatMonth() { formatMonth() {
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1) let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
return date.toLocaleString('en-US', { let month = __(
month: 'long', date.toLocaleString('en-US', {
year: 'numeric', month: 'long',
}) })
)
return `${month}, ${date.getFullYear()}`
}, },
}, },
methods: { methods: {

View File

@ -48,7 +48,7 @@
v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']" v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']"
:key="i" :key="i"
> >
{{ d }} {{ __(d) }}
</div> </div>
</div> </div>
<div <div
@ -79,13 +79,13 @@
</div> </div>
<div class="flex justify-end space-x-1 p-1"> <div class="flex justify-end space-x-1 p-1">
<Button <Button
label="Clear" :label="__('Clear')"
@click="() => clearDates() | togglePopover()" @click="() => clearDates() | togglePopover()"
:disabled="!fromDate || !toDate" :disabled="!fromDate || !toDate"
/> />
<Button <Button
variant="solid" variant="solid"
label="Apply" :label="__('Apply')"
:disabled="!fromDate || !toDate" :disabled="!fromDate || !toDate"
@click="() => selectDates() | togglePopover()" @click="() => selectDates() | togglePopover()"
/> />
@ -161,10 +161,12 @@ export default {
}, },
formatMonth() { formatMonth() {
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1) let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
return date.toLocaleString('en-US', { let month = __(
month: 'long', date.toLocaleString('en-US', {
year: 'numeric', month: 'long',
}) })
)
return `${month}, ${date.getFullYear()}`
}, },
}, },
methods: { methods: {

View File

@ -41,7 +41,7 @@
@change="updateDate($event.target.value) || togglePopover()" @change="updateDate($event.target.value) || togglePopover()"
/> />
<Button <Button
label="Now" :label="__('Now')"
class="text-sm" class="text-sm"
@click="selectDate(getDate(), false, true) || togglePopover()" @click="selectDate(getDate(), false, true) || togglePopover()"
/> />
@ -55,7 +55,7 @@
v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']" v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']"
:key="i" :key="i"
> >
{{ d }} {{ __(d) }}
</div> </div>
</div> </div>
<div <div
@ -128,7 +128,7 @@
</div> </div>
<div class="flex justify-end p-1"> <div class="flex justify-end p-1">
<Button <Button
label="Clear" :label="__('Clear')"
class="text-sm" class="text-sm"
@click=" @click="
() => { () => {
@ -208,10 +208,12 @@ export default {
}, },
formatMonth() { formatMonth() {
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1) let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
return date.toLocaleString('en-US', { let month = __(
month: 'long', date.toLocaleString('en-US', {
year: 'numeric', month: 'long',
}) })
)
return `${month}, ${date.getFullYear()}`
}, },
}, },
methods: { methods: {

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-1.5"> <div class="space-y-1.5">
<label class="block" :class="labelClasses" v-if="attrs.label"> <label class="block" :class="labelClasses" v-if="attrs.label">
{{ attrs.label }} {{ __(attrs.label) }}
</label> </label>
<Autocomplete <Autocomplete
ref="autocomplete" ref="autocomplete"
@ -33,7 +33,7 @@
<Button <Button
variant="ghost" variant="ghost"
class="w-full !justify-start" class="w-full !justify-start"
label="Create New" :label="__('Create New')"
@click="attrs.onCreate(value, close)" @click="attrs.onCreate(value, close)"
> >
<template #prefix> <template #prefix>
@ -45,7 +45,7 @@
<Button <Button
variant="ghost" variant="ghost"
class="w-full !justify-start" class="w-full !justify-start"
label="Clear" :label="__('Clear')"
@click="() => clearValue(close)" @click="() => clearValue(close)"
> >
<template #prefix> <template #prefix>

View File

@ -10,7 +10,7 @@
> >
<template #top> <template #top>
<div class="mx-10 flex items-center gap-2 border-t py-2.5"> <div class="mx-10 flex items-center gap-2 border-t py-2.5">
<span class="text-xs text-gray-500">SUBJECT:</span> <span class="text-xs text-gray-500">{{ __('SUBJECT') }}:</span>
<TextInput <TextInput
class="flex-1 border-none bg-white hover:bg-white focus:border-none focus:!shadow-none focus-visible:!ring-0" class="flex-1 border-none bg-white hover:bg-white focus:border-none focus:!shadow-none focus-visible:!ring-0"
v-model="subject" v-model="subject"
@ -20,12 +20,14 @@
class="mx-10 flex items-center gap-2 border-t py-2.5" class="mx-10 flex items-center gap-2 border-t py-2.5"
:class="[cc || bcc ? 'border-b' : '']" :class="[cc || bcc ? 'border-b' : '']"
> >
<span class="text-xs text-gray-500">TO:</span> <span class="text-xs text-gray-500">{{ __('TO') }}:</span>
<MultiselectInput <MultiselectInput
class="flex-1" class="flex-1"
v-model="toEmails" v-model="toEmails"
:validate="validateEmail" :validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`" :error-message="
(value) => __('{0} is an invalid email address', [value])
"
/> />
</div> </div>
<div <div
@ -33,23 +35,27 @@
class="mx-10 flex items-center gap-2 py-2.5" class="mx-10 flex items-center gap-2 py-2.5"
:class="bcc ? 'border-b' : ''" :class="bcc ? 'border-b' : ''"
> >
<span class="text-xs text-gray-500">CC:</span> <span class="text-xs text-gray-500">{{ __('CC') }}:</span>
<MultiselectInput <MultiselectInput
ref="ccInput" ref="ccInput"
class="flex-1" class="flex-1"
v-model="ccEmails" v-model="ccEmails"
:validate="validateEmail" :validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`" :error-message="
(value) => __('{0} is an invalid email address', [value])
"
/> />
</div> </div>
<div v-if="bcc" class="mx-10 flex items-center gap-2 py-2.5"> <div v-if="bcc" class="mx-10 flex items-center gap-2 py-2.5">
<span class="text-xs text-gray-500">BCC:</span> <span class="text-xs text-gray-500">{{ __('BCC') }}:</span>
<MultiselectInput <MultiselectInput
ref="bccInput" ref="bccInput"
class="flex-1" class="flex-1"
v-model="bccEmails" v-model="bccEmails"
:validate="validateEmail" :validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`" :error-message="
(value) => __('{0} is an invalid email address', [value])
"
/> />
</div> </div>
</template> </template>
@ -114,11 +120,11 @@
</div> </div>
</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')" />
<Button <Button
variant="solid" variant="solid"
v-bind="submitButtonProps || {}" v-bind="submitButtonProps || {}"
label="Submit" :label="__('Send')"
/> />
</div> </div>
</div> </div>
@ -158,7 +164,7 @@ const props = defineProps({
}, },
subject: { subject: {
type: String, type: String,
default: 'Email from Lead', default: __('Email from Lead'),
}, },
editorProps: { editorProps: {
type: Object, type: Object,

View File

@ -1,7 +1,7 @@
<template> <template>
<NestedPopover> <NestedPopover>
<template #target> <template #target>
<Button label="Filter"> <Button :label="__('Filter')">
<template #prefix><FilterIcon class="h-4" /></template> <template #prefix><FilterIcon class="h-4" /></template>
<template v-if="filters?.size" #suffix> <template v-if="filters?.size" #suffix>
<div <div
@ -24,14 +24,14 @@
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="w-13 pl-2 text-end text-base text-gray-600"> <div class="w-13 pl-2 text-end text-base text-gray-600">
{{ i == 0 ? 'Where' : 'And' }} {{ i == 0 ? __('Where') : __('And') }}
</div> </div>
<div id="fieldname" class="!min-w-[140px]"> <div id="fieldname" class="!min-w-[140px]">
<Autocomplete <Autocomplete
:value="f.field.fieldname" :value="f.field.fieldname"
:options="filterableFields.data" :options="filterableFields.data"
@change="(e) => updateFilter(e, i)" @change="(e) => updateFilter(e, i)"
placeholder="Filter by..." :placeholder="__('First Name')"
/> />
</div> </div>
<div id="operator"> <div id="operator">
@ -40,7 +40,7 @@
v-model="f.operator" v-model="f.operator"
@change="(e) => updateOperator(e, f)" @change="(e) => updateOperator(e, f)"
:options="getOperators(f.field.fieldtype, f.field.fieldname)" :options="getOperators(f.field.fieldtype, f.field.fieldname)"
placeholder="Operator" :placeholder="__('Equals')"
/> />
</div> </div>
<div id="value" class="!min-w-[140px]"> <div id="value" class="!min-w-[140px]">
@ -48,7 +48,7 @@
:is="getValSelect(f)" :is="getValSelect(f)"
v-model="f.value" v-model="f.value"
@change="(v) => updateValue(v, f)" @change="(v) => updateValue(v, f)"
placeholder="Value" :placeholder="__('John Doe')"
/> />
</div> </div>
</div> </div>
@ -58,21 +58,21 @@
v-else v-else
class="mb-3 flex h-7 items-center px-3 text-sm text-gray-600" class="mb-3 flex h-7 items-center px-3 text-sm text-gray-600"
> >
Empty - Choose a field to filter by {{ __('Empty - Choose a field to filter by') }}
</div> </div>
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<Autocomplete <Autocomplete
value="" value=""
:options="filterableFields.data" :options="filterableFields.data"
@change="(e) => setfilter(e)" @change="(e) => setfilter(e)"
placeholder="Filter by..." :placeholder="__('First name')"
> >
<template #target="{ togglePopover }"> <template #target="{ togglePopover }">
<Button <Button
class="!text-gray-600" class="!text-gray-600"
variant="ghost" variant="ghost"
@click="togglePopover()" @click="togglePopover()"
label="Add Filter" :label="__('Add Filter')"
> >
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4" /> <FeatherIcon name="plus" class="h-4" />
@ -84,7 +84,7 @@
v-if="filters?.size" v-if="filters?.size"
class="!text-gray-600" class="!text-gray-600"
variant="ghost" variant="ghost"
label="Clear all Filter" :label="__('Clear all Filter')"
@click="clearfilter(close)" @click="clearfilter(close)"
/> />
</div> </div>
@ -200,89 +200,89 @@ function getOperators(fieldtype, fieldname) {
if (typeString.includes(fieldtype)) { if (typeString.includes(fieldtype)) {
options.push( options.push(
...[ ...[
{ label: 'Equals', value: 'equals' }, { label: __('Equals'), value: 'equals' },
{ label: 'Not Equals', value: 'not equals' }, { label: __('Not Equals'), value: 'not equals' },
{ label: 'Like', value: 'like' }, { label: __('Like'), value: 'like' },
{ label: 'Not Like', value: 'not like' }, { label: __('Not Like'), value: 'not like' },
{ label: 'In', value: 'in' }, { label: __('In'), value: 'in' },
{ label: 'Not In', value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
] ]
) )
} }
if (fieldname === '_assign') { if (fieldname === '_assign') {
// TODO: make equals and not equals work // TODO: make equals and not equals work
options = [ options = [
{ label: 'Like', value: 'like' }, { label: __('Like'), value: 'like' },
{ label: 'Not Like', value: 'not like' }, { label: __('Not Like'), value: 'not like' },
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
] ]
} }
if (typeNumber.includes(fieldtype)) { if (typeNumber.includes(fieldtype)) {
options.push( options.push(
...[ ...[
{ label: 'Equals', value: 'equals' }, { label: __('Equals'), value: 'equals' },
{ label: 'Not Equals', value: 'not equals' }, { label: __('Not Equals'), value: 'not equals' },
{ label: 'Like', value: 'like' }, { label: __('Like'), value: 'like' },
{ label: 'Not Like', value: 'not like' }, { label: __('Not Like'), value: 'not like' },
{ label: 'In', value: 'in' }, { label: __('In'), value: 'in' },
{ label: 'Not In', value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
{ label: '<', value: '<' }, { label: __('<'), value: '<' },
{ label: '>', value: '>' }, { label: __('>'), value: '>' },
{ label: '<=', value: '<=' }, { label: __('<='), value: '<=' },
{ label: '>=', value: '>=' }, { label: __('>='), value: '>=' },
] ]
) )
} }
if (typeSelect.includes(fieldtype)) { if (typeSelect.includes(fieldtype)) {
options.push( options.push(
...[ ...[
{ label: 'Equals', value: 'equals' }, { label: __('Equals'), value: 'equals' },
{ label: 'Not Equals', value: 'not equals' }, { label: __('Not Equals'), value: 'not equals' },
{ label: 'In', value: 'in' }, { label: __('In'), value: 'in' },
{ label: 'Not In', value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
] ]
) )
} }
if (typeLink.includes(fieldtype)) { if (typeLink.includes(fieldtype)) {
options.push( options.push(
...[ ...[
{ label: 'Equals', value: 'equals' }, { label: __('Equals'), value: 'equals' },
{ label: 'Not Equals', value: 'not equals' }, { label: __('Not Equals'), value: 'not equals' },
{ label: 'Like', value: 'like' }, { label: __('Like'), value: 'like' },
{ label: 'Not Like', value: 'not like' }, { label: __('Not Like'), value: 'not like' },
{ label: 'In', value: 'in' }, { label: __('In'), value: 'in' },
{ label: 'Not In', value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
] ]
) )
} }
if (typeCheck.includes(fieldtype)) { if (typeCheck.includes(fieldtype)) {
options.push(...[{ label: 'Equals', value: 'equals' }]) options.push(...[{ label: __('Equals'), value: 'equals' }])
} }
if (['Duration'].includes(fieldtype)) { if (['Duration'].includes(fieldtype)) {
options.push( options.push(
...[ ...[
{ label: 'Like', value: 'like' }, { label: __('Like'), value: 'like' },
{ label: 'Not Like', value: 'not like' }, { label: __('Not Like'), value: 'not like' },
{ label: 'In', value: 'in' }, { label: __('In'), value: 'in' },
{ label: 'Not In', value: 'not in' }, { label: __('Not In'), value: 'not in' },
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
] ]
) )
} }
if (typeDate.includes(fieldtype)) { if (typeDate.includes(fieldtype)) {
options.push( options.push(
...[ ...[
{ label: 'Is', value: 'is' }, { label: __('Is'), value: 'is' },
{ label: '>', value: '>' }, { label: __('>'), value: '>' },
{ label: '<', value: '<' }, { label: __('<'), value: '<' },
{ label: '>=', value: '>=' }, { label: __('>='), value: '>=' },
{ label: '<=', value: '<=' }, { label: __('<='), value: '<=' },
{ label: 'Between', value: 'between' }, { label: __('Between'), value: 'between' },
{ label: 'Timespan', value: 'timespan' }, { label: __('Timespan'), value: 'timespan' },
] ]
) )
} }
@ -327,13 +327,15 @@ function getValSelect(f) {
if (fieldtype == 'Dynamic Link') { if (fieldtype == 'Dynamic Link') {
return h(FormControl, { type: 'text' }) return h(FormControl, { type: 'text' })
} }
return h(Link, { class: 'form-control', doctype: options }) return h(Link, { class: 'form-control', doctype: options, value: f.value })
} else if (typeNumber.includes(fieldtype)) { } else if (typeNumber.includes(fieldtype)) {
return h(FormControl, { type: 'number' }) return h(FormControl, { type: 'number' })
} else if (typeDate.includes(fieldtype) && operator == 'between') { } else if (typeDate.includes(fieldtype) && operator == 'between') {
return h(DateRangePicker) return h(DateRangePicker, { value: f.value })
} else if (typeDate.includes(fieldtype)) { } else if (typeDate.includes(fieldtype)) {
return h(fieldtype == 'Date' ? DatePicker : DatetimePicker) return h(fieldtype == 'Date' ? DatePicker : DatetimePicker, {
value: f.value,
})
} else { } else {
return h(FormControl, { type: 'text' }) return h(FormControl, { type: 'text' })
} }
@ -437,8 +439,6 @@ function updateOperator(event, filter) {
function isSameTypeOperator(oldOperator, newOperator) { function isSameTypeOperator(oldOperator, newOperator) {
let textOperators = [ let textOperators = [
'like',
'not like',
'equals', 'equals',
'not equals', 'not equals',
'in', 'in',
@ -531,71 +531,71 @@ const oppositeOperatorMap = {
const timespanOptions = [ const timespanOptions = [
{ {
label: 'Last Week', label: __('Last Week'),
value: 'last week', value: 'last week',
}, },
{ {
label: 'Last Month', label: __('Last Month'),
value: 'last month', value: 'last month',
}, },
{ {
label: 'Last Quarter', label: __('Last Quarter'),
value: 'last quarter', value: 'last quarter',
}, },
{ {
label: 'Last 6 Months', label: __('Last 6 Months'),
value: 'last 6 months', value: 'last 6 months',
}, },
{ {
label: 'Last Year', label: __('Last Year'),
value: 'last year', value: 'last year',
}, },
{ {
label: 'Yesterday', label: __('Yesterday'),
value: 'yesterday', value: 'yesterday',
}, },
{ {
label: 'Today', label: __('Today'),
value: 'today', value: 'today',
}, },
{ {
label: 'Tomorrow', label: __('Tomorrow'),
value: 'tomorrow', value: 'tomorrow',
}, },
{ {
label: 'This Week', label: __('This Week'),
value: 'this week', value: 'this week',
}, },
{ {
label: 'This Month', label: __('This Month'),
value: 'this month', value: 'this month',
}, },
{ {
label: 'This Quarter', label: __('This Quarter'),
value: 'this quarter', value: 'this quarter',
}, },
{ {
label: 'This Year', label: __('This Year'),
value: 'this year', value: 'this year',
}, },
{ {
label: 'Next Week', label: __('Next Week'),
value: 'next week', value: 'next week',
}, },
{ {
label: 'Next Month', label: __('Next Month'),
value: 'next month', value: 'next month',
}, },
{ {
label: 'Next Quarter', label: __('Next Quarter'),
value: 'next quarter', value: 'next quarter',
}, },
{ {
label: 'Next 6 Months', label: __('Next 6 Months'),
value: 'next 6 months', value: 'next 6 months',
}, },
{ {
label: 'Next Year', label: __('Next Year'),
value: 'next year', value: 'next year',
}, },
] ]

View File

@ -8,24 +8,11 @@
> >
<path <path
d="M93.7101 0H23.8141C10.7986 0 0.247559 10.5511 0.247559 23.5665V93.4626C0.247559 106.478 10.7986 117.029 23.8141 117.029H93.7101C106.726 117.029 117.277 106.478 117.277 93.4626V23.5665C117.277 10.5511 106.726 0 93.7101 0Z" d="M93.7101 0H23.8141C10.7986 0 0.247559 10.5511 0.247559 23.5665V93.4626C0.247559 106.478 10.7986 117.029 23.8141 117.029H93.7101C106.726 117.029 117.277 106.478 117.277 93.4626V23.5665C117.277 10.5511 106.726 0 93.7101 0Z"
fill="url(#paint0_angular_0_12)" fill="#DB4EE0"
/> />
<path <path
d="M21.2487 27.5115V38.0121H85.7749V45.8876L62.5161 68.4638V80.7495L54.9556 83.3222V68.4638C54.9556 68.4638 41.1474 55.0756 36.3171 50.3503H21.3013L42.6174 71.089C43.825 72.2441 44.5076 73.8716 44.5076 75.5517V87.9424L73.0167 88.0474V75.5517C73.0167 73.8716 73.6992 72.2441 74.9068 71.089L96.2755 50.2978V27.5115H21.2487Z" d="M21.2488 27.5116V38.0122H85.7749V45.8876L62.5161 68.4639V80.7496L54.9557 83.3222V68.4639C54.9557 68.4639 41.1474 55.0756 36.3171 50.3504H21.3013L42.6175 71.089C43.825 72.2441 44.5076 73.8717 44.5076 75.5518V87.9425L73.0167 88.0475V75.5518C73.0167 73.8717 73.6992 72.2441 74.9068 71.089L96.2755 50.2979V27.5116H21.2488Z"
fill="#F1FCFF" fill="#F1FCFF"
/> />
<defs>
<radialGradient
id="paint0_angular_0_12"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(81.5001 -21.5) rotate(67.4893) scale(262.5 101.567)"
>
<stop offset="0.494792" stop-color="#E953E6" />
<stop offset="1" stop-color="#BA27D1" />
</radialGradient>
</defs>
</svg> </svg>
</template> </template>

View File

@ -10,7 +10,7 @@
<div class="mb-3 flex flex-col"> <div class="mb-3 flex flex-col">
<SidebarLink <SidebarLink
id="notifications-btn" id="notifications-btn"
label="Notifications" :label="__('Notifications')"
:icon="NotificationsIcon" :icon="NotificationsIcon"
:isCollapsed="isSidebarCollapsed" :isCollapsed="isSidebarCollapsed"
@click="() => toggleNotificationPanel()" @click="() => toggleNotificationPanel()"
@ -45,7 +45,7 @@
<template #header="{ opened, hide, toggle }"> <template #header="{ opened, hide, toggle }">
<div <div
v-if="!hide" v-if="!hide"
class="flex cursor-pointer gap-1.5 px-1 text-sm font-medium text-gray-600 transition-all duration-300 ease-in-out" class="flex cursor-pointer gap-1.5 px-1 text-base font-medium text-gray-600 transition-all duration-300 ease-in-out"
:class=" :class="
isSidebarCollapsed isSidebarCollapsed
? 'ml-0 h-0 overflow-hidden opacity-0' ? 'ml-0 h-0 overflow-hidden opacity-0'
@ -59,7 +59,7 @@
:class="{ 'rotate-90': opened }" :class="{ 'rotate-90': opened }"
/> />
<span class="uppercase"> <span class="uppercase">
{{ view.name }} {{ __(view.name) }}
</span> </span>
</div> </div>
</template> </template>
@ -67,7 +67,7 @@
<SidebarLink <SidebarLink
v-for="link in view.views" v-for="link in view.views"
:icon="link.icon" :icon="link.icon"
:label="link.label" :label="__(link.label)"
:to="link.to" :to="link.to"
:isCollapsed="isSidebarCollapsed" :isCollapsed="isSidebarCollapsed"
class="mx-2 my-0.5" class="mx-2 my-0.5"
@ -78,19 +78,19 @@
</div> </div>
<div class="m-2 flex flex-col gap-1"> <div class="m-2 flex flex-col gap-1">
<SidebarLink <SidebarLink
label="Docs" :label="__('Docs')"
:isCollapsed="isSidebarCollapsed" :isCollapsed="isSidebarCollapsed"
icon="book-open" icon="book-open"
@click="() => openDocs()" @click="() => openDocs()"
/> />
<SidebarLink <SidebarLink
:label="isSidebarCollapsed ? 'Expand' : 'Collapse'" :label="isSidebarCollapsed ? __('Expand') : __('Collapse')"
:isCollapsed="isSidebarCollapsed" :isCollapsed="isSidebarCollapsed"
@click="isSidebarCollapsed = !isSidebarCollapsed" @click="isSidebarCollapsed = !isSidebarCollapsed"
class="" class=""
> >
<template #icon> <template #icon>
<span class="grid h-5 w-6 flex-shrink-0 place-items-center"> <span class="grid h-4.5 w-4.5 flex-shrink-0 place-items-center">
<CollapseSidebar <CollapseSidebar
class="h-4.5 w-4.5 text-gray-700 duration-300 ease-in-out" class="h-4.5 w-4.5 text-gray-700 duration-300 ease-in-out"
:class="{ '[transform:rotateY(180deg)]': isSidebarCollapsed }" :class="{ '[transform:rotateY(180deg)]': isSidebarCollapsed }"

View File

@ -53,7 +53,7 @@
:variant="'subtle'" :variant="'subtle'"
:theme="item.color" :theme="item.color"
size="md" size="md"
:label="item.label" :label="__(item.label)"
/> />
</div> </div>
<div v-else-if="column.type === 'Check'"> <div v-else-if="column.type === 'Check'">

View File

@ -172,14 +172,14 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) { function deleteValues(selections, unselectAll) {
$dialog({ $dialog({
title: 'Delete', title: __('Delete'),
message: `Are you sure you want to delete ${selections.size} item${ message: __('Are you sure you want to delete {0} item(s)?', [
selections.size > 1 ? 's' : '' selections.size,
}?`, ]),
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => { onClick: (close) => {
@ -188,7 +188,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'Contact', doctype: 'Contact',
}).then(() => { }).then(() => {
createToast({ createToast({
title: 'Deleted successfully', title: __('Deleted successfully'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -207,11 +207,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) { function bulkActions(selections, unselectAll) {
let actions = [ let actions = [
{ {
label: 'Edit', label: __('Edit'),
onClick: () => editValues(selections, unselectAll), onClick: () => editValues(selections, unselectAll),
}, },
{ {
label: 'Delete', label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll), onClick: () => deleteValues(selections, unselectAll),
}, },
] ]

View File

@ -203,14 +203,12 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) { function deleteValues(selections, unselectAll) {
$dialog({ $dialog({
title: 'Delete', title: __('Delete'),
message: `Are you sure you want to delete ${selections.size} item${ message: __('Are you sure you want to delete {0} item(s)?', [selections.size]),
selections.size > 1 ? 's' : ''
}?`,
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => { onClick: (close) => {
@ -219,7 +217,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Deal', doctype: 'CRM Deal',
}).then(() => { }).then(() => {
createToast({ createToast({
title: 'Deleted successfully', title: __('Deleted successfully'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -239,17 +237,17 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) { function bulkActions(selections, unselectAll) {
let actions = [ let actions = [
{ {
label: 'Edit', label: __('Edit'),
onClick: () => editValues(selections, unselectAll), onClick: () => editValues(selections, unselectAll),
}, },
{ {
label: 'Delete', label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll), onClick: () => deleteValues(selections, unselectAll),
}, },
] ]
customBulkActions.value.forEach((action) => { customBulkActions.value.forEach((action) => {
actions.push({ actions.push({
label: action.label, label: __(action.label),
onClick: () => onClick: () =>
action.onClick({ action.onClick({
list: list.value, list: list.value,

View File

@ -154,14 +154,14 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) { function deleteValues(selections, unselectAll) {
$dialog({ $dialog({
title: 'Delete', title: __('Delete'),
message: `Are you sure you want to delete ${selections.size} item${ message: __('Are you sure you want to delete {0} item(s)?', [
selections.size > 1 ? 's' : '' selections.size,
}?`, ]),
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => { onClick: (close) => {
@ -170,7 +170,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'Email Template', doctype: 'Email Template',
}).then(() => { }).then(() => {
createToast({ createToast({
title: 'Deleted successfully', title: __('Deleted successfully'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -189,11 +189,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) { function bulkActions(selections, unselectAll) {
let actions = [ let actions = [
{ {
label: 'Edit', label: __('Edit'),
onClick: () => editValues(selections, unselectAll), onClick: () => editValues(selections, unselectAll),
}, },
{ {
label: 'Delete', label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll), onClick: () => deleteValues(selections, unselectAll),
}, },
] ]

View File

@ -212,14 +212,12 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) { function deleteValues(selections, unselectAll) {
$dialog({ $dialog({
title: 'Delete', title: __('Delete'),
message: `Are you sure you want to delete ${selections.size} item${ message: __('Are you sure you want to delete {0} item(s)?', [selections.size]),
selections.size > 1 ? 's' : ''
}?`,
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => { onClick: (close) => {
@ -228,7 +226,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Lead', doctype: 'CRM Lead',
}).then(() => { }).then(() => {
createToast({ createToast({
title: 'Deleted successfully', title: __('Deleted successfully'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -248,17 +246,17 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) { function bulkActions(selections, unselectAll) {
let actions = [ let actions = [
{ {
label: 'Edit', label: __('Edit'),
onClick: () => editValues(selections, unselectAll), onClick: () => editValues(selections, unselectAll),
}, },
{ {
label: 'Delete', label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll), onClick: () => deleteValues(selections, unselectAll),
}, },
] ]
customBulkActions.value.forEach((action) => { customBulkActions.value.forEach((action) => {
actions.push({ actions.push({
label: action.label, label: __(action.label),
onClick: () => onClick: () =>
action.onClick({ action.onClick({
list: list.value, list: list.value,

View File

@ -157,14 +157,14 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) { function deleteValues(selections, unselectAll) {
$dialog({ $dialog({
title: 'Delete', title: __('Delete'),
message: `Are you sure you want to delete ${selections.size} item${ message: __('Are you sure you want to delete {0} item(s)?', [
selections.size > 1 ? 's' : '' selections.size,
}?`, ]),
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => { onClick: (close) => {
@ -173,7 +173,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Organization', doctype: 'CRM Organization',
}).then(() => { }).then(() => {
createToast({ createToast({
title: 'Deleted successfully', title: __('Deleted successfully'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -192,11 +192,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) { function bulkActions(selections, unselectAll) {
let actions = [ let actions = [
{ {
label: 'Edit', label: __('Edit'),
onClick: () => editValues(selections, unselectAll), onClick: () => editValues(selections, unselectAll),
}, },
{ {
label: 'Delete', label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll), onClick: () => deleteValues(selections, unselectAll),
}, },
] ]

View File

@ -176,14 +176,12 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) { function deleteValues(selections, unselectAll) {
$dialog({ $dialog({
title: 'Delete', title: __('Delete'),
message: `Are you sure you want to delete ${selections.size} item${ message: __('Are you sure you want to delete {0} item(s)?'),
selections.size > 1 ? 's' : ''
}?`,
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => { onClick: (close) => {
@ -192,7 +190,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Task', doctype: 'CRM Task',
}).then(() => { }).then(() => {
createToast({ createToast({
title: 'Deleted successfully', title: __('Deleted successfully'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -211,11 +209,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) { function bulkActions(selections, unselectAll) {
let actions = [ let actions = [
{ {
label: 'Edit', label: __('Edit'),
onClick: () => editValues(selections, unselectAll), onClick: () => editValues(selections, unselectAll),
}, },
{ {
label: 'Delete', label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll), onClick: () => deleteValues(selections, unselectAll),
}, },
] ]

View File

@ -2,11 +2,11 @@
<Dialog <Dialog
v-model="show" v-model="show"
:options="{ :options="{
title: 'Assign To', title: __('Assign To'),
size: 'xl', size: 'xl',
actions: [ actions: [
{ {
label: 'Cancel', label: __('Cancel'),
variant: 'subtle', variant: 'subtle',
onClick: () => { onClick: () => {
assignees = oldAssignees assignees = oldAssignees
@ -14,7 +14,7 @@
}, },
}, },
{ {
label: 'Update', label: __('Update'),
variant: 'solid', variant: 'solid',
onClick: () => updateAssignees(), onClick: () => updateAssignees(),
}, },
@ -65,7 +65,7 @@
</Button> </Button>
</Tooltip> </Tooltip>
</div> </div>
<ErrorMessage class="mt-2" v-if="error" :message="error" /> <ErrorMessage class="mt-2" v-if="error" :message="__(error)" />
</template> </template>
</Dialog> </Dialog>
</template> </template>

View File

@ -5,7 +5,7 @@
<div class="mb-5 flex items-center justify-between"> <div class="mb-5 flex items-center justify-between">
<div> <div>
<h3 class="text-2xl font-semibold leading-6 text-gray-900"> <h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ dialogOptions.title || 'Untitled' }} {{ __(dialogOptions.title) || __('Untitled') }}
</h3> </h3>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@ -64,14 +64,14 @@
v-if="field.type === 'link'" v-if="field.type === 'link'"
variant="outline" variant="outline"
size="md" size="md"
:label="field.label" :label="__(field.label)"
v-model="_contact[field.name]" v-model="_contact[field.name]"
:doctype="field.doctype" :doctype="field.doctype"
:placeholder="field.placeholder" :placeholder="field.placeholder"
/> />
<div class="space-y-1.5" v-if="field.type === 'dropdown'"> <div class="space-y-1.5" v-if="field.type === 'dropdown'">
<label class="block text-base text-gray-600"> <label class="block text-base text-gray-600">
{{ field.label }} {{ __(field.label) }}
</label> </label>
<NestedPopover> <NestedPopover>
<template #target="{ open }"> <template #target="{ open }">
@ -101,7 +101,7 @@
/> />
<div v-else> <div v-else>
<div class="p-1.5 px-7 text-base text-gray-500"> <div class="p-1.5 px-7 text-base text-gray-500">
No {{ field.label }} Available {{ __('No {0} Available', [field.label]) }}
</div> </div>
</div> </div>
</div> </div>
@ -109,7 +109,7 @@
<Button <Button
variant="ghost" variant="ghost"
class="w-full !justify-start" class="w-full !justify-start"
label="Create New" :label="__('Create New')"
@click="field.create()" @click="field.create()"
> >
<template #prefix> <template #prefix>
@ -126,7 +126,7 @@
variant="outline" variant="outline"
size="md" size="md"
type="text" type="text"
:label="field.label" :label="__(field.label)"
:placeholder="field.placeholder" :placeholder="field.placeholder"
v-model="_contact[field.name]" v-model="_contact[field.name]"
/> />
@ -143,7 +143,7 @@
:key="action.label" :key="action.label"
v-bind="action" v-bind="action"
> >
{{ action.label }} {{ __(action.label) }}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,23 +1,23 @@
<template> <template>
<Dialog v-model="show" :options="{ title: 'Bulk Edit' }"> <Dialog v-model="show" :options="{ title: __('Bulk Edit') }">
<template #body-content> <template #body-content>
<div class="mb-4"> <div class="mb-4">
<div class="mb-1.5 text-sm text-gray-600">Field</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Field') }}</div>
<Autocomplete <Autocomplete
:value="field.label" :value="field.label"
:options="fields.data" :options="fields.data"
@change="(e) => changeField(e)" @change="(e) => changeField(e)"
placeholder="Select Field..." :placeholder="__('Source')"
/> />
</div> </div>
<div> <div>
<div class="mb-1.5 text-sm text-gray-600">Value</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Value') }}</div>
<component <component
:is="getValueComponent(field)" :is="getValueComponent(field)"
:value="newValue" :value="newValue"
size="md" size="md"
@change="(v) => updateValue(v)" @change="(v) => updateValue(v)"
placeholder="Value" :placeholder="__('Contact Us')"
/> />
</div> </div>
</template> </template>
@ -27,7 +27,7 @@
variant="solid" variant="solid"
@click="updateValues" @click="updateValues"
:loading="loading" :loading="loading"
:label="`Update ${recordCount} Records`" :label="__('Update {0} Records', [recordCount])"
/> />
</template> </template>
</Dialog> </Dialog>

View File

@ -2,11 +2,11 @@
<Dialog <Dialog
v-model="show" v-model="show"
:options="{ :options="{
title: editMode ? emailTemplate.name : 'Create Email Template', title: editMode ? __(emailTemplate.name) : __('Create Email Template'),
size: 'xl', size: 'xl',
actions: [ actions: [
{ {
label: editMode ? 'Update' : 'Create', label: editMode ? __('Update') : __('Create'),
variant: 'solid', variant: 'solid',
onClick: () => (editMode ? updateEmailTemplate() : callInsertDoc()), onClick: () => (editMode ? updateEmailTemplate() : callInsertDoc()),
}, },
@ -18,41 +18,41 @@
<div class="flex gap-4"> <div class="flex gap-4">
<div class="flex-1"> <div class="flex-1">
<div class="mb-1.5 text-sm text-gray-600"> <div class="mb-1.5 text-sm text-gray-600">
Name {{ __('Name') }}
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</div> </div>
<TextInput <TextInput
ref="nameRef" ref="nameRef"
variant="outline" variant="outline"
v-model="_emailTemplate.name" v-model="_emailTemplate.name"
placeholder="Add name" :placeholder="__('Payment Reminder')"
/> />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="mb-1.5 text-sm text-gray-600">Doctype</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Doctype') }}</div>
<Select <Select
variant="outline" variant="outline"
v-model="_emailTemplate.reference_doctype" v-model="_emailTemplate.reference_doctype"
:options="['CRM Deal', 'CRM Lead']" :options="['CRM Deal', 'CRM Lead']"
placeholder="Select Doctype" :placeholder="__('CRM Deal')"
/> />
</div> </div>
</div> </div>
<div> <div>
<div class="mb-1.5 text-sm text-gray-600"> <div class="mb-1.5 text-sm text-gray-600">
Subject {{ __('Subject') }}
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</div> </div>
<TextInput <TextInput
ref="subjectRef" ref="subjectRef"
variant="outline" variant="outline"
v-model="_emailTemplate.subject" v-model="_emailTemplate.subject"
placeholder="Add subject" :placeholder="__('Payment Reminder from Frappé - (#{{ name }})')"
/> />
</div> </div>
<div> <div>
<div class="mb-1.5 text-sm text-gray-600"> <div class="mb-1.5 text-sm text-gray-600">
Content {{ __('Content') }}
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</div> </div>
<TextEditor <TextEditor
@ -62,13 +62,13 @@
:bubbleMenu="true" :bubbleMenu="true"
:content="_emailTemplate.response" :content="_emailTemplate.response"
@change="(val) => (_emailTemplate.response = val)" @change="(val) => (_emailTemplate.response = val)"
placeholder="Type a Content" :placeholder="__('Dear {{ lead_name }}, \n\nThis is a reminder for the payment of {{ grand_total }}. \n\nThanks, \nFrappé')"
/> />
</div> </div>
<div> <div>
<Checkbox v-model="_emailTemplate.enabled" label="Enabled" /> <Checkbox v-model="_emailTemplate.enabled" :label="__('Enabled')" />
</div> </div>
<ErrorMessage :message="errorMessage" /> <ErrorMessage :message="__(errorMessage)" />
</div> </div>
</template> </template>
</Dialog> </Dialog>

View File

@ -1,16 +1,22 @@
<template> <template>
<Dialog v-model="show" :options="{ title: 'Email Templates', size: '4xl' }"> <Dialog
v-model="show"
:options="{ title: __('Email Templates'), size: '4xl' }"
>
<template #body-content> <template #body-content>
<TextInput <TextInput
ref="searchInput" ref="searchInput"
v-model="search" v-model="search"
type="text" type="text"
class="mb-2 w-full" :placeholder="__('Payment Reminder')"
placeholder="Search" >
/> <template #prefix>
<FeatherIcon name="search" class="h-4 w-4 text-gray-500" />
</template>
</TextInput>
<div <div
v-if="filteredTemplates.length" v-if="filteredTemplates.length"
class="grid max-h-[560px] grid-cols-3 gap-2 overflow-y-auto" class="mt-2 grid max-h-[560px] grid-cols-3 gap-2 overflow-y-auto"
> >
<div <div
v-for="template in filteredTemplates" v-for="template in filteredTemplates"
@ -22,7 +28,7 @@
{{ template.name }} {{ template.name }}
</div> </div>
<div v-if="template.subject" class="text-sm text-gray-600"> <div v-if="template.subject" class="text-sm text-gray-600">
Subject: {{ template.subject }} {{ __('Subject: {0}', [template.subject]) }}
</div> </div>
<TextEditor <TextEditor
v-if="template.response" v-if="template.response"
@ -33,11 +39,13 @@
/> />
</div> </div>
</div> </div>
<div v-else> <div v-else class="mt-2">
<div class="flex h-56 flex-col items-center justify-center"> <div class="flex h-56 flex-col items-center justify-center">
<div class="text-lg text-gray-500">No templates found</div> <div class="text-lg text-gray-500">
{{ __('No templates found') }}
</div>
<Button <Button
label="Create New" :label="__('Create New')"
class="mt-4" class="mt-4"
@click=" @click="
() => { () => {

View File

@ -5,7 +5,7 @@
size: 'xl', size: 'xl',
actions: [ actions: [
{ {
label: editMode ? 'Update' : 'Create', label: editMode ? __('Update') : __('Create'),
variant: 'solid', variant: 'solid',
onClick: () => updateNote(), onClick: () => updateNote(),
}, },
@ -15,14 +15,16 @@
<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-gray-900"> <h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ editMode ? 'Edit Note' : 'Create Note' }} {{ editMode ? __('Edit Note') : __('Create Note') }}
</h3> </h3>
<Button <Button
v-if="_note?.reference_docname" v-if="_note?.reference_docname"
variant="outline" variant="outline"
size="sm" size="sm"
:label=" :label="
_note.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead' _note.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
" "
@click="redirect()" @click="redirect()"
> >
@ -35,16 +37,16 @@
<template #body-content> <template #body-content>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<div class="mb-1.5 text-sm text-gray-600">Title</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Title') }}</div>
<TextInput <TextInput
ref="title" ref="title"
variant="outline" variant="outline"
v-model="_note.title" v-model="_note.title"
placeholder="Add title" :placeholder="__('Call with John Doe')"
/> />
</div> </div>
<div> <div>
<div class="mb-1.5 text-sm text-gray-600">Content</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Content') }}</div>
<TextEditor <TextEditor
variant="outline" variant="outline"
ref="content" ref="content"
@ -52,7 +54,9 @@
:bubbleMenu="true" :bubbleMenu="true"
:content="_note.content" :content="_note.content"
@change="(val) => (_note.content = val)" @change="(val) => (_note.content = val)"
placeholder="Type a Content" :placeholder="
__('Took a call with John Doe and discussed the new project.')
"
/> />
</div> </div>
</div> </div>
@ -64,7 +68,7 @@
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue' import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import { TextEditor, call } from 'frappe-ui' import { TextEditor, call } from 'frappe-ui'
import { ref, nextTick, watch } from 'vue' import { ref, nextTick, watch } from 'vue'
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
note: { note: {

View File

@ -5,7 +5,7 @@
<div class="mb-5 flex items-center justify-between"> <div class="mb-5 flex items-center justify-between">
<div> <div>
<h3 class="text-2xl font-semibold leading-6 text-gray-900"> <h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ dialogOptions.title || 'Untitled' }} {{ __(dialogOptions.title) || __('Untitled') }}
</h3> </h3>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@ -41,67 +41,68 @@
type="text" type="text"
ref="title" ref="title"
size="md" size="md"
label="Organization Name" :label="__('Organization Name')"
variant="outline" variant="outline"
v-model="_organization.organization_name" v-model="_organization.organization_name"
placeholder="Add Organization Name" placeholder="Frappé Technologies"
/> />
<div class="flex gap-4"> <div class="flex gap-4">
<FormControl <FormControl
class="flex-1" class="flex-1"
type="text" type="text"
size="md" size="md"
label="Website" :label="__('Website')"
variant="outline" variant="outline"
v-model="_organization.website" v-model="_organization.website"
placeholder="Add Website" placeholder="https://example.com"
/> />
<FormControl <FormControl
class="flex-1" class="flex-1"
type="text" type="text"
size="md" size="md"
label="Annual Revenue" :label="__('Annual Revenue')"
variant="outline" variant="outline"
v-model="_organization.annual_revenue" v-model="_organization.annual_revenue"
placeholder="Add Annual Revenue" :placeholder="__('9,999,999')"
/> />
</div> </div>
<Link <Link
class="flex-1" class="flex-1"
size="md" size="md"
label="Territory" :label="__('Territory')"
variant="outline" variant="outline"
v-model="_organization.territory" v-model="_organization.territory"
doctype="CRM Territory" doctype="CRM Territory"
placeholder="Add Territory" placeholder="India"
/> />
<div class="flex gap-4"> <div class="flex gap-4">
<FormControl <FormControl
class="flex-1" class="flex-1"
type="select" type="select"
:options="[ :options="[
'1-10', { label: __('1-10'), value: '1-10' },
'11-50', { label: __('11-50'), value: '11-50' },
'51-200', { label: __('51-200'), value: '51-200' },
'201-500', { label: __('201-500'), value: '201-500' },
'501-1000', { label: __('501-1000'), value: '501-1000' },
'1001-5000', { label: __('1001-5000'), value: '1001-5000' },
'5001-10000', { label: __('5001-10000'), value: '5001-10000' },
'10001+', { label: __('10001+'), value: '10001+' },
]" ]"
size="md" size="md"
label="No. of Employees" :label="__('No of Employees')"
variant="outline" variant="outline"
:placeholder="__('1-10')"
v-model="_organization.no_of_employees" v-model="_organization.no_of_employees"
/> />
<Link <Link
class="flex-1" class="flex-1"
size="md" size="md"
label="Industry" :label="__('Industry')"
variant="outline" variant="outline"
v-model="_organization.industry" v-model="_organization.industry"
doctype="CRM Industry" doctype="CRM Industry"
placeholder="Add Industry" :placeholder="__('Technology')"
/> />
</div> </div>
</div> </div>
@ -115,7 +116,7 @@
v-for="action in dialogOptions.actions" v-for="action in dialogOptions.actions"
:key="action.label" :key="action.label"
v-bind="action" v-bind="action"
:label="action.label" :label="__(action.label)"
:loading="loading" :loading="loading"
/> />
</div> </div>
@ -236,14 +237,14 @@ function handleOrganizationUpdate(doc, renamed = false) {
const dialogOptions = computed(() => { const dialogOptions = computed(() => {
let title = !editMode.value let title = !editMode.value
? 'New Organization' ? __('New Organization')
: _organization.value.organization_name : __(_organization.value.organization_name)
let size = detailMode.value ? '' : 'xl' let size = detailMode.value ? '' : 'xl'
let actions = detailMode.value let actions = detailMode.value
? [] ? []
: [ : [
{ {
label: editMode.value ? 'Save' : 'Create', label: editMode.value ? __('Save') : __('Create'),
variant: 'solid', variant: 'solid',
onClick: () => onClick: () =>
editMode.value ? updateOrganization() : callInsertDoc(), editMode.value ? updateOrganization() : callInsertDoc(),

View File

@ -5,7 +5,7 @@
size: 'xl', size: 'xl',
actions: [ actions: [
{ {
label: editMode ? 'Update' : 'Create', label: editMode ? __('Update') : __('Create'),
variant: 'solid', variant: 'solid',
onClick: () => updateTask(), onClick: () => updateTask(),
}, },
@ -15,14 +15,14 @@
<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-gray-900"> <h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ editMode ? 'Edit Task' : 'Create Task' }} {{ editMode ? __('Edit Task') : __('Create Task') }}
</h3> </h3>
<Button <Button
v-if="task?.reference_docname" v-if="task?.reference_docname"
variant="outline" variant="outline"
size="sm" size="sm"
:label=" :label="
task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead' task.reference_doctype == 'CRM Deal' ? __('Open Deal') : __('Open Lead')
" "
@click="redirect()" @click="redirect()"
> >
@ -35,16 +35,16 @@
<template #body-content> <template #body-content>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<div class="mb-1.5 text-sm text-gray-600">Title</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Title') }}</div>
<TextInput <TextInput
ref="title" ref="title"
variant="outline" variant="outline"
v-model="_task.title" v-model="_task.title"
placeholder="Add Title" :placeholder="__('Call with John Doe')"
/> />
</div> </div>
<div> <div>
<div class="mb-1.5 text-sm text-gray-600">Description</div> <div class="mb-1.5 text-sm text-gray-600">{{ __('Description') }}</div>
<TextEditor <TextEditor
variant="outline" variant="outline"
ref="description" ref="description"
@ -52,7 +52,7 @@
:bubbleMenu="true" :bubbleMenu="true"
:content="_task.description" :content="_task.description"
@change="(val) => (_task.description = val)" @change="(val) => (_task.description = val)"
placeholder="Type a Description" :placeholder="__('Took a call with John Doe and discussed the new project.')"
/> />
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -68,7 +68,7 @@
:value="getUser(_task.assigned_to).full_name" :value="getUser(_task.assigned_to).full_name"
doctype="User" doctype="User"
@change="(option) => (_task.assigned_to = option)" @change="(option) => (_task.assigned_to = option)"
placeholder="Assignee" :placeholder="__('John Doe')"
:hideMe="true" :hideMe="true"
> >
<template #prefix> <template #prefix>
@ -90,7 +90,7 @@
icon-left="calendar" icon-left="calendar"
:value="_task.due_date" :value="_task.due_date"
@change="(val) => (_task.due_date = val)" @change="(val) => (_task.due_date = val)"
placeholder="Due Date" :placeholder="__('01/04/2024 11:30 PM')"
input-class="border-none" input-class="border-none"
/> />
<Dropdown :options="taskPriorityOptions(updateTaskPriority)"> <Dropdown :options="taskPriorityOptions(updateTaskPriority)">

View File

@ -3,17 +3,17 @@
v-model="show" v-model="show"
:options="{ :options="{
title: editMode title: editMode
? 'Edit View' ? __('Edit View')
: duplicateMode : duplicateMode
? 'Duplicate View' ? __('Duplicate View')
: 'Create View', : __('Create View'),
actions: [ actions: [
{ {
label: editMode label: editMode
? 'Save Changes' ? __('Save Changes')
: duplicateMode : duplicateMode
? 'Duplicate' ? __('Duplicate')
: 'Create', : __('Create'),
variant: 'solid', variant: 'solid',
onClick: () => (editMode ? update() : create()), onClick: () => (editMode ? update() : create()),
}, },
@ -25,8 +25,8 @@
variant="outline" variant="outline"
size="md" size="md"
type="text" type="text"
label="View Name" :label="__('View Name')"
placeholder="View Name" :placeholder="__('My Open Deals')"
v-model="view.label" v-model="view.label"
/> />
</template> </template>
@ -51,7 +51,6 @@ const props = defineProps({
}, },
}) })
const show = defineModel() const show = defineModel()
const view = defineModel('view') const view = defineModel('view')

View File

@ -14,9 +14,9 @@
<div <div
class="z-20 flex items-center justify-between border-b bg-white px-5 py-2.5" class="z-20 flex items-center justify-between border-b bg-white px-5 py-2.5"
> >
<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"> <Tooltip :text="__('Mark all as read')">
<div> <div>
<Button <Button
variant="ghost" variant="ghost"
@ -28,7 +28,7 @@
</Button> </Button>
</div> </div>
</Tooltip> </Tooltip>
<Tooltip text="Close"> <Tooltip :text="__('Close')">
<div> <div>
<Button variant="ghost" @click="() => toggleNotificationPanel()"> <Button variant="ghost" @click="() => toggleNotificationPanel()">
<template #icon> <template #icon>
@ -58,17 +58,17 @@
<UserAvatar :user="n.from_user.name" size="lg" /> <UserAvatar :user="n.from_user.name" size="lg" />
</div> </div>
<div> <div>
<div class="mb-2 space-x-1 leading-5 text-gray-700"> <div class="mb-2 space-x-1 leading-5 text-gray-600">
<span class="font-medium text-gray-900"> <span class="font-medium text-gray-900">
{{ n.from_user.full_name }} {{ n.from_user.full_name }}
</span> </span>
<span>mentioned you in {{ n.reference_doctype }}</span> <span>{{ __('mentioned you in {0}', [n.reference_doctype]) }}</span>
<span class="font-medium text-gray-900"> <span class="font-medium text-gray-900">
{{ n.reference_name }} {{ n.reference_name }}
</span> </span>
</div> </div>
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ timeAgo(n.creation) }} {{ __(timeAgo(n.creation)) }}
</div> </div>
</div> </div>
</RouterLink> </RouterLink>
@ -79,7 +79,7 @@
> >
<NotificationsIcon class="h-20 w-20 text-gray-300" /> <NotificationsIcon class="h-20 w-20 text-gray-300" />
<div class="text-lg font-medium text-gray-500"> <div class="text-lg font-medium text-gray-500">
No new notifications {{ __('No new notifications') }}
</div> </div>
</div> </div>
</div> </div>
@ -90,7 +90,6 @@ import MarkAsDoneIcon from '@/components/Icons/MarkAsDoneIcon.vue'
import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue' import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { notificationsStore } from '@/stores/notifications' import { notificationsStore } from '@/stores/notifications'
import { globalStore } from '@/stores/global'
import { timeAgo } from '@/utils' import { timeAgo } from '@/utils'
import { onClickOutside } from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
import { Tooltip } from 'frappe-ui' import { Tooltip } from 'frappe-ui'

View File

@ -5,9 +5,9 @@
:key="s.label" :key="s.label"
class="flex items-center gap-2 text-base leading-5" class="flex items-center gap-2 text-base leading-5"
> >
<div class="w-[106px] text-sm text-gray-600">{{ s.label }}</div> <div class="w-[106px] text-sm text-gray-600">{{ __(s.label) }}</div>
<div class="grid min-h-[28px] items-center"> <div class="grid min-h-[28px] items-center">
<Tooltip v-if="s.tooltipText" :text="s.tooltipText"> <Tooltip v-if="s.tooltipText" :text="__(s.tooltipText)">
<div class="ml-2 cursor-pointer"> <div class="ml-2 cursor-pointer">
<Badge <Badge
v-if="s.type == 'Badge'" v-if="s.type == 'Badge'"
@ -69,12 +69,12 @@ let slaSection = computed(() => {
tooltipText = dateFormat(data.value.response_by, dateTooltipFormat) tooltipText = dateFormat(data.value.response_by, dateTooltipFormat)
if (new Date(data.value.response_by) < new Date()) { if (new Date(data.value.response_by) < new Date()) {
color = 'red' color = 'red'
if (status == 'In less than a minute') { if (status == __('In less than a minute')) {
status = 'less than a minute ago' status = 'less than a minute ago'
} }
} }
} else if (['Fulfilled', 'Failed'].includes(status)) { } else if (['Fulfilled', 'Failed'].includes(status)) {
status = status + ' in ' + formatTime(data.value.first_response_time) status = __(status) + ' in ' + formatTime(data.value.first_response_time)
tooltipText = dateFormat(data.value.first_responded_on, dateTooltipFormat) tooltipText = dateFormat(data.value.first_responded_on, dateTooltipFormat)
} }
@ -83,7 +83,7 @@ let slaSection = computed(() => {
{ {
label: 'First Response', label: 'First Response',
type: 'Badge', type: 'Badge',
value: status, value: __(status),
tooltipText: tooltipText, tooltipText: tooltipText,
color: color, color: color,
}, },

View File

@ -10,7 +10,7 @@
class="h-4 text-gray-900 transition-all duration-300 ease-in-out" class="h-4 text-gray-900 transition-all duration-300 ease-in-out"
:class="{ 'rotate-90': opened }" :class="{ 'rotate-90': opened }"
/> />
{{ label || 'Untitled' }} {{ __(label) || __('Untitled') }}
</div> </div>
<slot name="actions"></slot> <slot name="actions"></slot>
</div> </div>

View File

@ -7,7 +7,7 @@
class="flex items-center gap-2 px-3 leading-5 first:mt-3" class="flex items-center gap-2 px-3 leading-5 first:mt-3"
> >
<div class="w-[106px] shrink-0 text-sm text-gray-600"> <div class="w-[106px] shrink-0 text-sm text-gray-600">
{{ field.label }} {{ __(field.label) }}
<span class="text-red-500">{{ field.reqd ? ' *' : '' }}</span> <span class="text-red-500">{{ field.reqd ? ' *' : '' }}</span>
</div> </div>
<div <div
@ -17,7 +17,7 @@
v-if="field.read_only && field.type !== 'checkbox'" v-if="field.read_only && field.type !== 'checkbox'"
class="flex h-7 cursor-pointer items-center px-2 py-1 text-gray-600" class="flex h-7 cursor-pointer items-center px-2 py-1 text-gray-600"
> >
<Tooltip :text="field.tooltip"> <Tooltip :text="__(field.tooltip)">
<div>{{ data[field.name] }}</div> <div>{{ data[field.name] }}</div>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -1,28 +1,28 @@
<template> <template>
<button <button
class="flex h-7 cursor-pointer items-center rounded text-gray-800 duration-300 ease-in-out focus:outline-none focus:transition-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-gray-400" class="flex h-7 cursor-pointer items-center rounded text-gray-700 duration-300 ease-in-out focus:outline-none focus:transition-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-gray-400"
:class="isActive ? 'bg-white shadow-sm' : 'hover:bg-gray-100'" :class="isActive ? 'bg-white shadow-sm' : 'hover:bg-gray-100'"
@click="handleClick" @click="handleClick"
> >
<div <div
class="flex w-full items-center justify-between duration-300 ease-in-out" class="flex w-full items-center justify-between duration-300 ease-in-out"
:class="isCollapsed ? 'p-1' : 'px-2 py-1'" :class="isCollapsed ? 'ml-[3px] p-1' : 'px-2 py-1'"
> >
<div class="flex items-center"> <div class="flex items-center">
<Tooltip :text="label" placement="right" :disabled="!isCollapsed"> <Tooltip :text="label" placement="right" :disabled="!isCollapsed">
<slot name="icon"> <slot name="icon">
<span class="grid h-5 w-6 flex-shrink-0 place-items-center"> <span class="grid h-4.5 w-4.5 flex-shrink-0 place-items-center">
<FeatherIcon <FeatherIcon
v-if="typeof icon == 'string'" v-if="typeof icon == 'string'"
:name="icon" :name="icon"
class="h-4 w-4 text-gray-700" class="h-4.5 w-4.5 text-gray-700"
/> />
<component v-else :is="icon" class="h-4 w-4 text-gray-700" /> <component v-else :is="icon" class="h-4.5 w-4.5 text-gray-700" />
</span> </span>
</slot> </slot>
</Tooltip> </Tooltip>
<span <span
class="flex-1 flex-shrink-0 text-sm duration-300 ease-in-out" class="flex-1 flex-shrink-0 text-base duration-300 ease-in-out"
:class=" :class="
isCollapsed isCollapsed
? 'ml-0 w-0 overflow-hidden opacity-0' ? 'ml-0 w-0 overflow-hidden opacity-0'

View File

@ -1,7 +1,7 @@
<template> <template>
<NestedPopover> <NestedPopover>
<template #target> <template #target>
<Button label="Sort" ref="sortButtonRef"> <Button :label="__('Sort')" ref="sortButtonRef">
<template #prefix><SortIcon class="h-4" /></template> <template #prefix><SortIcon class="h-4" /></template>
<template v-if="sortValues?.size" #suffix> <template v-if="sortValues?.size" #suffix>
<div <div
@ -33,15 +33,15 @@
:value="sort.fieldname" :value="sort.fieldname"
:options="sortOptions.data" :options="sortOptions.data"
@change="(e) => updateSort(e, i)" @change="(e) => updateSort(e, i)"
placeholder="Sort by" :placeholder="__('First Name')"
/> />
<FormControl <FormControl
class="!w-32" class="!w-32"
type="select" type="select"
v-model="sort.direction" v-model="sort.direction"
:options="[ :options="[
{ label: 'Ascending', value: 'asc' }, { label: __('Ascending'), value: 'asc' },
{ label: 'Descending', value: 'desc' }, { label: __('Descending'), value: 'desc' },
]" ]"
@change=" @change="
(e) => { (e) => {
@ -49,7 +49,7 @@
apply() apply()
} }
" "
placeholder="Sort by" :placeholder="__('Ascending')"
/> />
<Button variant="ghost" icon="x" @click="removeSort(i)" /> <Button variant="ghost" icon="x" @click="removeSort(i)" />
</div> </div>
@ -58,13 +58,13 @@
v-else v-else
class="mb-3 flex h-7 items-center px-3 text-sm text-gray-600" class="mb-3 flex h-7 items-center px-3 text-sm text-gray-600"
> >
Empty - Choose a field to sort by {{ __('Empty - Choose a field to sort by') }}
</div> </div>
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<Autocomplete <Autocomplete
:options="options" :options="options"
value="" value=""
placeholder="Sort by" :placeholder="__('First Name')"
@change="(e) => setSort(e)" @change="(e) => setSort(e)"
> >
<template #target="{ togglePopover }"> <template #target="{ togglePopover }">
@ -72,7 +72,7 @@
class="!text-gray-600" class="!text-gray-600"
variant="ghost" variant="ghost"
@click="togglePopover()" @click="togglePopover()"
label="Add Sort" :label="__('Add Sort')"
> >
<template #prefix> <template #prefix>
<FeatherIcon name="plus" class="h-4" /> <FeatherIcon name="plus" class="h-4" />
@ -84,7 +84,7 @@
v-if="sortValues?.size" v-if="sortValues?.size"
class="!text-gray-600" class="!text-gray-600"
variant="ghost" variant="ghost"
label="Clear Sort" :label="__('Clear Sort')"
@click="clearSort(close)" @click="clearSort(close)"
/> />
</div> </div>

View File

@ -1,29 +1,29 @@
<template> <template>
<Dropdown :options="userDropdownOptions"> <Dropdown :options="dropdownOptions">
<template v-slot="{ open }"> <template v-slot="{ open }">
<button <button
class="flex h-12 py-2 items-center rounded-md duration-300 ease-in-out" class="flex h-12 items-center rounded-md py-2 duration-300 ease-in-out"
:class=" :class="
isCollapsed isCollapsed
? 'px-0 w-auto' ? 'w-auto px-0'
: open : open
? 'bg-white shadow-sm px-2 w-52' ? 'w-52 bg-white px-2 shadow-sm'
: 'hover:bg-gray-200 px-2 w-52' : 'w-52 px-2 hover:bg-gray-200'
" "
> >
<CRMLogo class="w-8 h-8 rounded flex-shrink-0" /> <CRMLogo class="size-8 flex-shrink-0 rounded" />
<div <div
class="flex flex-1 flex-col text-left duration-300 ease-in-out" class="flex flex-1 flex-col text-left duration-300 ease-in-out"
:class=" :class="
isCollapsed isCollapsed
? 'opacity-0 ml-0 w-0 overflow-hidden' ? 'ml-0 w-0 overflow-hidden opacity-0'
: 'opacity-100 ml-2 w-auto' : 'ml-2 w-auto opacity-100'
" "
> >
<div class="text-base font-medium text-gray-900 leading-none"> <div class="text-base font-medium leading-none text-gray-900">
CRM {{ __('CRM') }}
</div> </div>
<div class="mt-1 text-sm text-gray-700 leading-none"> <div class="mt-1 text-sm leading-none text-gray-700">
{{ user.full_name }} {{ user.full_name }}
</div> </div>
</div> </div>
@ -31,11 +31,15 @@
class="duration-300 ease-in-out" class="duration-300 ease-in-out"
:class=" :class="
isCollapsed isCollapsed
? 'opacity-0 ml-0 w-0 overflow-hidden' ? 'ml-0 w-0 overflow-hidden opacity-0'
: 'opacity-100 ml-2 w-auto' : 'ml-2 w-auto opacity-100'
" "
> >
<FeatherIcon name="chevron-down" class="h-4 w-4 text-gray-600" aria-hidden="true" /> <FeatherIcon
name="chevron-down"
class="size-4 text-gray-600"
aria-hidden="true"
/>
</div> </div>
</button> </button>
</template> </template>
@ -47,7 +51,7 @@ import CRMLogo from '@/components/Icons/CRMLogo.vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { Dropdown } from 'frappe-ui' import { Dropdown } from 'frappe-ui'
import { computed } from 'vue' import { computed, ref } from 'vue'
const props = defineProps({ const props = defineProps({
isCollapsed: { isCollapsed: {
@ -61,16 +65,16 @@ const { getUser } = usersStore()
const user = computed(() => getUser() || {}) const user = computed(() => getUser() || {})
const userDropdownOptions = [ let dropdownOptions = ref([
{ {
icon: 'corner-up-left', icon: 'corner-up-left',
label: 'Switch to Desk', label: computed(() => __('Switch to Desk')),
onClick: () => window.location.replace('/app'), onClick: () => window.location.replace('/app'),
}, },
{ {
icon: 'log-out', icon: 'log-out',
label: 'Log out', label: computed(() => __('Log out')),
onClick: () => logout.submit(), onClick: () => logout.submit(),
}, },
] ])
</script> </script>

View File

@ -3,7 +3,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Dropdown :options="viewsDropdownOptions"> <Dropdown :options="viewsDropdownOptions">
<template #default="{ open }"> <template #default="{ open }">
<Button :label="currentView.label"> <Button :label="__(currentView.label)">
<template #prefix> <template #prefix>
<FeatherIcon :name="currentView.icon" class="h-4" /> <FeatherIcon :name="currentView.icon" class="h-4" />
</template> </template>
@ -29,11 +29,11 @@
v-if="viewUpdated && route.query.view && (!view.public || isManager())" v-if="viewUpdated && route.query.view && (!view.public || isManager())"
class="flex items-center gap-2 border-r pr-2" class="flex items-center gap-2 border-r pr-2"
> >
<Button label="Cancel" @click="cancelChanges" /> <Button :label="__('Cancel')" @click="cancelChanges" />
<Button label="Save Changes" @click="saveView" /> <Button :label="__('Save Changes')" @click="saveView" />
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Button label="Refresh" @click="reload()" :loading="isLoading"> <Button :label="__('Refresh')" @click="reload()" :loading="isLoading">
<template #icon> <template #icon>
<RefreshIcon class="h-4 w-4" /> <RefreshIcon class="h-4 w-4" />
</template> </template>
@ -55,11 +55,11 @@
v-if="!options.hideColumnsButton" v-if="!options.hideColumnsButton"
:options="[ :options="[
{ {
group: 'Options', group: __('Options'),
hideLabel: true, hideLabel: true,
items: [ items: [
{ {
label: 'Export', label: __('Export'),
icon: () => icon: () =>
h(FeatherIcon, { name: 'download', class: 'h-4 w-4' }), h(FeatherIcon, { name: 'download', class: 'h-4 w-4' }),
onClick: () => (showExportDialog = true), onClick: () => (showExportDialog = true),
@ -96,10 +96,10 @@
<Dialog <Dialog
v-model="showExportDialog" v-model="showExportDialog"
:options="{ :options="{
title: 'Export', title: __('Export'),
actions: [ actions: [
{ {
label: 'Download', label: __('Download'),
variant: 'solid', variant: 'solid',
onClick: () => exportRows(), onClick: () => exportRows(),
}, },
@ -109,16 +109,25 @@
<template #body-content> <template #body-content>
<FormControl <FormControl
variant="outline" variant="outline"
label="Export Type" :label="__('Export Type')"
type="select" type="select"
:options="['Excel', 'CSV']" :options="[
{
label: __('Excel'),
value: 'Excel',
},
{
label: __('CSV'),
value: 'CSV',
},
]"
v-model="export_type" v-model="export_type"
placeholder="Select Export Type" :placeholder="__('Excel')"
/> />
<div class="mt-3"> <div class="mt-3">
<FormControl <FormControl
type="checkbox" type="checkbox"
:label="`Export All ${list.data.total_count} Records`" :label="__('Export All {0} Record(s)', [list.data.total_count])"
v-model="export_all" v-model="export_all"
/> />
</div> </div>
@ -321,7 +330,7 @@ async function exportRows() {
const defaultViews = [ const defaultViews = [
{ {
label: props.options?.defaultViewName || 'List View', label: __(props.options?.defaultViewName) || __('List View'),
icon: 'list', icon: 'list',
onClick() { onClick() {
viewUpdated.value = false viewUpdated.value = false
@ -333,7 +342,7 @@ const defaultViews = [
const viewsDropdownOptions = computed(() => { const viewsDropdownOptions = computed(() => {
let _views = [ let _views = [
{ {
group: 'Default Views', group: __('Default Views'),
hideLabel: true, hideLabel: true,
items: defaultViews, items: defaultViews,
}, },
@ -341,6 +350,7 @@ const viewsDropdownOptions = computed(() => {
if (list.value?.data?.views) { if (list.value?.data?.views) {
list.value.data.views.forEach((view) => { list.value.data.views.forEach((view) => {
view.label = __(view.label)
view.icon = view.icon || 'list' view.icon = view.icon || 'list'
view.filters = view.filters =
typeof view.filters == 'string' typeof view.filters == 'string'
@ -359,18 +369,18 @@ const viewsDropdownOptions = computed(() => {
publicViews.length && publicViews.length &&
_views.push({ _views.push({
group: 'Public Views', group: __('Public Views'),
items: publicViews, items: publicViews,
}) })
savedViews.length && savedViews.length &&
_views.push({ _views.push({
group: 'Saved Views', group: __('Saved Views'),
items: savedViews, items: savedViews,
}) })
pinnedViews.length && pinnedViews.length &&
_views.push({ _views.push({
group: 'Pinned Views', group: __('Pinned Views'),
items: pinnedViews, items: pinnedViews,
}) })
} }
@ -489,11 +499,11 @@ function updatePageLength(value, loadMore = false) {
const viewActions = computed(() => { const viewActions = computed(() => {
let actions = [ let actions = [
{ {
group: 'Default Views', group: __('Default Views'),
hideLabel: true, hideLabel: true,
items: [ items: [
{ {
label: 'Duplicate', label: __('Duplicate'),
icon: () => h(DuplicateIcon, { class: 'h-4 w-4' }), icon: () => h(DuplicateIcon, { class: 'h-4 w-4' }),
onClick: () => duplicateView(), onClick: () => duplicateView(),
}, },
@ -503,14 +513,14 @@ const viewActions = computed(() => {
if (route.query.view && (!view.value.public || isManager())) { if (route.query.view && (!view.value.public || isManager())) {
actions[0].items.push({ actions[0].items.push({
label: 'Rename', label: __('Rename'),
icon: () => h(EditIcon, { class: 'h-4 w-4' }), icon: () => h(EditIcon, { class: 'h-4 w-4' }),
onClick: () => renameView(), onClick: () => renameView(),
}) })
if (!view.value.public) { if (!view.value.public) {
actions[0].items.push({ actions[0].items.push({
label: view.value.pinned ? 'Unpin View' : 'Pin View', label: view.value.pinned ? __('Unpin View') : __('Pin View'),
icon: () => icon: () =>
h(view.value.pinned ? UnpinIcon : PinIcon, { class: 'h-4 w-4' }), h(view.value.pinned ? UnpinIcon : PinIcon, { class: 'h-4 w-4' }),
onClick: () => pinView(), onClick: () => pinView(),
@ -519,7 +529,7 @@ const viewActions = computed(() => {
if (isManager()) { if (isManager()) {
actions[0].items.push({ actions[0].items.push({
label: view.value.public ? 'Make Private' : 'Make Public', label: view.value.public ? __('Make Private') : __('Make Public'),
icon: () => icon: () =>
h(FeatherIcon, { h(FeatherIcon, {
name: view.value.public ? 'lock' : 'unlock', name: view.value.public ? 'lock' : 'unlock',
@ -530,20 +540,20 @@ const viewActions = computed(() => {
} }
actions.push({ actions.push({
group: 'Delete View', group: __('Delete View'),
hideLabel: true, hideLabel: true,
items: [ items: [
{ {
label: 'Delete', label: __('Delete'),
icon: 'trash-2', icon: 'trash-2',
onClick: () => onClick: () =>
$dialog({ $dialog({
title: 'Delete View', title: __('Delete View'),
message: 'Are you sure you want to delete this view?', message: __('Are you sure you want to delete this view?'),
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => deleteView(close), onClick: (close) => deleteView(close),
@ -558,15 +568,15 @@ const viewActions = computed(() => {
}) })
function duplicateView() { function duplicateView() {
let label = getView(route.query.view)?.label || 'List View' let label = __(getView(route.query.view)?.label) || __('List View')
view.value.name = '' view.value.name = ''
view.value.label = label + ' (New)' view.value.label = label + __(' (New)')
showViewModal.value = true showViewModal.value = true
} }
function renameView() { function renameView() {
view.value.name = route.query.view view.value.name = route.query.view
view.value.label = getView(route.query.view).label view.value.label = __(getView(route.query.view).label)
showViewModal.value = true showViewModal.value = true
} }

View File

@ -19,6 +19,7 @@ import {
frappeRequest, frappeRequest,
FeatherIcon, FeatherIcon,
} from 'frappe-ui' } from 'frappe-ui'
import translationPlugin from './translation'
import { createDialog } from './utils/dialogs' import { createDialog } from './utils/dialogs'
import socket from './socket' import socket from './socket'
import { getCachedListResource } from 'frappe-ui/src/resources/listResource' import { getCachedListResource } from 'frappe-ui/src/resources/listResource'
@ -45,6 +46,7 @@ setConfig('resourceFetcher', frappeRequest)
app.use(FrappeUI) app.use(FrappeUI)
app.use(pinia) app.use(pinia)
app.use(router) app.use(router)
app.use(translationPlugin)
for (let key in globalComponents) { for (let key in globalComponents) {
app.component(key, globalComponents[key]) app.component(key, globalComponents[key])
} }

View File

@ -7,16 +7,15 @@
<Button <Button
v-if="callLog.data.type == 'Incoming' && !callLog.data.lead" v-if="callLog.data.type == 'Incoming' && !callLog.data.lead"
variant="solid" variant="solid"
label="Create lead" :label="__('Create lead')"
@click="createLead" @click="createLead"
> >
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div class="border-b"></div>
<div v-if="callLog.data" class="max-w-lg p-6"> <div v-if="callLog.data" class="max-w-lg p-6">
<div class="pb-3 text-base font-medium">Call details</div> <div class="pb-3 text-base font-medium">{{ __('Call details') }}</div>
<div class="mb-3 flex flex-col gap-4 rounded-lg border p-4 shadow-sm"> <div class="mb-3 flex flex-col gap-4 rounded-lg border p-4 shadow-sm">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -29,7 +28,11 @@
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
/> />
<div class="font-medium"> <div class="font-medium">
{{ callLog.data.type == 'Incoming' ? 'Inbound' : 'Outbound' }} call {{
callLog.data.type == 'Incoming'
? __('Inbound Call')
: __('Outbound Call')
}}
</div> </div>
</div> </div>
<div> <div>
@ -37,7 +40,7 @@
:variant="'subtle'" :variant="'subtle'"
:theme="statusColorMap[callLog.data.status]" :theme="statusColorMap[callLog.data.status]"
size="md" size="md"
:label="statusLabelMap[callLog.data.status]" :label="__(statusLabelMap[callLog.data.status])"
/> />
</div> </div>
</div> </div>
@ -50,7 +53,7 @@
/> />
<div class="ml-1 flex flex-col gap-1"> <div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium"> <div class="text-base font-medium">
{{ callLog.data.caller.label }} {{ __(callLog.data.caller.label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ callLog.data.from }} {{ callLog.data.from }}
@ -64,7 +67,7 @@
/> />
<div class="ml-1 flex flex-col gap-1"> <div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium"> <div class="text-base font-medium">
{{ callLog.data.receiver.label }} {{ __(callLog.data.receiver.label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ callLog.data.to }} {{ callLog.data.to }}
@ -75,19 +78,19 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<DurationIcon class="h-4 w-4 text-gray-600" /> <DurationIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">Duration</div> <div class="text-sm text-gray-600">{{ __('Duration') }}</div>
<div class="text-sm">{{ callLog.data.duration }}</div> <div class="text-sm">{{ callLog.data.duration }}</div>
</div> </div>
<Tooltip :text="dateFormat(callLog.data.creation, dateTooltipFormat)"> <Tooltip :text="dateFormat(callLog.data.creation, dateTooltipFormat)">
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
{{ timeAgo(callLog.data.creation) }} {{ __(timeAgo(callLog.data.creation)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
<div v-if="callLog.data.recording_url" class="mt-6"> <div v-if="callLog.data.recording_url" class="mt-6">
<div class="mb-3 text-base font-medium">Call recording</div> <div class="mb-3 text-base font-medium">{{ __('Call recording') }}</div>
<div class="flex items-center justify-between rounded border shadow-sm"> <div class="flex items-center justify-between rounded border shadow-sm">
<audio <audio
class="audio-control" class="audio-control"
@ -98,7 +101,7 @@
</div> </div>
<div v-if="callLog.data.note" class="mt-6"> <div v-if="callLog.data.note" class="mt-6">
<div class="mb-3 text-base font-medium">Call note</div> <div class="mb-3 text-base font-medium">{{ __('Call note') }}</div>
<div <div
class="flex h-56 cursor-pointer flex-col gap-3 rounded border p-4 shadow-sm" class="flex h-56 cursor-pointer flex-col gap-3 rounded border p-4 shadow-sm"
@click="showNoteModal = true" @click="showNoteModal = true"
@ -117,7 +120,7 @@
</div> </div>
<div v-if="callLog.data.lead" class="mt-6"> <div v-if="callLog.data.lead" class="mt-6">
<div class="mb-3 text-base font-medium">Lead</div> <div class="mb-3 text-base font-medium">{{ __('Lead') }}</div>
<Button <Button
variant="outline" variant="outline"
@ -206,7 +209,7 @@ function createLead() {
} }
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Call Logs', route: { name: 'Call Logs' } }] let items = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
items.push({ items.push({
label: callLog.data?.caller.label, label: callLog.data?.caller.label,
route: { name: 'Call Log', params: { callLogId: props.callLogId } }, route: { name: 'Call Log', params: { callLogId: props.callLogId } },

View File

@ -44,7 +44,7 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<PhoneIcon class="h-10 w-10" /> <PhoneIcon class="h-10 w-10" />
<span>No Logs Found</span> <span>{{ __('No Logs Found') }}</span>
</div> </div>
</div> </div>
</template> </template>
@ -69,7 +69,7 @@ import { computed, ref } from 'vue'
const { getUser } = usersStore() const { getUser } = usersStore()
const { getContact, getLeadContact } = contactsStore() const { getContact, getLeadContact } = contactsStore()
const breadcrumbs = [{ label: 'Call Logs', route: { name: 'Call Logs' } }] const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
const callLogsListView = ref(null) const callLogsListView = ref(null)
@ -131,7 +131,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) { } else if (['modified', 'creation'].includes(row)) {
_rows[row] = { _rows[row] = {
label: dateFormat(callLog[row], dateTooltipFormat), label: dateFormat(callLog[row], dateTooltipFormat),
timeAgo: timeAgo(callLog[row]), timeAgo: __(timeAgo(callLog[row])),
} }
} }
}) })

View File

@ -24,13 +24,13 @@
{ {
icon: 'upload', icon: 'upload',
label: contact.data.image label: contact.data.image
? 'Change image' ? __('Change image')
: 'Upload image', : __('Upload image'),
onClick: openFileSelector, onClick: openFileSelector,
}, },
{ {
icon: 'trash-2', icon: 'trash-2',
label: 'Remove image', label: __('Remove image'),
onClick: () => changeContactImage(''), onClick: () => changeContactImage(''),
}, },
], ],
@ -71,7 +71,10 @@
> >
&middot; &middot;
</span> </span>
<Tooltip text="Make Call" v-if="contact.data.actual_mobile_no"> <Tooltip
:text="__('Make Call')"
v-if="contact.data.actual_mobile_no"
>
<div <div
class="flex cursor-pointer items-center gap-1.5" class="flex cursor-pointer items-center gap-1.5"
@click="makeCall(contact.data.actual_mobile_no)" @click="makeCall(contact.data.actual_mobile_no)"
@ -113,7 +116,7 @@
contact.data.company_name contact.data.company_name
" "
variant="ghost" variant="ghost"
label="More" :label="__('More')"
class="-ml-1 cursor-pointer hover:text-gray-900" class="-ml-1 cursor-pointer hover:text-gray-900"
@click=" @click="
() => { () => {
@ -125,7 +128,7 @@
</div> </div>
<div class="mt-2 flex gap-1.5"> <div class="mt-2 flex gap-1.5">
<Button <Button
label="Edit" :label="__('Edit')"
size="sm" size="sm"
@click=" @click="
() => { () => {
@ -139,7 +142,7 @@
</template> </template>
</Button> </Button>
<Button <Button
label="Delete" :label="__('Delete')"
theme="red" theme="red"
size="sm" size="sm"
@click="deleteContact" @click="deleteContact"
@ -149,7 +152,7 @@
</template> </template>
</Button> </Button>
</div> </div>
<ErrorMessage :message="error" /> <ErrorMessage :message="__(error)" />
</div> </div>
</div> </div>
</template> </template>
@ -161,7 +164,7 @@
:class="{ 'text-gray-900': selected }" :class="{ 'text-gray-900': selected }"
> >
<component v-if="tab.icon" :is="tab.icon" class="h-5" /> <component v-if="tab.icon" :is="tab.icon" class="h-5" />
{{ tab.label }} {{ __(tab.label) }}
<Badge <Badge
class="group-hover:bg-gray-900" class="group-hover:bg-gray-900"
:class="[selected ? 'bg-gray-900' : 'bg-gray-600']" :class="[selected ? 'bg-gray-900' : 'bg-gray-600']"
@ -187,7 +190,7 @@
> >
<div class="flex flex-col items-center justify-center space-y-3"> <div class="flex flex-col items-center justify-center space-y-3">
<component :is="tab.icon" class="!h-10 !w-10" /> <component :is="tab.icon" class="!h-10 !w-10" />
<div>No {{ tab.label }} Found</div> <div>{{ __('No {0} Found', [__(tab.label)]) }}</div>
</div> </div>
</div> </div>
</template> </template>
@ -251,7 +254,7 @@ const showContactModal = ref(false)
const detailMode = ref(false) const detailMode = ref(false)
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }] let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
items.push({ items.push({
label: contact.data?.full_name, label: contact.data?.full_name,
route: { name: 'Contact', params: { contactId: props.contactId } }, route: { name: 'Contact', params: { contactId: props.contactId } },
@ -262,7 +265,7 @@ const breadcrumbs = computed(() => {
function validateFile(file) { function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase() let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) { if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed' return __('Only PNG and JPG images are allowed')
} }
} }
@ -278,11 +281,11 @@ async function changeContactImage(file) {
async function deleteContact() { async function deleteContact() {
$dialog({ $dialog({
title: 'Delete contact', title: __('Delete contact'),
message: 'Are you sure you want to delete this contact?', message: __('Are you sure you want to delete this contact?'),
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
theme: 'red', theme: 'red',
variant: 'solid', variant: 'solid',
async onClick(close) { async onClick(close) {
@ -360,44 +363,44 @@ function getDealRowObject(deal) {
}, },
modified: { modified: {
label: dateFormat(deal.modified, dateTooltipFormat), label: dateFormat(deal.modified, dateTooltipFormat),
timeAgo: timeAgo(deal.modified), timeAgo: __(timeAgo(deal.modified)),
}, },
} }
} }
const dealColumns = [ const dealColumns = [
{ {
label: 'Organization', label: __('Organization'),
key: 'organization', key: 'organization',
width: '11rem', width: '11rem',
}, },
{ {
label: 'Amount', label: __('Amount'),
key: 'annual_revenue', key: 'annual_revenue',
width: '9rem', width: '9rem',
}, },
{ {
label: 'Status', label: __('Status'),
key: 'status', key: 'status',
width: '10rem', width: '10rem',
}, },
{ {
label: 'Email', label: __('Email'),
key: 'email', key: 'email',
width: '12rem', width: '12rem',
}, },
{ {
label: 'Mobile no', label: __('Mobile no'),
key: 'mobile_no', key: 'mobile_no',
width: '11rem', width: '11rem',
}, },
{ {
label: 'Deal owner', label: __('Deal owner'),
key: 'deal_owner', key: 'deal_owner',
width: '10rem', width: '10rem',
}, },
{ {
label: 'Last modified', label: __('Last modified'),
key: 'modified', key: 'modified',
width: '8rem', width: '8rem',
}, },

View File

@ -8,7 +8,11 @@
v-if="contactsListView?.customListActions" v-if="contactsListView?.customListActions"
:actions="contactsListView.customListActions" :actions="contactsListView.customListActions"
/> />
<Button variant="solid" label="Create" @click="showContactModal = true"> <Button
variant="solid"
:label="__('Create')"
@click="showContactModal = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -47,8 +51,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<ContactsIcon class="h-10 w-10" /> <ContactsIcon class="h-10 w-10" />
<span>No Contacts Found</span> <span>{{ __('No Contacts Found') }}</span>
<Button label="Create" @click="showContactModal = true"> <Button :label="__('Create')" @click="showContactModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</div> </div>
@ -81,10 +85,10 @@ const currentContact = computed(() => {
}) })
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }] let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
if (!currentContact.value) return items if (!currentContact.value) return items
items.push({ items.push({
label: currentContact.value.full_name, label: __(currentContact.value.full_name),
route: { route: {
name: 'Contact', name: 'Contact',
params: { contactId: currentContact.value.name }, params: { contactId: currentContact.value.name },
@ -123,7 +127,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) { } else if (['modified', 'creation'].includes(row)) {
_rows[row] = { _rows[row] = {
label: dateFormat(contact[row], dateTooltipFormat), label: dateFormat(contact[row], dateTooltipFormat),
timeAgo: timeAgo(contact[row]), timeAgo: __(timeAgo(contact[row])),
} }
} }
}) })

View File

@ -39,7 +39,7 @@
<Activities <Activities
ref="activities" ref="activities"
doctype="CRM Deal" doctype="CRM Deal"
:title="tab.label" :title="tab.name"
v-model:reload="reload" v-model:reload="reload"
v-model="deal" v-model="deal"
/> />
@ -48,10 +48,10 @@
<div <div
class="flex h-10.5 items-center border-b px-5 py-2.5 text-lg font-semibold" class="flex h-10.5 items-center border-b px-5 py-2.5 text-lg font-semibold"
> >
About this Deal {{ __('About this Deal') }}
</div> </div>
<div class="flex items-center justify-start gap-5 border-b p-5"> <div class="flex items-center justify-start gap-5 border-b p-5">
<Tooltip text="Organization logo"> <Tooltip :text="__('Organization logo')">
<div class="group relative h-[88px] w-[88px]"> <div class="group relative h-[88px] w-[88px]">
<Avatar <Avatar
size="3xl" size="3xl"
@ -68,31 +68,31 @@
</div> </div>
</Tooltip> </Tooltip>
<div class="flex gap-1.5"> <div class="flex gap-1.5">
<Tooltip text="Make a call"> <Tooltip :text="__('Make a call')">
<Button class="h-7 w-7" @click="triggerCall"> <Button class="h-7 w-7" @click="triggerCall">
<PhoneIcon class="h-4 w-4" /> <PhoneIcon class="h-4 w-4" />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip text="Send an email"> <Tooltip :text="__('Send an email')">
<Button class="h-7 w-7"> <Button class="h-7 w-7">
<EmailIcon <EmailIcon
class="h-4 w-4" class="h-4 w-4"
@click=" @click="
deal.data.email deal.data.email
? openEmailBox() ? openEmailBox()
: errorMessage('No email set') : errorMessage(__('No email set'))
" "
/> />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip text="Go to website"> <Tooltip :text="__('Go to website')">
<Button class="h-7 w-7"> <Button class="h-7 w-7">
<LinkIcon <LinkIcon
class="h-4 w-4" class="h-4 w-4"
@click=" @click="
deal.data.website deal.data.website
? openWebsite(deal.data.website) ? openWebsite(deal.data.website)
: errorMessage('No website set') : errorMessage(__('No website set'))
" "
/> />
</Button> </Button>
@ -137,7 +137,7 @@
<template #target="{ togglePopover }"> <template #target="{ togglePopover }">
<Button <Button
class="h-7 px-3" class="h-7 px-3"
label="Add Contact" :label="__('Add Contact')"
@click="togglePopover()" @click="togglePopover()"
> >
<template #prefix> <template #prefix>
@ -162,7 +162,7 @@
class="flex min-h-20 flex-1 items-center justify-center gap-3 text-base text-gray-500" class="flex min-h-20 flex-1 items-center justify-center gap-3 text-base text-gray-500"
> >
<LoadingIndicator class="h-4 w-4" /> <LoadingIndicator class="h-4 w-4" />
<span>Loading...</span> <span>{{ __('Loading...') }}</span>
</div> </div>
<div <div
v-else-if="section.contacts.length" v-else-if="section.contacts.length"
@ -194,7 +194,7 @@
v-if="contact.is_primary" v-if="contact.is_primary"
class="ml-2" class="ml-2"
variant="outline" variant="outline"
label="Primary" :label="__('Primary')"
theme="green" theme="green"
/> />
</div> </div>
@ -251,7 +251,7 @@
v-else v-else
class="flex h-20 items-center justify-center text-base text-gray-600" class="flex h-20 items-center justify-center text-base text-gray-600"
> >
No contacts added {{ __('No contacts added') }}
</div> </div>
</div> </div>
</Section> </Section>
@ -392,7 +392,7 @@ function updateDeal(fieldname, value, callback) {
deal.reload() deal.reload()
reload.value = true reload.value = true
createToast({ createToast({
title: 'Deal updated', title: __('Deal updated'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -400,8 +400,8 @@ function updateDeal(fieldname, value, callback) {
}, },
onError: (err) => { onError: (err) => {
createToast({ createToast({
title: 'Error updating deal', title: __('Error updating deal'),
text: err.messages?.[0], text: __(err.messages?.[0]),
icon: 'x', icon: 'x',
iconClasses: 'text-red-600', iconClasses: 'text-red-600',
}) })
@ -413,8 +413,8 @@ function validateRequired(fieldname, value) {
let meta = deal.data.all_fields || {} let meta = deal.data.all_fields || {}
if (meta[fieldname]?.reqd && !value) { if (meta[fieldname]?.reqd && !value) {
createToast({ createToast({
title: 'Error Updating Deal', title: __('Error Updating Deal'),
text: `${meta[fieldname].label} is a required field`, text: __('{0} is a required field', [meta[fieldname].label]),
icon: 'x', icon: 'x',
iconClasses: 'text-red-600', iconClasses: 'text-red-600',
}) })
@ -424,7 +424,7 @@ function validateRequired(fieldname, value) {
} }
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Deals', route: { name: 'Deals' } }] let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
items.push({ items.push({
label: organization.value?.name, label: organization.value?.name,
route: { name: 'Deal', params: { dealId: deal.data.name } }, route: { name: 'Deal', params: { dealId: deal.data.name } },
@ -435,22 +435,27 @@ const breadcrumbs = computed(() => {
const tabIndex = ref(0) const tabIndex = ref(0)
const tabs = [ const tabs = [
{ {
name: 'Activity',
label: 'Activity', label: 'Activity',
icon: ActivityIcon, icon: ActivityIcon,
}, },
{ {
name: 'Emails',
label: 'Emails', label: 'Emails',
icon: EmailIcon, icon: EmailIcon,
}, },
{ {
name: 'Calls',
label: 'Calls', label: 'Calls',
icon: PhoneIcon, icon: PhoneIcon,
}, },
{ {
name: 'Tasks',
label: 'Tasks', label: 'Tasks',
icon: TaskIcon, icon: TaskIcon,
}, },
{ {
name: 'Notes',
label: 'Notes', label: 'Notes',
icon: NoteIcon, icon: NoteIcon,
}, },
@ -504,7 +509,7 @@ const _contact = ref({})
function contactOptions(contact) { function contactOptions(contact) {
let options = [ let options = [
{ {
label: 'Delete', label: __('Delete'),
icon: 'trash-2', icon: 'trash-2',
onClick: () => removeContact(contact), onClick: () => removeContact(contact),
}, },
@ -512,7 +517,7 @@ function contactOptions(contact) {
if (!contact.is_primary) { if (!contact.is_primary) {
options.push({ options.push({
label: 'Set as Primary Contact', label: __('Set as Primary Contact'),
icon: h(SuccessIcon, { class: 'h-4 w-4' }), icon: h(SuccessIcon, { class: 'h-4 w-4' }),
onClick: () => setPrimaryContact(contact), onClick: () => setPrimaryContact(contact),
}) })
@ -529,7 +534,7 @@ async function addContact(contact) {
if (d) { if (d) {
deal_contacts.reload() deal_contacts.reload()
createToast({ createToast({
title: 'Contact added', title: __('Contact added'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -544,7 +549,7 @@ async function removeContact(contact) {
if (d) { if (d) {
deal_contacts.reload() deal_contacts.reload()
createToast({ createToast({
title: 'Contact removed', title: __('Contact removed'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -559,7 +564,7 @@ async function setPrimaryContact(contact) {
if (d) { if (d) {
deal_contacts.reload() deal_contacts.reload()
createToast({ createToast({
title: 'Primary contact set', title: __('Primary contact set'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -578,12 +583,12 @@ function triggerCall() {
let mobile_no = primaryContact.mobile_no || null let mobile_no = primaryContact.mobile_no || null
if (!primaryContact) { if (!primaryContact) {
errorMessage('No primary contact set') errorMessage(__('No primary contact set'))
return return
} }
if (!mobile_no) { if (!mobile_no) {
errorMessage('No mobile number set') errorMessage(__('No mobile number set'))
return return
} }

View File

@ -8,7 +8,11 @@
v-if="dealsListView?.customListActions" v-if="dealsListView?.customListActions"
:actions="dealsListView.customListActions" :actions="dealsListView.customListActions"
/> />
<Button variant="solid" label="Create" @click="showNewDialog = true"> <Button
variant="solid"
:label="__('Create')"
@click="showNewDialog = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -44,8 +48,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<DealsIcon class="h-10 w-10" /> <DealsIcon class="h-10 w-10" />
<span>No Deals Found</span> <span>{{ __('No Deals Found') }}</span>
<Button label="Create" @click="showNewDialog = true"> <Button :label="__('Create')" @click="showNewDialog = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</div> </div>
@ -54,8 +58,7 @@
v-model="showNewDialog" v-model="showNewDialog"
:options="{ :options="{
size: '3xl', size: '3xl',
title: 'New Deal', title: __('New Deal'),
actions: [{ label: 'Save', variant: 'solid' }],
}" }"
> >
<template #body-content> <template #body-content>
@ -63,7 +66,11 @@
</template> </template>
<template #actions="{ close }"> <template #actions="{ close }">
<div class="flex flex-row-reverse gap-2"> <div class="flex flex-row-reverse gap-2">
<Button variant="solid" label="Save" @click="createNewDeal(close)" /> <Button
variant="solid"
:label="__('Save')"
@click="createNewDeal(close)"
/>
</div> </div>
</template> </template>
</Dialog> </Dialog>
@ -85,12 +92,13 @@ import {
timeAgo, timeAgo,
formatNumberIntoCurrency, formatNumberIntoCurrency,
formatTime, formatTime,
createToast,
} from '@/utils' } from '@/utils'
import { createResource, Breadcrumbs } from 'frappe-ui' import { createResource, Breadcrumbs } from 'frappe-ui'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ref, computed, reactive } from 'vue' import { ref, computed, reactive } from 'vue'
const breadcrumbs = [{ label: 'Deals', route: { name: 'Deals' } }] const breadcrumbs = [{ label: __('Deals'), route: { name: 'Deals' } }]
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore() const { getOrganization } = organizationsStore()
@ -139,7 +147,7 @@ const rows = computed(() => {
? 'green' ? 'green'
: 'orange' : 'orange'
if (value == 'First Response Due') { if (value == 'First Response Due') {
value = timeAgo(deal.response_by) value = __(timeAgo(deal.response_by))
tooltipText = dateFormat(deal.response_by, dateTooltipFormat) tooltipText = dateFormat(deal.response_by, dateTooltipFormat)
if (new Date(deal.response_by) < new Date()) { if (new Date(deal.response_by) < new Date()) {
color = 'red' color = 'red'
@ -168,7 +176,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) { } else if (['modified', 'creation'].includes(row)) {
_rows[row] = { _rows[row] = {
label: dateFormat(deal[row], dateTooltipFormat), label: dateFormat(deal[row], dateTooltipFormat),
timeAgo: timeAgo(deal[row]), timeAgo: __(timeAgo(deal[row])),
} }
} else if ( } else if (
['first_response_time', 'first_responded_on', 'response_by'].includes( ['first_response_time', 'first_responded_on', 'response_by'].includes(
@ -181,7 +189,7 @@ const rows = computed(() => {
timeAgo: deal[row] timeAgo: deal[row]
? row == 'first_response_time' ? row == 'first_response_time'
? formatTime(deal[row]) ? formatTime(deal[row])
: timeAgo(deal[row]) : __(timeAgo(deal[row]))
: '', : '',
} }
} }
@ -218,7 +226,13 @@ function createNewDeal(close) {
.submit(newDeal, { .submit(newDeal, {
validate() { validate() {
if (!newDeal.first_name) { if (!newDeal.first_name) {
return 'First name is required' createToast({
title: __('Error creating deal'),
text: __('First name is required'),
icon: 'x',
iconClasses: 'text-red-600',
})
return __('First name is required')
} }
}, },
onSuccess(data) { onSuccess(data) {

View File

@ -8,7 +8,11 @@
v-if="emailTemplatesListView?.customListActions" v-if="emailTemplatesListView?.customListActions"
:actions="emailTemplatesListView.customListActions" :actions="emailTemplatesListView.customListActions"
/> />
<Button variant="solid" label="Create" @click="showEmailTemplateModal = true"> <Button
variant="solid"
:label="__('Create')"
@click="showEmailTemplateModal = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -48,7 +52,10 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<EmailIcon class="h-10 w-10" /> <EmailIcon class="h-10 w-10" />
<span>No Email Templates Found</span> <span>{{ __('No Email Templates Found') }}</span>
<Button :label="__('Create')" @click="showEmailTemplateModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div> </div>
</div> </div>
<EmailTemplateModal <EmailTemplateModal
@ -70,7 +77,7 @@ import { Breadcrumbs } from 'frappe-ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
const breadcrumbs = [ const breadcrumbs = [
{ label: 'Email Templates', route: { name: 'Email Templates' } }, { label: __('Email Templates'), route: { name: 'Email Templates' } },
] ]
const emailTemplatesListView = ref(null) const emailTemplatesListView = ref(null)
@ -103,12 +110,12 @@ const rows = computed(() => {
const showEmailTemplateModal = ref(false) const showEmailTemplateModal = ref(false)
const emailTemplate = ref({ const emailTemplate = ref({
subject: '', subject: '',
response: '', response: '',
name: '', name: '',
enabled: 1, enabled: 1,
owner: '', owner: '',
reference_doctype: 'CRM Deal', reference_doctype: 'CRM Deal',
}) })
function showEmailTemplate(name) { function showEmailTemplate(name) {

View File

@ -33,7 +33,7 @@
</template> </template>
</Dropdown> </Dropdown>
<Button <Button
label="Convert to Deal" :label="__('Convert to Deal')"
variant="solid" variant="solid"
@click="showConvertToDealModal = true" @click="showConvertToDealModal = true"
/> />
@ -44,7 +44,7 @@
<Activities <Activities
ref="activities" ref="activities"
doctype="CRM Lead" doctype="CRM Lead"
:title="tab.label" :title="tab.name"
v-model:reload="reload" v-model:reload="reload"
v-model="lead" v-model="lead"
/> />
@ -53,7 +53,7 @@
<div <div
class="flex h-10.5 items-center border-b px-5 py-2.5 text-lg font-semibold" class="flex h-10.5 items-center border-b px-5 py-2.5 text-lg font-semibold"
> >
About this Lead {{ __('About this Lead') }}
</div> </div>
<FileUploader <FileUploader
@success="(file) => updateField('image', file.file_url)" @success="(file) => updateField('image', file.file_url)"
@ -77,13 +77,13 @@
{ {
icon: 'upload', icon: 'upload',
label: lead.data.image label: lead.data.image
? 'Change image' ? __('Change image')
: 'Upload image', : __('Upload image'),
onClick: openFileSelector, onClick: openFileSelector,
}, },
{ {
icon: 'trash-2', icon: 'trash-2',
label: 'Remove image', label: __('Remove image'),
onClick: () => updateField('image', ''), onClick: () => updateField('image', ''),
}, },
], ],
@ -110,45 +110,45 @@
</div> </div>
</Tooltip> </Tooltip>
<div class="flex gap-1.5"> <div class="flex gap-1.5">
<Tooltip text="Make a call"> <Tooltip :text="__('Make a call')">
<Button <Button
class="h-7 w-7" class="h-7 w-7"
@click=" @click="
() => () =>
lead.data.mobile_no lead.data.mobile_no
? makeCall(lead.data.mobile_no) ? makeCall(lead.data.mobile_no)
: errorMessage('No phone number set') : errorMessage(__('No phone number set'))
" "
> >
<PhoneIcon class="h-4 w-4" /> <PhoneIcon class="h-4 w-4" />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip text="Send an email"> <Tooltip :text="__('Send an email')">
<Button class="h-7 w-7"> <Button class="h-7 w-7">
<EmailIcon <EmailIcon
class="h-4 w-4" class="h-4 w-4"
@click=" @click="
lead.data.email lead.data.email
? openEmailBox() ? openEmailBox()
: errorMessage('No email set') : errorMessage(__('No email set'))
" "
/> />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip text="Go to website"> <Tooltip :text="__('Go to website')">
<Button class="h-7 w-7"> <Button class="h-7 w-7">
<LinkIcon <LinkIcon
class="h-4 w-4" class="h-4 w-4"
@click=" @click="
lead.data.website lead.data.website
? openWebsite(lead.data.website) ? openWebsite(lead.data.website)
: errorMessage('No website set') : errorMessage(__('No website set'))
" "
/> />
</Button> </Button>
</Tooltip> </Tooltip>
</div> </div>
<ErrorMessage :message="error" /> <ErrorMessage :message="__(error)" />
</div> </div>
</div> </div>
</template> </template>
@ -190,11 +190,11 @@
<Dialog <Dialog
v-model="showConvertToDealModal" v-model="showConvertToDealModal"
:options="{ :options="{
title: 'Convert to Deal', title: __('Convert to Deal'),
size: 'xl', size: 'xl',
actions: [ actions: [
{ {
label: 'Convert', label: __('Convert'),
variant: 'solid', variant: 'solid',
onClick: convertToDeal, onClick: convertToDeal,
}, },
@ -204,11 +204,11 @@
<template #body-content> <template #body-content>
<div class="mb-4 flex items-center gap-2 text-gray-600"> <div class="mb-4 flex items-center gap-2 text-gray-600">
<OrganizationsIcon class="h-4 w-4" /> <OrganizationsIcon class="h-4 w-4" />
<label class="block text-base"> Organization </label> <label class="block text-base">{{ __('Organization') }}</label>
</div> </div>
<div class="ml-6"> <div class="ml-6">
<div class="flex items-center justify-between text-base"> <div class="flex items-center justify-between text-base">
<div>Choose Existing</div> <div>{{ __('Choose Existing') }}</div>
<Switch v-model="existingOrganizationChecked" /> <Switch v-model="existingOrganizationChecked" />
</div> </div>
<Link <Link
@ -221,17 +221,21 @@
@change="(data) => (existingOrganization = data)" @change="(data) => (existingOrganization = data)"
/> />
<div v-else class="mt-2.5 text-base"> <div v-else class="mt-2.5 text-base">
New organization will be created based on the data in details section {{
__(
'New organization will be created based on the data in details section'
)
}}
</div> </div>
</div> </div>
<div class="mb-4 mt-6 flex items-center gap-2 text-gray-600"> <div class="mb-4 mt-6 flex items-center gap-2 text-gray-600">
<ContactsIcon class="h-4 w-4" /> <ContactsIcon class="h-4 w-4" />
<label class="block text-base"> Contact </label> <label class="block text-base">{{ __('Contact') }}</label>
</div> </div>
<div class="ml-6"> <div class="ml-6">
<div class="flex items-center justify-between text-base"> <div class="flex items-center justify-between text-base">
<div>Choose Existing</div> <div>{{ __('Choose Existing') }}</div>
<Switch v-model="existingContactChecked" /> <Switch v-model="existingContactChecked" />
</div> </div>
<Link <Link
@ -244,7 +248,7 @@
@change="(data) => (existingContact = data)" @change="(data) => (existingContact = data)"
/> />
<div v-else class="mt-2.5 text-base"> <div v-else class="mt-2.5 text-base">
New contact will be created based on the person's details {{ __("New contact will be created based on the person's details") }}
</div> </div>
</div> </div>
</template> </template>
@ -352,7 +356,7 @@ function updateLead(fieldname, value, callback) {
lead.reload() lead.reload()
reload.value = true reload.value = true
createToast({ createToast({
title: 'Lead updated', title: __('Lead updated'),
icon: 'check', icon: 'check',
iconClasses: 'text-green-600', iconClasses: 'text-green-600',
}) })
@ -360,8 +364,8 @@ function updateLead(fieldname, value, callback) {
}, },
onError: (err) => { onError: (err) => {
createToast({ createToast({
title: 'Error updating lead', title: __('Error updating lead'),
text: err.messages?.[0], text: __(err.messages?.[0]),
icon: 'x', icon: 'x',
iconClasses: 'text-red-600', iconClasses: 'text-red-600',
}) })
@ -373,8 +377,8 @@ function validateRequired(fieldname, value) {
let meta = lead.data.all_fields || {} let meta = lead.data.all_fields || {}
if (meta[fieldname]?.reqd && !value) { if (meta[fieldname]?.reqd && !value) {
createToast({ createToast({
title: 'Error Updating Lead', title: __('Error Updating Lead'),
text: `${meta[fieldname].label} is a required field`, text: __('{0} is a required field', [meta[fieldname].label]),
icon: 'x', icon: 'x',
iconClasses: 'text-red-600', iconClasses: 'text-red-600',
}) })
@ -384,7 +388,7 @@ function validateRequired(fieldname, value) {
} }
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Leads', route: { name: 'Leads' } }] let items = [{ label: __('Leads'), route: { name: 'Leads' } }]
items.push({ items.push({
label: lead.data.lead_name, label: lead.data.lead_name,
route: { name: 'Lead', params: { leadId: lead.data.name } }, route: { name: 'Lead', params: { leadId: lead.data.name } },
@ -395,23 +399,28 @@ const breadcrumbs = computed(() => {
const tabIndex = ref(0) const tabIndex = ref(0)
const tabs = [ const tabs = [
{ {
label: 'Activity', name: 'Activity',
label: __('Activity'),
icon: ActivityIcon, icon: ActivityIcon,
}, },
{ {
label: 'Emails', name: 'Emails',
label: __('Emails'),
icon: EmailIcon, icon: EmailIcon,
}, },
{ {
label: 'Calls', name: 'Calls',
label: __('Calls'),
icon: PhoneIcon, icon: PhoneIcon,
}, },
{ {
label: 'Tasks', name: 'Tasks',
label: __('Tasks'),
icon: TaskIcon, icon: TaskIcon,
}, },
{ {
label: 'Notes', name: 'Notes',
label: __('Notes'),
icon: NoteIcon, icon: NoteIcon,
}, },
] ]
@ -419,7 +428,7 @@ const tabs = [
function validateFile(file) { function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase() let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) { if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed' return __('Only PNG and JPG images are allowed')
} }
} }
@ -457,8 +466,8 @@ async function convertToDeal(updated) {
if (existingContactChecked.value && !existingContact.value) { if (existingContactChecked.value && !existingContact.value) {
createToast({ createToast({
title: 'Error', title: __('Error'),
text: 'Please select an existing contact', text: __('Please select an existing contact'),
icon: 'x', icon: 'x',
iconClasses: 'text-red-600', iconClasses: 'text-red-600',
}) })
@ -467,8 +476,8 @@ async function convertToDeal(updated) {
if (existingOrganizationChecked.value && !existingOrganization.value) { if (existingOrganizationChecked.value && !existingOrganization.value) {
createToast({ createToast({
title: 'Error', title: __('Error'),
text: 'Please select an existing organization', text: __('Please select an existing organization'),
icon: 'x', icon: 'x',
iconClasses: 'text-red-600', iconClasses: 'text-red-600',
}) })

View File

@ -8,7 +8,11 @@
v-if="leadsListView?.customListActions" v-if="leadsListView?.customListActions"
:actions="leadsListView.customListActions" :actions="leadsListView.customListActions"
/> />
<Button variant="solid" label="Create" @click="showNewDialog = true"> <Button
variant="solid"
:label="__('Create')"
@click="showNewDialog = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -45,8 +49,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<LeadsIcon class="h-10 w-10" /> <LeadsIcon class="h-10 w-10" />
<span>No Leads Found</span> <span>{{ __('No Leads Found') }}</span>
<Button label="Create" @click="showNewDialog = true"> <Button :label="__('Create')" @click="showNewDialog = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</div> </div>
@ -55,8 +59,7 @@
v-model="showNewDialog" v-model="showNewDialog"
:options="{ :options="{
size: '3xl', size: '3xl',
title: 'New Lead', title: __('New Lead'),
actions: [{ label: 'Save', variant: 'solid' }],
}" }"
> >
<template #body-content> <template #body-content>
@ -64,7 +67,11 @@
</template> </template>
<template #actions="{ close }"> <template #actions="{ close }">
<div class="flex flex-row-reverse gap-2"> <div class="flex flex-row-reverse gap-2">
<Button variant="solid" label="Save" @click="createNewLead(close)" /> <Button
variant="solid"
:label="__('Save')"
@click="createNewLead(close)"
/>
</div> </div>
</template> </template>
</Dialog> </Dialog>
@ -80,12 +87,18 @@ import ViewControls from '@/components/ViewControls.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations' import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { dateFormat, dateTooltipFormat, timeAgo, formatTime } from '@/utils' import {
dateFormat,
dateTooltipFormat,
timeAgo,
formatTime,
createToast,
} from '@/utils'
import { createResource, Breadcrumbs } from 'frappe-ui' import { createResource, Breadcrumbs } from 'frappe-ui'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ref, computed, reactive } from 'vue' import { ref, computed, reactive } from 'vue'
const breadcrumbs = [{ label: 'Leads', route: { name: 'Leads' } }] const breadcrumbs = [{ label: __('Leads'), route: { name: 'Leads' } }]
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore() const { getOrganization } = organizationsStore()
@ -136,7 +149,7 @@ const rows = computed(() => {
? 'green' ? 'green'
: 'orange' : 'orange'
if (value == 'First Response Due') { if (value == 'First Response Due') {
value = timeAgo(lead.response_by) value = __(timeAgo(lead.response_by))
tooltipText = dateFormat(lead.response_by, dateTooltipFormat) tooltipText = dateFormat(lead.response_by, dateTooltipFormat)
if (new Date(lead.response_by) < new Date()) { if (new Date(lead.response_by) < new Date()) {
color = 'red' color = 'red'
@ -165,7 +178,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) { } else if (['modified', 'creation'].includes(row)) {
_rows[row] = { _rows[row] = {
label: dateFormat(lead[row], dateTooltipFormat), label: dateFormat(lead[row], dateTooltipFormat),
timeAgo: timeAgo(lead[row]), timeAgo: __(timeAgo(lead[row])),
} }
} else if ( } else if (
['first_response_time', 'first_responded_on', 'response_by'].includes( ['first_response_time', 'first_responded_on', 'response_by'].includes(
@ -178,7 +191,7 @@ const rows = computed(() => {
timeAgo: lead[row] timeAgo: lead[row]
? row == 'first_response_time' ? row == 'first_response_time'
? formatTime(lead[row]) ? formatTime(lead[row])
: timeAgo(lead[row]) : __(timeAgo(lead[row]))
: '', : '',
} }
} }
@ -219,7 +232,13 @@ function createNewLead(close) {
.submit(newLead, { .submit(newLead, {
validate() { validate() {
if (!newLead.first_name) { if (!newLead.first_name) {
return 'First name is required' createToast({
title: __('Error creating lead'),
text: __('First name is required'),
icon: 'x',
iconClasses: 'text-red-600',
})
return __('First name is required')
} }
}, },
onSuccess(data) { onSuccess(data) {

View File

@ -4,7 +4,7 @@
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
</template> </template>
<template #right-header> <template #right-header>
<Button variant="solid" label="Create" @click="createNote"> <Button variant="solid" :label="__('Create')" @click="createNote">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -16,7 +16,7 @@
doctype="FCRM Note" doctype="FCRM Note"
:options="{ :options="{
hideColumnsButton: true, hideColumnsButton: true,
defaultViewName: 'Notes View', defaultViewName: __('Notes View'),
}" }"
/> />
<div class="flex-1 overflow-y-auto"> <div class="flex-1 overflow-y-auto">
@ -36,8 +36,8 @@
<Dropdown <Dropdown
:options="[ :options="[
{ {
label: __('Delete'),
icon: 'trash-2', icon: 'trash-2',
label: 'Delete',
onClick: () => deleteNote(note.name), onClick: () => deleteNote(note.name),
}, },
]" ]"
@ -66,7 +66,7 @@
</div> </div>
<Tooltip :text="dateFormat(note.modified, dateTooltipFormat)"> <Tooltip :text="dateFormat(note.modified, dateTooltipFormat)">
<div class="text-sm text-gray-700"> <div class="text-sm text-gray-700">
{{ timeAgo(note.modified) }} {{ __(timeAgo(note.modified)) }}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -88,8 +88,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<NoteIcon class="h-10 w-10" /> <NoteIcon class="h-10 w-10" />
<span>No Notes Found</span> <span>{{ __('No Notes Found') }}</span>
<Button label="Create" @click="createNote"> <Button :label="__('Create')" @click="createNote">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</div> </div>
@ -121,13 +121,7 @@ import { ref, watch } from 'vue'
const { getUser } = usersStore() const { getUser } = usersStore()
const list = { const breadcrumbs = [{ label: __('Notes'), route: { name: 'Notes' } }]
title: 'Notes',
plural_label: 'Notes',
singular_label: 'Note',
}
const breadcrumbs = [{ label: list.title, route: { name: 'Notes' } }]
const showNoteModal = ref(false) const showNoteModal = ref(false)
const currentNote = ref(null) const currentNote = ref(null)

View File

@ -27,13 +27,13 @@
{ {
icon: 'upload', icon: 'upload',
label: organization.doc.organization_logo label: organization.doc.organization_logo
? 'Change image' ? __('Change image')
: 'Upload image', : __('Upload image'),
onClick: openFileSelector, onClick: openFileSelector,
}, },
{ {
icon: 'trash-2', icon: 'trash-2',
label: 'Remove image', label: __('Remove image'),
onClick: () => changeOrganizationImage(''), onClick: () => changeOrganizationImage(''),
}, },
], ],
@ -118,7 +118,7 @@
organization.doc.annual_revenue organization.doc.annual_revenue
" "
variant="ghost" variant="ghost"
label="More" :label="__('More')"
class="-ml-1 cursor-pointer hover:text-gray-900" class="-ml-1 cursor-pointer hover:text-gray-900"
@click=" @click="
() => { () => {
@ -130,7 +130,7 @@
</div> </div>
<div class="mt-2 flex gap-1.5"> <div class="mt-2 flex gap-1.5">
<Button <Button
label="Edit" :label="__('Edit')"
size="sm" size="sm"
@click=" @click="
() => { () => {
@ -144,7 +144,7 @@
</template> </template>
</Button> </Button>
<Button <Button
label="Delete" :label="__('Delete')"
theme="red" theme="red"
size="sm" size="sm"
@click="deleteOrganization" @click="deleteOrganization"
@ -154,7 +154,7 @@
</template> </template>
</Button> </Button>
</div> </div>
<ErrorMessage class="mt-2" :message="error" /> <ErrorMessage class="mt-2" :message="__(error)" />
</div> </div>
</div> </div>
</template> </template>
@ -166,7 +166,7 @@
:class="{ 'text-gray-900': selected }" :class="{ 'text-gray-900': selected }"
> >
<component v-if="tab.icon" :is="tab.icon" class="h-5" /> <component v-if="tab.icon" :is="tab.icon" class="h-5" />
{{ tab.label }} {{ __(tab.label) }}
<Badge <Badge
class="group-hover:bg-gray-900" class="group-hover:bg-gray-900"
:class="[selected ? 'bg-gray-900' : 'bg-gray-600']" :class="[selected ? 'bg-gray-900' : 'bg-gray-600']"
@ -199,7 +199,7 @@
> >
<div class="flex flex-col items-center justify-center space-y-3"> <div class="flex flex-col items-center justify-center space-y-3">
<component :is="tab.icon" class="!h-10 !w-10" /> <component :is="tab.icon" class="!h-10 !w-10" />
<div>No {{ tab.label }} Found</div> <div>{{ __('No {0} Found', [__(tab.label)]) }}</div>
</div> </div>
</div> </div>
</template> </template>
@ -268,7 +268,7 @@ const organization = createDocumentResource({
}) })
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Organizations', route: { name: 'Organizations' } }] let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
items.push({ items.push({
label: props.organizationId, label: props.organizationId,
route: { route: {
@ -282,7 +282,7 @@ const breadcrumbs = computed(() => {
function validateFile(file) { function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase() let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) { if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed' return __('Only PNG and JPG images are allowed')
} }
} }
@ -298,11 +298,11 @@ async function changeOrganizationImage(file) {
async function deleteOrganization() { async function deleteOrganization() {
$dialog({ $dialog({
title: 'Delete organization', title: __('Delete organization'),
message: 'Are you sure you want to delete this organization?', message: __('Are you sure you want to delete this organization?'),
actions: [ actions: [
{ {
label: 'Delete', label: __('Delete'),
theme: 'red', theme: 'red',
variant: 'solid', variant: 'solid',
async onClick(close) { async onClick(close) {
@ -416,7 +416,7 @@ function getDealRowObject(deal) {
}, },
modified: { modified: {
label: dateFormat(deal.modified, dateTooltipFormat), label: dateFormat(deal.modified, dateTooltipFormat),
timeAgo: timeAgo(deal.modified), timeAgo: __(timeAgo(deal.modified)),
}, },
} }
} }
@ -437,44 +437,44 @@ function getContactRowObject(contact) {
}, },
modified: { modified: {
label: dateFormat(contact.modified, dateTooltipFormat), label: dateFormat(contact.modified, dateTooltipFormat),
timeAgo: timeAgo(contact.modified), timeAgo: __(timeAgo(contact.modified)),
}, },
} }
} }
const dealColumns = [ const dealColumns = [
{ {
label: 'Organization', label: __('Organization'),
key: 'organization', key: 'organization',
width: '11rem', width: '11rem',
}, },
{ {
label: 'Amount', label: __('Amount'),
key: 'annual_revenue', key: 'annual_revenue',
width: '9rem', width: '9rem',
}, },
{ {
label: 'Status', label: __('Status'),
key: 'status', key: 'status',
width: '10rem', width: '10rem',
}, },
{ {
label: 'Email', label: __('Email'),
key: 'email', key: 'email',
width: '12rem', width: '12rem',
}, },
{ {
label: 'Mobile no', label: __('Mobile no'),
key: 'mobile_no', key: 'mobile_no',
width: '11rem', width: '11rem',
}, },
{ {
label: 'Deal owner', label: __('Deal owner'),
key: 'deal_owner', key: 'deal_owner',
width: '10rem', width: '10rem',
}, },
{ {
label: 'Last modified', label: __('Last modified'),
key: 'modified', key: 'modified',
width: '8rem', width: '8rem',
}, },
@ -482,27 +482,27 @@ const dealColumns = [
const contactColumns = [ const contactColumns = [
{ {
label: 'Name', label: __('Name'),
key: 'full_name', key: 'full_name',
width: '17rem', width: '17rem',
}, },
{ {
label: 'Email', label: __('Email'),
key: 'email', key: 'email',
width: '12rem', width: '12rem',
}, },
{ {
label: 'Phone', label: __('Phone'),
key: 'mobile_no', key: 'mobile_no',
width: '12rem', width: '12rem',
}, },
{ {
label: 'Organization', label: __('Organization'),
key: 'company_name', key: 'company_name',
width: '12rem', width: '12rem',
}, },
{ {
label: 'Last modified', label: __('Last modified'),
key: 'modified', key: 'modified',
width: '8rem', width: '8rem',
}, },

View File

@ -10,7 +10,7 @@
/> />
<Button <Button
variant="solid" variant="solid"
label="Create" :label="__('Create')"
@click="showOrganizationModal = true" @click="showOrganizationModal = true"
> >
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
@ -51,8 +51,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<OrganizationsIcon class="h-10 w-10" /> <OrganizationsIcon class="h-10 w-10" />
<span>No Organizations Found</span> <span>{{ __('No Organizations Found') }}</span>
<Button label="Create" @click="showOrganizationModal = true"> <Button :label="__('Create')" @click="showOrganizationModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</div> </div>
@ -88,10 +88,10 @@ const currentOrganization = computed(() => {
}) })
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Organizations', route: { name: 'Organizations' } }] let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
if (!currentOrganization.value) return items if (!currentOrganization.value) return items
items.push({ items.push({
label: currentOrganization.value.name, label: __(currentOrganization.value.name),
route: { route: {
name: 'Organization', name: 'Organization',
params: { organizationId: currentOrganization.value.name }, params: { organizationId: currentOrganization.value.name },
@ -126,7 +126,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) { } else if (['modified', 'creation'].includes(row)) {
_rows[row] = { _rows[row] = {
label: dateFormat(organization[row], dateTooltipFormat), label: dateFormat(organization[row], dateTooltipFormat),
timeAgo: timeAgo(organization[row]), timeAgo: __(timeAgo(organization[row])),
} }
} }
}) })

View File

@ -8,7 +8,7 @@
v-if="tasksListView?.customListActions" v-if="tasksListView?.customListActions"
:actions="tasksListView.customListActions" :actions="tasksListView.customListActions"
/> />
<Button variant="solid" label="Create" @click="createTask"> <Button variant="solid" :label="__('Create')" @click="createTask">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -45,7 +45,10 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500" class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
> >
<EmailIcon class="h-10 w-10" /> <EmailIcon class="h-10 w-10" />
<span>No Tasks Found</span> <span>{{ __('No Tasks Found') }}</span>
<Button :label="__('Create')" @click="showTaskModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div> </div>
</div> </div>
<TaskModal v-model="showTaskModal" v-model:reloadTasks="tasks" :task="task" /> <TaskModal v-model="showTaskModal" v-model:reloadTasks="tasks" :task="task" />
@ -63,7 +66,7 @@ import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
import { Breadcrumbs } from 'frappe-ui' import { Breadcrumbs } from 'frappe-ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
const breadcrumbs = [{ label: 'Tasks', route: { name: 'Tasks' } }] const breadcrumbs = [{ label: __('Tasks'), route: { name: 'Tasks' } }]
const { getUser } = usersStore() const { getUser } = usersStore()
@ -86,7 +89,7 @@ const rows = computed(() => {
if (['modified', 'creation'].includes(row)) { if (['modified', 'creation'].includes(row)) {
_rows[row] = { _rows[row] = {
label: dateFormat(task[row], dateTooltipFormat), label: dateFormat(task[row], dateTooltipFormat),
timeAgo: timeAgo(task[row]), timeAgo: __(timeAgo(task[row])),
} }
} else if (row == 'assigned_to') { } else if (row == 'assigned_to') {
_rows[row] = { _rows[row] = {

View File

@ -0,0 +1,47 @@
import { createResource } from 'frappe-ui'
export default function translationPlugin(app) {
app.config.globalProperties.__ = translate
window.__ = translate
if (!window.translatedMessages) fetchTranslations()
}
function format(message, replace) {
return message.replace(/{(\d+)}/g, function (match, number) {
return typeof replace[number] != 'undefined' ? replace[number] : match
})
}
function translate(message, replace, context = null) {
let translatedMessages = window.translatedMessages || {}
let translatedMessage = ''
if (context) {
let key = `${message}:${context}`
if (translatedMessages[key]) {
translatedMessage = translatedMessages[key]
}
}
if (!translatedMessage) {
translatedMessage = translatedMessages[message] || message
}
const hasPlaceholders = /{\d+}/.test(message)
if (!hasPlaceholders) {
return translatedMessage
}
return format(translatedMessage, replace)
}
function fetchTranslations(lang) {
createResource({
url: 'crm.api.get_translations',
cache: 'translations',
auto: true,
transform: (data) => {
window.translatedMessages = data
},
})
}