fix: added organization link field with options and external link in lead/deal/contat page

This commit is contained in:
Shariq Ansari 2023-11-06 16:17:46 +05:30
parent 4a089faa4d
commit 475bf6469b
4 changed files with 264 additions and 171 deletions

View File

@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.86813 10.1317C5.67286 9.93647 5.67286 9.61988 5.86813 9.42462L12.7926 2.5001L9 2.5001C8.72386 2.5001 8.5 2.27624 8.5 2.0001C8.5 1.72396 8.72386 1.5001 9 1.5001L13.9899 1.5001C14.1076 1.49777 14.2261 1.53678 14.3215 1.61714C14.4306 1.70886 14.5 1.84638 14.5 2.0001V7.0001C14.5 7.27624 14.2761 7.5001 14 7.5001C13.7239 7.5001 13.5 7.27624 13.5 7.0001V3.20696L6.57523 10.1317C6.37997 10.327 6.06339 10.327 5.86813 10.1317ZM2.5 4.0001C2.5 3.17167 3.17157 2.5001 4 2.5001H5.8C6.07614 2.5001 6.3 2.27624 6.3 2.0001C6.3 1.72396 6.07614 1.5001 5.8 1.5001H4C2.61929 1.5001 1.5 2.61939 1.5 4.0001V12.0001C1.5 13.3808 2.61929 14.5001 4 14.5001H12C13.3807 14.5001 14.5 13.3808 14.5 12.0001V10.2001C14.5 9.92396 14.2761 9.7001 14 9.7001C13.7239 9.7001 13.5 9.92396 13.5 10.2001V12.0001C13.5 12.8285 12.8284 13.5001 12 13.5001H4C3.17157 13.5001 2.5 12.8285 2.5 12.0001V4.0001Z"
fill="currentColor"
/>
</svg>
</template>

View File

@ -91,10 +91,10 @@
:key="field.name"
class="flex items-center gap-2 px-3 text-base leading-5 last:mb-3"
>
<div class="w-[106px] text-gray-600">
<div class="w-[106px] shrink-0 text-gray-600">
{{ field.label }}
</div>
<div class="flex-1">
<div class="flex-1 overflow-hidden">
<FormControl
v-if="field.type === 'email'"
type="email"
@ -121,6 +121,11 @@
:debounce="500"
/>
</div>
<ExternalLinkIcon
v-if="field.type === 'link' && field.link"
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
@click="field.link(contact[field.name])"
/>
</div>
</div>
</div>
@ -197,6 +202,7 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LeadsListView from '@/components/ListViews/LeadsListView.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue'
import {
@ -210,10 +216,12 @@ import {
} from '@/utils'
import { usersStore } from '@/stores/users.js'
import { contactsStore } from '@/stores/contacts.js'
import { organizationsStore } from '@/stores/organizations.js'
import { ref, computed, h } from 'vue'
const { getContactByName, contacts } = contactsStore()
const { getUser } = usersStore()
const { getOrganization, organizationOptions } = organizationsStore()
const showContactModal = ref(false)
@ -297,8 +305,7 @@ const leads = createListResource({
'first_name',
'lead_name',
'image',
'organization_name',
'organization_logo',
'organization',
'status',
'email',
'mobile_no',
@ -320,8 +327,7 @@ const deals = createListResource({
cache: ['deals', props.contactId],
fields: [
'name',
'organization_name',
'organization_logo',
'organization',
'annual_revenue',
'deal_status',
'email',
@ -361,9 +367,9 @@ function getLeadRowObject(lead) {
image: lead.image,
image_label: lead.first_name,
},
organization_name: {
label: lead.organization_name,
logo: lead.organization_logo,
organization: {
label: lead.organization,
logo: getOrganization(lead.organization)?.organization_logo,
},
status: {
label: lead.status,
@ -385,9 +391,9 @@ function getLeadRowObject(lead) {
function getDealRowObject(deal) {
return {
name: deal.name,
organization_name: {
label: deal.organization_name,
logo: deal.organization_logo,
organization: {
label: deal.organization,
logo: getOrganization(deal.organization)?.organization_logo,
},
annual_revenue: formatNumberIntoCurrency(deal.annual_revenue),
deal_status: {
@ -415,7 +421,7 @@ const leadColumns = [
},
{
label: 'Organization',
key: 'organization_name',
key: 'organization',
width: '10rem',
},
{
@ -448,7 +454,7 @@ const leadColumns = [
const dealColumns = [
{
label: 'Organization',
key: 'organization_name',
key: 'organization',
width: '11rem',
},
{
@ -483,54 +489,68 @@ const dealColumns = [
},
]
const details = [
{
label: 'Salutation',
type: 'link',
name: 'salutation',
placeholder: 'Mr./Mrs./Ms.',
options: [
{ label: 'Dr', value: 'Dr' },
{ label: 'Mr', value: 'Mr' },
{ label: 'Mrs', value: 'Mrs' },
{ label: 'Ms', value: 'Ms' },
{ label: 'Mx', value: 'Mx' },
{ label: 'Prof', value: 'Prof' },
{ label: 'Master', value: 'Master' },
{ label: 'Madam', value: 'Madam' },
{ label: 'Miss', value: 'Miss' },
],
change: (data) => {
contact.value.salutation = data.value
updateContact('salutation', data.value)
const details = computed(() => {
return [
{
label: 'Salutation',
type: 'link',
name: 'salutation',
placeholder: 'Mr./Mrs./Ms.',
options: [
{ label: 'Dr', value: 'Dr' },
{ label: 'Mr', value: 'Mr' },
{ label: 'Mrs', value: 'Mrs' },
{ label: 'Ms', value: 'Ms' },
{ label: 'Mx', value: 'Mx' },
{ label: 'Prof', value: 'Prof' },
{ label: 'Master', value: 'Master' },
{ label: 'Madam', value: 'Madam' },
{ label: 'Miss', value: 'Miss' },
],
change: (data) => {
contact.value.salutation = data.value
updateContact('salutation', data.value)
},
},
},
{
label: 'First name',
type: 'data',
name: 'first_name',
},
{
label: 'Last name',
type: 'data',
name: 'last_name',
},
{
label: 'Email',
type: 'email',
name: 'email',
},
{
label: 'Mobile no.',
type: 'phone',
name: 'mobile_no',
},
{
label: 'Organization',
type: 'data',
name: 'company_name',
},
]
{
label: 'First name',
type: 'data',
name: 'first_name',
},
{
label: 'Last name',
type: 'data',
name: 'last_name',
},
{
label: 'Email',
type: 'email',
name: 'email',
},
{
label: 'Mobile no.',
type: 'phone',
name: 'mobile_no',
},
{
label: 'Organization',
type: 'link',
name: 'company_name',
placeholder: 'Select organization',
options: organizationOptions,
change: (data) => {
contact.value.company_name = data.value
updateContact('company_name', data.value)
},
link: (data) => {
router.push({
name: 'Organization',
params: { organizationId: data.value },
})
},
},
]
})
function updateContact(fieldname, value) {
createResource({
@ -570,7 +590,18 @@ function updateContact(fieldname, value) {
background: white;
}
:deep(.form-control button) {
gap: 0;
}
:deep(.form-control button > div) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.form-control button svg) {
color: white;
width: 0;
}
</style>

View File

@ -4,7 +4,8 @@
<Breadcrumbs :items="breadcrumbs" />
</template>
<template #right-header>
<Autocomplete
<FormControl
type="autocomplete"
:options="activeAgents"
:value="getUser(deal.data.lead_owner).full_name"
@change="(option) => updateAssignedAgent(option.email)"
@ -16,7 +17,7 @@
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.email" size="sm" />
</template>
</Autocomplete>
</FormControl>
<Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
<template #default="{ open }">
<Button :label="deal.data.deal_status">
@ -45,79 +46,48 @@
>
About this deal
</div>
<FileUploader @success="changeDealImage" :validateFile="validateFile">
<template #default="{ openFileSelector, error }">
<div class="flex items-center justify-start gap-5 border-b p-5">
<div class="group relative h-[88px] w-[88px]">
<Avatar
size="3xl"
class="h-[88px] w-[88px]"
:label="deal.data.organization_name"
:image="deal.data.organization_logo"
/>
<Dropdown
:options="[
{
icon: 'upload',
label: deal.data.organization_logo
? 'Change image'
: 'Upload image',
onClick: openFileSelector,
},
{
icon: 'trash-2',
label: 'Remove image',
onClick: () => {
deal.data.organization_logo = ''
updateDeal('organization_logo', '')
},
},
]"
class="!absolute bottom-0 left-0 right-0"
<div class="flex items-center justify-start gap-5 border-b p-5">
<Tooltip
text="Organization logo"
class="group relative h-[88px] w-[88px]"
>
<Avatar
size="3xl"
class="h-[88px] w-[88px]"
:label="organization.name"
:image="deal.data.organization_logo"
/>
</Tooltip>
<div class="flex flex-col gap-2.5 truncate">
<Tooltip :text="organization.name">
<div class="truncate text-2xl font-medium">
{{ organization.name }}
</div>
</Tooltip>
<div class="flex gap-1.5">
<Tooltip text="Make a call...">
<Button
class="h-7 w-7"
@click="() => makeCall(deal.data.mobile_no)"
>
<div
class="z-1 absolute bottom-0 left-0 right-0 flex h-11 cursor-pointer items-center justify-center rounded-b-full bg-black bg-opacity-40 pt-3 opacity-0 duration-300 ease-in-out group-hover:opacity-100"
style="
-webkit-clip-path: inset(12px 0 0 0);
clip-path: inset(12px 0 0 0);
"
>
<CameraIcon class="h-6 w-6 cursor-pointer text-white" />
</div>
</Dropdown>
</div>
<div class="flex flex-col gap-2.5 truncate">
<Tooltip :text="deal.data.organization_name">
<div class="truncate text-2xl font-medium">
{{ deal.data.organization_name }}
</div>
</Tooltip>
<div class="flex gap-1.5">
<Tooltip text="Make a call...">
<Button
class="h-7 w-7"
@click="() => makeCall(deal.data.mobile_no)"
>
<PhoneIcon class="h-4 w-4" />
</Button>
</Tooltip>
<Button class="h-7 w-7">
<EmailIcon class="h-4 w-4" />
</Button>
<Tooltip text="Go to website...">
<Button class="h-7 w-7">
<LinkIcon
class="h-4 w-4"
@click="openWebsite(deal.data.website)"
/>
</Button>
</Tooltip>
</div>
<ErrorMessage :message="error" />
</div>
<PhoneIcon class="h-4 w-4" />
</Button>
</Tooltip>
<Button class="h-7 w-7">
<EmailIcon class="h-4 w-4" />
</Button>
<Tooltip text="Go to website...">
<Button class="h-7 w-7">
<LinkIcon
class="h-4 w-4"
@click="openWebsite(deal.data.website)"
/>
</Button>
</Tooltip>
</div>
</template>
</FileUploader>
<ErrorMessage :message="error" />
</div>
</div>
<div class="flex flex-1 flex-col justify-between overflow-hidden">
<div class="flex flex-col overflow-y-auto">
<div
@ -152,10 +122,10 @@
:key="field.label"
class="flex items-center gap-2 px-3 text-base leading-5 first:mt-3"
>
<div class="w-[106px] text-gray-600">
<div class="w-[106px] shrink-0 text-gray-600">
{{ field.label }}
</div>
<div class="flex-1">
<div class="flex-1 overflow-hidden">
<FormControl
v-if="field.type === 'select'"
type="select"
@ -183,16 +153,18 @@
"
:debounce="500"
/>
<Autocomplete
<FormControl
v-else-if="field.type === 'link'"
type="autocomplete"
:value="deal.data[field.name]"
:options="field.options"
@change="(e) => field.change(e)"
:placeholder="field.placeholder"
class="form-control"
/>
<Autocomplete
<FormControl
v-else-if="field.type === 'user'"
type="autocomplete"
:options="activeAgents"
:value="getUser(deal.data[field.name]).full_name"
@change="(option) => updateAssignedAgent(option.email)"
@ -221,7 +193,7 @@
size="sm"
/>
</template>
</Autocomplete>
</FormControl>
<Dropdown
v-else-if="field.type === 'dropdown'"
:options="
@ -283,6 +255,12 @@
:debounce="500"
class="form-control"
/>
<div
class="flex h-7 cursor-pointer items-center px-2 py-1"
v-else-if="field.type === 'read_only'"
>
{{ field.value }}
</div>
<FormControl
v-else
type="text"
@ -294,6 +272,11 @@
class="form-control"
/>
</div>
<ExternalLinkIcon
v-if="field.type === 'link' && field.link"
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
@click="field.link(deal.data[field.name])"
/>
</div>
</div>
</transition>
@ -311,8 +294,8 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue'
@ -326,12 +309,11 @@ import {
} from '@/utils'
import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts'
import { organizationsStore } from '@/stores/organizations'
import {
createResource,
FeatherIcon,
FileUploader,
ErrorMessage,
Autocomplete,
FormControl,
Dropdown,
Tooltip,
@ -340,9 +322,12 @@ import {
Breadcrumbs,
} from 'frappe-ui'
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const { getUser, users } = usersStore()
const { getUser } = usersStore()
const { contacts } = contactsStore()
const { getOrganization, organizationOptions } = organizationsStore()
const router = useRouter()
const props = defineProps({
dealId: {
@ -394,7 +379,7 @@ function updateDeal(fieldname, value) {
const breadcrumbs = computed(() => {
let items = [{ label: 'Deals', route: { name: 'Deals' } }]
items.push({
label: deal.data.organization_name,
label: organization.value.name,
route: { name: 'Deal', params: { dealId: deal.data.name } },
})
return items
@ -424,18 +409,6 @@ const tabs = [
},
]
function changeDealImage(file) {
deal.data.organization_logo = file.file_url
updateDeal('organization_logo', file.file_url)
}
function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed'
}
}
const detailSections = computed(() => {
return [
{
@ -444,13 +417,26 @@ const detailSections = computed(() => {
fields: [
{
label: 'Organization',
type: 'data',
name: 'organization_name',
type: 'link',
name: 'organization',
placeholder: 'Select organization',
options: organizationOptions,
change: (data) => {
deal.data.organization = data.value
updateDeal('organization', data.value)
},
link: () => {
router.push({
name: 'Organization',
params: { organizationId: organization.value.name },
})
},
},
{
label: 'Website',
type: 'data',
type: 'read_only',
name: 'website',
value: organization.value?.website,
},
{
label: 'Amount',
@ -524,6 +510,10 @@ const detailSections = computed(() => {
]
})
const organization = computed(() => {
return getOrganization(deal.data.organization)
})
function updateAssignedAgent(email) {
deal.data.lead_owner = email
updateDeal('lead_owner', email)
@ -538,7 +528,18 @@ function updateAssignedAgent(email) {
background: white;
}
:deep(.form-control button) {
gap: 0;
}
:deep(.form-control button > div) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.form-control button svg) {
color: white;
width: 0;
}
</style>

View File

@ -4,7 +4,8 @@
<Breadcrumbs :items="breadcrumbs" />
</template>
<template #right-header>
<Autocomplete
<FormControl
type="autocomplete"
:options="activeAgents"
:value="getUser(lead.data.lead_owner).full_name"
@change="(option) => updateAssignedAgent(option.email)"
@ -16,7 +17,7 @@
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.email" size="sm" />
</template>
</Autocomplete>
</FormControl>
<Dropdown :options="statusDropdownOptions(lead.data, 'lead', updateLead)">
<template #default="{ open }">
<Button :label="lead.data.status">
@ -154,10 +155,10 @@
:key="field.name"
class="flex items-center gap-2 px-3 text-base leading-5 last:mb-3"
>
<div class="w-[106px] text-gray-600">
<div class="w-[106px] shrink-0 text-gray-600">
{{ field.label }}
</div>
<div class="flex-1">
<div class="flex-1 overflow-hidden">
<FormControl
v-if="field.type === 'select'"
type="select"
@ -185,16 +186,18 @@
"
:debounce="500"
/>
<Autocomplete
<FormControl
v-else-if="field.type === 'link'"
type="autocomplete"
:value="lead.data[field.name]"
:options="field.options"
@change="(e) => field.change(e)"
:placeholder="field.placeholder"
class="form-control"
/>
<Autocomplete
<FormControl
v-else-if="field.type === 'user'"
type="autocomplete"
:options="activeAgents"
:value="getUser(lead.data[field.name]).full_name"
@change="(option) => updateAssignedAgent(option.email)"
@ -223,7 +226,7 @@
size="sm"
/>
</template>
</Autocomplete>
</FormControl>
<Dropdown
v-else-if="field.type === 'dropdown'"
:options="
@ -255,6 +258,12 @@
</Button>
</template>
</Dropdown>
<div
class="flex h-7 cursor-pointer items-center px-2 py-1"
v-else-if="field.type === 'read_only'"
>
{{ field.value }}
</div>
<FormControl
v-else
type="text"
@ -266,6 +275,11 @@
:debounce="500"
/>
</div>
<ExternalLinkIcon
v-if="field.type === 'link' && field.link"
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
@click="field.link(lead.data[field.name])"
/>
</div>
</div>
</transition>
@ -283,7 +297,9 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue'
@ -297,6 +313,7 @@ import {
} from '@/utils'
import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts'
import { organizationsStore } from '@/stores/organizations'
import {
createResource,
FileUploader,
@ -312,10 +329,10 @@ import {
} from 'frappe-ui'
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import CameraIcon from '../components/Icons/CameraIcon.vue'
const { getUser, users } = usersStore()
const { getUser } = usersStore()
const { contacts } = contactsStore()
const { getOrganization, organizationOptions } = organizationsStore()
const router = useRouter()
const props = defineProps({
@ -421,13 +438,26 @@ const detailSections = computed(() => {
fields: [
{
label: 'Organization',
type: 'data',
name: 'organization_name',
type: 'link',
name: 'organization',
placeholder: 'Select organization',
options: organizationOptions,
change: (data) => {
lead.data.organization = data.value
updateLead('organization', data.value)
},
link: () => {
router.push({
name: 'Organization',
params: { organizationId: organization.value?.name },
})
},
},
{
label: 'Website',
type: 'data',
type: 'read_only',
name: 'website',
value: organization.value?.website,
},
{
label: 'Job title',
@ -517,6 +547,10 @@ const detailSections = computed(() => {
]
})
const organization = computed(() => {
return getOrganization(lead.data.organization)
})
function convertToDeal() {
lead.data.status = 'Qualified'
lead.data.is_deal = 1
@ -537,7 +571,18 @@ function updateAssignedAgent(email) {
background: white;
}
:deep(.form-control button) {
gap: 0;
}
:deep(.form-control button > div) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.form-control button svg) {
color: white;
width: 0;
}
</style>