Merge pull request #13 from shariquerik/lead-page-redesign
This commit is contained in:
commit
6fc2da29e6
@ -16,7 +16,7 @@
|
||||
"@vueuse/integrations": "^10.3.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"feather-icons": "^4.28.0",
|
||||
"frappe-ui": "^0.1.1",
|
||||
"frappe-ui": "^0.1.5",
|
||||
"pinia": "^2.0.33",
|
||||
"postcss": "^8.4.5",
|
||||
"socket.io-client": "^4.7.2",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="p-5 flex items-center justify-between font-medium text-lg">
|
||||
<div>{{ title }}</div>
|
||||
<div class="px-10 py-5 flex items-center justify-between font-medium text-lg">
|
||||
<div class="flex items-center h-7 text-xl font-semibold">{{ title }}</div>
|
||||
<Button v-if="title == 'Calls'" variant="solid" @click="emit('makeCall')">
|
||||
<PhoneIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
@ -12,8 +12,8 @@
|
||||
<FeatherIcon name="plus" class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="activities.length" class="overflow-y-auto">
|
||||
<div v-if="title == 'Notes'" class="grid grid-cols-3 gap-4 p-5 pt-0">
|
||||
<div v-if="activities.length" class="flex-1 overflow-y-auto">
|
||||
<div v-if="title == 'Notes'" class="grid grid-cols-3 gap-4 px-10 py-5 pt-0">
|
||||
<div
|
||||
v-for="note in activities"
|
||||
class="group flex flex-col justify-between gap-2 px-4 py-3 border rounded-lg h-48 shadow-sm hover:bg-gray-50 cursor-pointer"
|
||||
@ -82,7 +82,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-3 border rounded-lg p-4 mb-3 shadow-sm max-w-[60%]"
|
||||
class="flex flex-col gap-3 bg-gray-50 rounded-md p-4 mb-3 max-w-[70%]"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@ -166,13 +166,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else v-for="(activity, i) in activities">
|
||||
<div class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-5">
|
||||
<div class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-10">
|
||||
<div
|
||||
class="relative flex justify-center after:absolute after:border-l after:border-gray-300 after:top-0 after:left-[50%] after:-z-10"
|
||||
class="relative flex justify-center after:absolute after:border-l after:border-gray-200 after:top-0 after:left-[50%] after:-z-10"
|
||||
:class="i != activities.length - 1 ? 'after:h-full' : 'after:h-4'"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center rounded-full outline outline-4 outline-white w-6 h-6 bg-gray-200 z-10"
|
||||
class="flex items-center justify-center rounded-full w-7 h-7 bg-gray-100 z-10"
|
||||
:class="{
|
||||
'mt-[15px]': [
|
||||
'communication',
|
||||
@ -181,15 +181,12 @@
|
||||
].includes(activity.activity_type),
|
||||
}"
|
||||
>
|
||||
<FeatherIcon
|
||||
:name="activity.icon"
|
||||
class="w-3.5 h-3.5 text-gray-600"
|
||||
/>
|
||||
<FeatherIcon :name="activity.icon" class="w-4 h-4 text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activity.activity_type == 'communication'" class="pb-6">
|
||||
<div
|
||||
class="shadow-sm border max-w-[80%] rounded-xl p-3 text-base cursor-pointer leading-6 transition-all duration-300 ease-in-out"
|
||||
class="rounded-md p-3 text-base cursor-pointer bg-gray-50 leading-6 transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2 mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
@ -219,7 +216,7 @@
|
||||
activity.activity_type == 'incoming_call' ||
|
||||
activity.activity_type == 'outgoing_call'
|
||||
"
|
||||
class="flex flex-col gap-3 border rounded-lg p-4 mb-3 shadow-sm max-w-[60%]"
|
||||
class="flex flex-col gap-3 bg-gray-50 rounded-md p-4 mb-3 max-w-[70%]"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@ -303,8 +300,6 @@
|
||||
<div
|
||||
class="flex items-start justify-stretch gap-2 text-base leading-6"
|
||||
>
|
||||
<UserAvatar :user="activity.owner" size="md" />
|
||||
|
||||
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
||||
<span class="text-gray-900">{{ activity.owner_name }}</span>
|
||||
<span v-if="activity.type">{{ activity.type }}</span>
|
||||
@ -370,9 +365,14 @@
|
||||
v-else-if="title == 'Emails'"
|
||||
variant="solid"
|
||||
label="Send email"
|
||||
@click="emit('setFocusOnEmail')"
|
||||
@click="$refs.emailBox.show = true"
|
||||
/>
|
||||
</div>
|
||||
<CommunicationArea
|
||||
ref="emailBox"
|
||||
v-if="['Emails', 'Activity'].includes(title) && lead"
|
||||
v-model="lead"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
@ -381,6 +381,7 @@ import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
||||
import DurationIcon from '@/components/Icons/DurationIcon.vue'
|
||||
import PlayIcon from '@/components/Icons/PlayIcon.vue'
|
||||
import CommunicationArea from '@/components/CommunicationArea.vue'
|
||||
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import {
|
||||
@ -391,7 +392,7 @@ import {
|
||||
TextEditor,
|
||||
Avatar,
|
||||
} from 'frappe-ui'
|
||||
import { computed, h } from 'vue'
|
||||
import { computed, h, defineModel } from 'vue'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
@ -406,12 +407,9 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'makeCall',
|
||||
'makeNote',
|
||||
'deleteNote',
|
||||
'setFocusOnEmail',
|
||||
])
|
||||
const lead = defineModel()
|
||||
|
||||
const emit = defineEmits(['makeCall', 'makeNote', 'deleteNote'])
|
||||
|
||||
const activities = computed(() => {
|
||||
if (props.title == 'Calls') {
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<div class="max-w-[81.7%] pl-16 p-4 pt-2">
|
||||
<button
|
||||
<div class="flex gap-3 pt-2 pb-6 px-10">
|
||||
<UserAvatar :user="getUser().name" size="xl" />
|
||||
<Button
|
||||
ref="sendEmailRef"
|
||||
class="flex gap-2 w-full items-center rounded-lg p-2 bg-gray-100 hover:bg-gray-200"
|
||||
variant="outline"
|
||||
size="md"
|
||||
class="h-8.5 w-full inline-flex justify-between"
|
||||
@click="showCommunicationBox = true"
|
||||
v-show="!showCommunicationBox"
|
||||
>
|
||||
<UserAvatar :user="getUser().name" size="sm" />
|
||||
<div class="text-base text-gray-600">Add a reply...</div>
|
||||
</button>
|
||||
<template #suffix>
|
||||
<div class="flex gap-3">
|
||||
<!-- <FeatherIcon name="paperclip" class="h-4" /> -->
|
||||
</div>
|
||||
</template>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showCommunicationBox"
|
||||
class="w-full rounded-lg border bg-white p-4 focus-within:border-gray-400"
|
||||
@ -47,9 +54,8 @@
|
||||
<script setup>
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import EmailEditor from '@/components/EmailEditor.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { Tooltip, call, Button } from 'frappe-ui'
|
||||
import { call } from 'frappe-ui'
|
||||
import { ref, watch, computed, defineModel } from 'vue'
|
||||
|
||||
const modelValue = defineModel()
|
||||
@ -101,5 +107,5 @@ async function submitComment() {
|
||||
modelValue.value.reload()
|
||||
}
|
||||
|
||||
defineExpose({ el: sendEmailRef })
|
||||
defineExpose({ show: showCommunicationBox })
|
||||
</script>
|
||||
|
||||
16
frontend/src/components/Icons/CameraIcon.vue
Normal file
16
frontend/src/components/Icons/CameraIcon.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
width="20"
|
||||
height="21"
|
||||
viewBox="0 0 20 21"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.5 3.53906C8.02786 3.53906 7.58328 3.76135 7.3 4.13906L6.325 5.43906C6.04172 5.81677 5.59714 6.03906 5.125 6.03906H3.25C2.42157 6.03906 1.75 6.71064 1.75 7.53906V16.0391C1.75 16.8675 2.42157 17.5391 3.25 17.5391H16.75C17.5784 17.5391 18.25 16.8675 18.25 16.0391V7.53906C18.25 6.71064 17.5784 6.03906 16.75 6.03906H14.875C14.4029 6.03906 13.9583 5.81677 13.675 5.43906L12.7 4.13906C12.4167 3.76135 11.9721 3.53906 11.5 3.53906H8.5ZM6.5 3.53906C6.97214 2.90955 7.71311 2.53906 8.5 2.53906H11.5C12.2869 2.53906 13.0279 2.90955 13.5 3.53906L14.475 4.83906C14.5694 4.96497 14.7176 5.03906 14.875 5.03906H16.75C18.1307 5.03906 19.25 6.15835 19.25 7.53906V16.0391C19.25 17.4198 18.1307 18.5391 16.75 18.5391H3.25C1.86929 18.5391 0.75 17.4198 0.75 16.0391V7.53906C0.75 6.15835 1.86929 5.03906 3.25 5.03906H5.125C5.28238 5.03906 5.43057 4.96497 5.525 4.83906L6.5 3.53906ZM10 13.9141C11.5188 13.9141 12.75 12.6828 12.75 11.1641C12.75 9.64528 11.5188 8.41406 10 8.41406C8.48122 8.41406 7.25 9.64528 7.25 11.1641C7.25 12.6828 8.48122 13.9141 10 13.9141ZM10 14.9141C12.0711 14.9141 13.75 13.2351 13.75 11.1641C13.75 9.09299 12.0711 7.41406 10 7.41406C7.92893 7.41406 6.25 9.09299 6.25 11.1641C6.25 13.2351 7.92893 14.9141 10 14.9141ZM16.25 8.66406C16.5952 8.66406 16.875 8.38424 16.875 8.03906C16.875 7.69388 16.5952 7.41406 16.25 7.41406C15.9048 7.41406 15.625 7.69388 15.625 8.03906C15.625 8.38424 15.9048 8.66406 16.25 8.66406Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
17
frontend/src/components/Icons/InboundCall.vue
Normal file
17
frontend/src/components/Icons/InboundCall.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="16" height="16" fill="white" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.7659 5.02696V2.49055C10.7659 2.21441 10.5421 1.99055 10.2659 1.99055C9.98979 1.99055 9.76593 2.21441 9.76593 2.49055V6.22461C9.76593 6.50075 9.98979 6.72461 10.2659 6.72461L14 6.72461C14.2761 6.72461 14.5 6.50075 14.5 6.22461C14.5 5.94847 14.2761 5.72461 14 5.72461L11.4825 5.72461L14.8535 2.35355C15.0488 2.15829 15.0488 1.84171 14.8535 1.64645C14.6583 1.45118 14.3417 1.45118 14.1464 1.64645L10.7659 5.02696ZM3.95262 3.01471C3.92305 2.96757 3.85703 2.96079 3.81849 3.00094L2.78472 4.07779C2.49483 4.37975 2.42118 4.80645 2.58821 5.15824C2.84578 5.7007 3.21973 6.43293 3.69626 7.22349L5.4337 5.5008C5.46212 5.47262 5.4672 5.42851 5.44593 5.39462L3.95262 3.01471ZM4.24559 8.08705L6.13779 6.21091C6.49848 5.85329 6.56295 5.29336 6.29299 4.86312L4.79968 2.48321C4.42423 1.88484 3.58632 1.79881 3.09711 2.30841L2.06333 3.38526C1.50617 3.96563 1.32728 4.83405 1.68487 5.58716C2.28388 6.84874 3.52538 9.17888 5.26635 10.9491C7.06226 12.7753 9.57149 14.1765 10.8502 14.8261C11.5427 15.1779 12.3553 15.0724 12.9518 14.6132L14.0767 13.7472C14.6547 13.3022 14.638 12.425 14.0434 12.0023L11.6812 10.323C11.2786 10.0367 10.7335 10.0585 10.3549 10.3758L8.26185 12.1305C7.46603 11.5789 6.66354 10.9437 5.97933 10.2479C5.32711 9.58475 4.7457 8.83116 4.24559 8.08705ZM9.13279 12.7053C9.97097 13.2327 10.7473 13.6522 11.3031 13.9345C11.6307 14.1009 12.0316 14.0597 12.3418 13.8209L13.4666 12.9548C13.5122 12.9198 13.5109 12.8507 13.464 12.8174L11.1018 11.138C11.0701 11.1155 11.0271 11.1172 10.9973 11.1422L9.13279 12.7053Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
43
frontend/src/components/Icons/LinkIcon.vue
Normal file
43
frontend/src/components/Icons/LinkIcon.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_b_525_5376)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M9.50545 11.0115H11.5132C13.1775 11.0115 14.5268 9.66229 14.5268 7.99794C14.5268 6.3336 13.1775 4.98438 11.5132 4.98438L9.50545 4.98438V3.98438H11.5132C13.7298 3.98438 15.5268 5.78131 15.5268 7.99794C15.5268 10.2146 13.7298 12.0115 11.5132 12.0115H9.50545V11.0115ZM6.49422 4.98465H4.48647C2.82212 4.98465 1.4729 6.33387 1.4729 7.99822C1.4729 9.66256 2.82212 11.0118 4.48647 11.0118H6.49422V12.0118H4.48647C2.26984 12.0118 0.4729 10.2148 0.4729 7.99822C0.4729 5.78159 2.26983 3.98465 4.48647 3.98465H6.49422V4.98465ZM10.0079 8.49808H10.5079V7.49808H10.0079H5.99239H5.49239V8.49808H5.99239H10.0079Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_b_525_5376"
|
||||
x="-4"
|
||||
y="-4"
|
||||
width="24"
|
||||
height="24"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
|
||||
<feComposite
|
||||
in2="SourceAlpha"
|
||||
operator="in"
|
||||
result="effect1_backgroundBlur_525_5376"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_backgroundBlur_525_5376"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
17
frontend/src/components/Icons/OutboundCall.vue
Normal file
17
frontend/src/components/Icons/OutboundCall.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="16" height="16" fill="white" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.7656 1.5C10.4894 1.5 10.2656 1.72386 10.2656 2C10.2656 2.27614 10.4894 2.5 10.7656 2.5H13.2927L9.92161 5.87106C9.72634 6.06632 9.72634 6.3829 9.92161 6.57816C10.1169 6.77342 10.4334 6.77342 10.6287 6.57816L13.9997 3.20722V5.73406C13.9997 6.0102 14.2235 6.23406 14.4997 6.23406C14.7758 6.23406 14.9997 6.0102 14.9997 5.73406V2.01073C15.0025 1.87922 14.9537 1.74681 14.8533 1.64645C14.7935 1.58659 14.7222 1.54509 14.6466 1.52193C14.6001 1.50767 14.5508 1.5 14.4997 1.5H10.7656ZM3.95262 3.04013C3.92305 2.99299 3.85703 2.98621 3.81849 3.02636L2.78472 4.10321C2.49483 4.40518 2.42118 4.83187 2.58821 5.18366C2.84578 5.72612 3.21973 6.45835 3.69626 7.24891L5.4337 5.52622C5.46212 5.49804 5.4672 5.45393 5.44593 5.42004L3.95262 3.04013ZM4.24559 8.11247L6.13779 6.23633C6.49848 5.87871 6.56295 5.31878 6.29299 4.88854L4.79968 2.50863C4.42423 1.91026 3.58632 1.82423 3.09711 2.33383L2.06333 3.41068C1.50617 3.99105 1.32728 4.85947 1.68487 5.61258C2.28388 6.87416 3.52538 9.2043 5.26635 10.9746C7.06226 12.8007 9.57149 14.2019 10.8502 14.8515C11.5427 15.2033 12.3553 15.0979 12.9518 14.6386L14.0767 13.7726C14.6547 13.3276 14.638 12.4504 14.0434 12.0278L11.6812 10.3484C11.2786 10.0622 10.7335 10.0839 10.3549 10.4013L8.26185 12.1559C7.46603 11.6043 6.66354 10.9691 5.97933 10.2734C5.32711 9.61018 4.7457 8.85659 4.24559 8.11247ZM9.13279 12.7307C9.97097 13.2581 10.7473 13.6776 11.3031 13.9599C11.6307 14.1264 12.0316 14.0851 12.3418 13.8463L13.4666 12.9802C13.5122 12.9452 13.5109 12.8761 13.464 12.8428L11.1018 11.1634C11.0701 11.1409 11.0271 11.1426 10.9973 11.1676L9.13279 12.7307Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -50,7 +50,7 @@
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
: 'opacity-100 ml-2 w-auto'
|
||||
"
|
||||
>
|
||||
<FeatherIcon name="chevron-down" class="h-4 w-4" aria-hidden="true" />
|
||||
<FeatherIcon name="chevron-down" class="h-4 w-4 text-gray-600" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<template #suffix
|
||||
><FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/></template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@ -19,10 +19,7 @@
|
||||
</Autocomplete>
|
||||
<Dropdown :options="statusDropdownOptions(deal.data, 'deal', updateDeal)">
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="deal.data.deal_status"
|
||||
:class="dealStatuses[deal.data.deal_status].bgColor"
|
||||
>
|
||||
<Button :label="deal.data.deal_status">
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="dealStatuses[deal.data.deal_status].color"
|
||||
@ -31,320 +28,318 @@
|
||||
<template #suffix
|
||||
><FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/></template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<TabGroup v-slot="{ selectedIndex }" v-if="deal.data" @change="onTabChange">
|
||||
<TabList class="flex items-center gap-6 border-b pl-5 relative">
|
||||
<Tab
|
||||
ref="tabRef"
|
||||
as="template"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
v-slot="{ selected }"
|
||||
>
|
||||
<button
|
||||
class="flex items-center gap-2 py-[9px] -mb-[1px] text-base text-gray-600 border-b border-transparent hover:text-gray-900 hover:border-gray-400 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'text-gray-900': selected }"
|
||||
<div v-if="deal.data" class="flex h-full overflow-hidden">
|
||||
<TabGroup as="div" class="flex flex-col flex-1" @change="onTabChange">
|
||||
<TabList class="flex items-center gap-6 border-b pl-5 relative">
|
||||
<Tab
|
||||
ref="tabRef"
|
||||
as="template"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
v-slot="{ selected }"
|
||||
>
|
||||
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</Tab>
|
||||
<div
|
||||
ref="indicator"
|
||||
class="h-[1px] bg-gray-900 w-[82px] absolute -bottom-[1px]"
|
||||
:style="{ left: `${indicatorLeftValue}px` }"
|
||||
/>
|
||||
</TabList>
|
||||
<div class="flex h-full overflow-hidden">
|
||||
<div class="flex-1 flex flex-col">
|
||||
<TabPanels class="flex flex-1 overflow-hidden">
|
||||
<TabPanel
|
||||
class="flex-1 flex flex-col overflow-y-auto"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
<button
|
||||
class="flex items-center gap-2 py-2.5 -mb-[1px] text-base text-gray-600 border-b border-transparent hover:text-gray-900 hover:border-gray-400 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'text-gray-900': selected }"
|
||||
>
|
||||
<Activities
|
||||
:title="tab.activityTitle"
|
||||
:activities="tab.content"
|
||||
@makeCall="makeCall(deal.data.mobile_no)"
|
||||
@makeNote="(e) => showNote(e)"
|
||||
@deleteNote="(e) => deleteNote(e)"
|
||||
@setFocusOnEmail="() => $refs.sendEmailRef.el.click()"
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
<CommunicationArea
|
||||
ref="sendEmailRef"
|
||||
v-if="[0, 1].includes(selectedIndex)"
|
||||
v-model="deal"
|
||||
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</Tab>
|
||||
<div
|
||||
ref="indicator"
|
||||
class="h-[1px] bg-gray-900 w-[82px] absolute -bottom-[1px]"
|
||||
:style="{ left: `${indicatorLeftValue}px` }"
|
||||
/>
|
||||
</TabList>
|
||||
<TabPanels class="flex flex-1 overflow-hidden">
|
||||
<TabPanel
|
||||
class="flex-1 flex flex-col overflow-y-auto"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
>
|
||||
<Activities
|
||||
:title="tab.activityTitle"
|
||||
:activities="tab.content"
|
||||
v-model="deal"
|
||||
@makeCall="makeCall(deal.data.mobile_no)"
|
||||
@makeNote="(e) => showNote(e)"
|
||||
@deleteNote="(e) => deleteNote(e)"
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
<div class="flex flex-col justify-between border-l w-[352px]">
|
||||
<div
|
||||
class="flex items-center border-b px-5 py-2.5 h-[41px] font-semibold text-lg"
|
||||
>
|
||||
About this deal
|
||||
</div>
|
||||
<div class="flex flex-col justify-between border-l w-[370px]">
|
||||
<FileUploader @success="changeDealImage" :validateFile="validateFile">
|
||||
<template #default="{ openFileSelector, error }">
|
||||
<div
|
||||
class="flex flex-col gap-3 pb-4 p-5 items-center justify-center border-b"
|
||||
>
|
||||
<FileUploader @success="changeDealImage" :validateFile="validateFile">
|
||||
<template #default="{ openFileSelector, error }">
|
||||
<div class="flex gap-5 items-center justify-start p-5 border-b">
|
||||
<div class="relative w-[88px] h-[88px] group">
|
||||
<Avatar
|
||||
size="3xl"
|
||||
shape="square"
|
||||
class="w-[88px] h-[88px]"
|
||||
:label="deal.data.organization_name"
|
||||
:image="deal.data.organization_logo"
|
||||
/>
|
||||
<ErrorMessage :message="error" />
|
||||
<div class="font-medium text-2xl">
|
||||
{{ deal.data.organization_name }}
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<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="absolute bottom-0 left-0 right-0 rounded-b-full z-1 h-11 flex items-center justify-center pt-3 bg-black bg-opacity-40 cursor-pointer opacity-0 group-hover:opacity-100 duration-300 ease-in-out"
|
||||
style="
|
||||
-webkit-clip-path: inset(12px 0 0 0);
|
||||
clip-path: inset(12px 0 0 0);
|
||||
"
|
||||
>
|
||||
<CameraIcon class="h-6 w-6 text-white cursor-pointer" />
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 truncate">
|
||||
<Tooltip :text="deal.data.organization_name">
|
||||
<div class="font-medium text-2xl truncate">
|
||||
{{ deal.data.organization_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="flex gap-1.5">
|
||||
<Tooltip text="Make a call...">
|
||||
<Button
|
||||
class="rounded-full h-8 w-8"
|
||||
class="h-7 w-7"
|
||||
@click="() => makeCall(deal.data.mobile_no)"
|
||||
>
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button class="rounded-full h-8 w-8">
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Tooltip text="Go to website...">
|
||||
<Button
|
||||
icon="link"
|
||||
@click="openWebsite(deal.data.website)"
|
||||
class="rounded-full h-8 w-8"
|
||||
/>
|
||||
<Button class="h-7 w-7">
|
||||
<LinkIcon
|
||||
class="h-4 w-4"
|
||||
@click="openWebsite(deal.data.website)"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<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', '')
|
||||
},
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Button icon="more-horizontal" class="rounded-full h-8 w-8" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FileUploader>
|
||||
<div class="flex-1 flex flex-col justify-between overflow-hidden">
|
||||
<div class="flex flex-col gap-6 p-3 overflow-y-auto">
|
||||
<div
|
||||
v-for="section in detailSections"
|
||||
:key="section.label"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }">
|
||||
<div
|
||||
class="flex items-center gap-2 text-base font-semibold leading-5 pl-2 pr-3 cursor-pointer max-w-fit"
|
||||
@click="toggle()"
|
||||
>
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 text-gray-600 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': opened }"
|
||||
/>
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="duration-300 ease-in"
|
||||
leave-active-class="duration-300 ease-[cubic-bezier(0, 1, 0.5, 1)]"
|
||||
enter-to-class="max-h-[200px] overflow-hidden"
|
||||
leave-from-class="max-h-[200px] overflow-hidden"
|
||||
enter-from-class="max-h-0 overflow-hidden"
|
||||
leave-to-class="max-h-0 overflow-hidden"
|
||||
>
|
||||
<div v-if="opened" class="flex flex-col gap-1.5">
|
||||
<div
|
||||
v-for="field in section.fields"
|
||||
:key="field.label"
|
||||
class="flex items-center px-3 gap-2 text-base leading-5 first:mt-3"
|
||||
>
|
||||
<div class="text-gray-600 w-[106px]">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<FormControl
|
||||
v-if="field.type === 'select'"
|
||||
type="select"
|
||||
:options="field.options"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="dealStatuses[deal.data[field.name]].color"
|
||||
/>
|
||||
</template>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'email'"
|
||||
type="email"
|
||||
class="form-control"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'link'"
|
||||
:value="deal.data[field.name]"
|
||||
:options="field.options"
|
||||
@change="(e) => field.change(e)"
|
||||
:placeholder="field.placeholder"
|
||||
class="form-control"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'user'"
|
||||
:options="activeAgents"
|
||||
:value="getUser(deal.data[field.name]).full_name"
|
||||
@change="
|
||||
(option) => updateAssignedAgent(option.email)
|
||||
"
|
||||
class="form-control"
|
||||
:placeholder="deal.placeholder"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="togglePopover()"
|
||||
:label="getUser(deal.data[field.name]).full_name"
|
||||
class="!justify-start w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserAvatar
|
||||
:user="deal.data[field.name]"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
<template #item-prefix="{ option }">
|
||||
<UserAvatar
|
||||
class="mr-2"
|
||||
:user="option.email"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="
|
||||
statusDropdownOptions(deal.data, 'deal', updateDeal)
|
||||
"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="deal.data[field.name]"
|
||||
class="justify-between w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="
|
||||
dealStatuses[deal.data[field.name]].color
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{
|
||||
deal.data[field.name]
|
||||
}}</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'date'"
|
||||
type="date"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'number'"
|
||||
type="number"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'tel'"
|
||||
type="tel"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
<FormControl
|
||||
v-else
|
||||
type="text"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
<ErrorMessage :message="error" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-1 text-sm px-6 p-3 leading-5 cursor-pointer"
|
||||
>
|
||||
<span class="text-gray-600">Created </span>
|
||||
<Tooltip :text="dateFormat(deal.data.creation, dateTooltipFormat)">
|
||||
{{ timeAgo(deal.data.creation) }}
|
||||
</Tooltip>
|
||||
<span> · </span>
|
||||
<span class="text-gray-600">Updated </span>
|
||||
<Tooltip :text="dateFormat(deal.data.modified, dateTooltipFormat)">
|
||||
{{ timeAgo(deal.data.modified) }}
|
||||
</Tooltip>
|
||||
</template>
|
||||
</FileUploader>
|
||||
<div class="flex-1 flex flex-col justify-between overflow-hidden">
|
||||
<div class="flex flex-col overflow-y-auto">
|
||||
<div
|
||||
v-for="(section, i) in detailSections"
|
||||
:key="section.label"
|
||||
class="flex flex-col p-3"
|
||||
:class="{ 'border-b': i !== detailSections.length - 1 }"
|
||||
>
|
||||
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }">
|
||||
<div
|
||||
class="flex items-center gap-2 text-base font-semibold leading-5 pl-2 pr-3 cursor-pointer max-w-fit"
|
||||
@click="toggle()"
|
||||
>
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 text-gray-600 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': opened }"
|
||||
/>
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="duration-300 ease-in"
|
||||
leave-active-class="duration-300 ease-[cubic-bezier(0, 1, 0.5, 1)]"
|
||||
enter-to-class="max-h-[200px] overflow-hidden"
|
||||
leave-from-class="max-h-[200px] overflow-hidden"
|
||||
enter-from-class="max-h-0 overflow-hidden"
|
||||
leave-to-class="max-h-0 overflow-hidden"
|
||||
>
|
||||
<div v-if="opened" class="flex flex-col gap-1.5">
|
||||
<div
|
||||
v-for="field in section.fields"
|
||||
:key="field.label"
|
||||
class="flex items-center px-3 gap-2 text-base leading-5 first:mt-3"
|
||||
>
|
||||
<div class="text-gray-600 w-[106px]">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<FormControl
|
||||
v-if="field.type === 'select'"
|
||||
type="select"
|
||||
:options="field.options"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="dealStatuses[deal.data[field.name]].color"
|
||||
/>
|
||||
</template>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'email'"
|
||||
type="email"
|
||||
class="form-control"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'link'"
|
||||
:value="deal.data[field.name]"
|
||||
:options="field.options"
|
||||
@change="(e) => field.change(e)"
|
||||
:placeholder="field.placeholder"
|
||||
class="form-control"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'user'"
|
||||
:options="activeAgents"
|
||||
:value="getUser(deal.data[field.name]).full_name"
|
||||
@change="(option) => updateAssignedAgent(option.email)"
|
||||
class="form-control"
|
||||
:placeholder="deal.placeholder"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="togglePopover()"
|
||||
:label="getUser(deal.data[field.name]).full_name"
|
||||
class="!justify-start w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserAvatar
|
||||
:user="deal.data[field.name]"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
<template #item-prefix="{ option }">
|
||||
<UserAvatar
|
||||
class="mr-2"
|
||||
:user="option.email"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="
|
||||
statusDropdownOptions(deal.data, 'deal', updateDeal)
|
||||
"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="deal.data[field.name]"
|
||||
class="justify-between w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="
|
||||
dealStatuses[deal.data[field.name]].color
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{
|
||||
deal.data[field.name]
|
||||
}}</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'date'"
|
||||
type="date"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'number'"
|
||||
type="number"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'tel'"
|
||||
type="tel"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
<FormControl
|
||||
v-else
|
||||
type="text"
|
||||
:value="deal.data[field.name]"
|
||||
@change.stop="
|
||||
updateDeal(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabGroup>
|
||||
</div>
|
||||
<NoteModal v-model="showNoteModal" :note="note" @updateNote="updateNote" />
|
||||
</template>
|
||||
<script setup>
|
||||
@ -354,19 +349,17 @@ 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 LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Toggler from '@/components/Toggler.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import CommunicationArea from '@/components/CommunicationArea.vue'
|
||||
import NoteModal from '@/components/NoteModal.vue'
|
||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
|
||||
import { TransitionPresets, useTransition } from '@vueuse/core'
|
||||
import {
|
||||
dateFormat,
|
||||
timeAgo,
|
||||
dateTooltipFormat,
|
||||
dealStatuses,
|
||||
statusDropdownOptions,
|
||||
openWebsite,
|
||||
@ -377,7 +370,6 @@ import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import {
|
||||
createResource,
|
||||
createDocumentResource,
|
||||
createListResource,
|
||||
FeatherIcon,
|
||||
FileUploader,
|
||||
@ -408,17 +400,6 @@ const deal = createResource({
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const uDeal = createDocumentResource({
|
||||
doctype: 'CRM Lead',
|
||||
name: props.dealId,
|
||||
setValue: {
|
||||
onSuccess: () => {
|
||||
deal.reload()
|
||||
contacts.reload()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function updateDeal(fieldname, value) {
|
||||
createResource({
|
||||
url: 'frappe.client.set_value',
|
||||
@ -464,7 +445,7 @@ const tabs = computed(() => {
|
||||
label: 'Activity',
|
||||
icon: ActivityIcon,
|
||||
content: all_activities(),
|
||||
activityTitle: 'Activity log',
|
||||
activityTitle: 'Activity',
|
||||
},
|
||||
{
|
||||
label: 'Emails',
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
@ -19,17 +19,14 @@
|
||||
</Autocomplete>
|
||||
<Dropdown :options="statusDropdownOptions(lead.data, 'lead', updateLead)">
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="lead.data.status"
|
||||
:class="leadStatuses[lead.data.status].bgColor"
|
||||
>
|
||||
<Button :label="lead.data.status">
|
||||
<template #prefix>
|
||||
<IndicatorIcon :class="leadStatuses[lead.data.status].color" />
|
||||
</template>
|
||||
<template #suffix
|
||||
><FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/></template>
|
||||
</Button>
|
||||
</template>
|
||||
@ -41,118 +38,135 @@
|
||||
/>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<TabGroup v-slot="{ selectedIndex }" v-if="lead.data" @change="onTabChange">
|
||||
<TabList class="flex items-center gap-6 border-b pl-5 relative">
|
||||
<Tab
|
||||
ref="tabRef"
|
||||
as="template"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
v-slot="{ selected }"
|
||||
>
|
||||
<button
|
||||
class="flex items-center gap-2 py-[9px] -mb-[1px] text-base text-gray-600 border-b border-transparent hover:text-gray-900 hover:border-gray-400 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'text-gray-900': selected }"
|
||||
<div v-if="lead.data" class="flex h-full overflow-hidden">
|
||||
<TabGroup as="div" class="flex flex-col flex-1" @change="onTabChange">
|
||||
<TabList class="flex items-center gap-6 border-b pl-5 relative">
|
||||
<Tab
|
||||
ref="tabRef"
|
||||
as="template"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
v-slot="{ selected }"
|
||||
>
|
||||
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</Tab>
|
||||
<div
|
||||
ref="indicator"
|
||||
class="h-[1px] bg-gray-900 w-[82px] absolute -bottom-[1px]"
|
||||
:style="{ left: `${indicatorLeftValue}px` }"
|
||||
/>
|
||||
</TabList>
|
||||
<div class="flex h-full overflow-hidden">
|
||||
<div class="flex-1 flex flex-col">
|
||||
<TabPanels class="flex flex-1 overflow-hidden">
|
||||
<TabPanel
|
||||
class="flex-1 flex flex-col overflow-y-auto"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
<button
|
||||
class="flex items-center gap-2 py-2.5 -mb-[1px] text-base text-gray-600 border-b border-transparent hover:text-gray-900 hover:border-gray-400 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'text-gray-900': selected }"
|
||||
>
|
||||
<Activities
|
||||
:title="tab.activityTitle"
|
||||
:activities="tab.content"
|
||||
@makeCall="makeCall(lead.data.mobile_no)"
|
||||
@makeNote="(e) => showNote(e)"
|
||||
@deleteNote="(e) => deleteNote(e)"
|
||||
@setFocusOnEmail="() => $refs.sendEmailRef.el.click()"
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
<CommunicationArea
|
||||
ref="sendEmailRef"
|
||||
v-if="[0, 1].includes(selectedIndex)"
|
||||
v-model="lead"
|
||||
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</Tab>
|
||||
<div
|
||||
ref="indicator"
|
||||
class="h-[1px] bg-gray-900 w-[82px] absolute -bottom-[1px]"
|
||||
:style="{ left: `${indicatorLeftValue}px` }"
|
||||
/>
|
||||
</TabList>
|
||||
<TabPanels class="flex flex-1 overflow-hidden">
|
||||
<TabPanel
|
||||
class="flex-1 flex flex-col overflow-y-auto"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.label"
|
||||
>
|
||||
<Activities
|
||||
:title="tab.activityTitle"
|
||||
:activities="tab.content"
|
||||
v-model="lead"
|
||||
@makeCall="makeCall(lead.data.mobile_no)"
|
||||
@makeNote="(e) => showNote(e)"
|
||||
@deleteNote="(e) => deleteNote(e)"
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
<div class="flex flex-col justify-between border-l w-[352px]">
|
||||
<div
|
||||
class="flex items-center border-b px-5 py-2.5 h-[41px] font-semibold text-lg"
|
||||
>
|
||||
About this lead
|
||||
</div>
|
||||
<div class="flex flex-col justify-between border-l w-[370px]">
|
||||
<FileUploader @success="changeLeadImage" :validateFile="validateFile">
|
||||
<template #default="{ openFileSelector, error }">
|
||||
<div
|
||||
class="flex flex-col gap-3 pb-4 p-5 items-center justify-center border-b"
|
||||
>
|
||||
<FileUploader @success="changeLeadImage" :validateFile="validateFile">
|
||||
<template #default="{ openFileSelector, error }">
|
||||
<div class="flex gap-5 items-center justify-start p-5">
|
||||
<div class="relative w-[88px] h-[88px] group">
|
||||
<Avatar
|
||||
size="3xl"
|
||||
class="w-[88px] h-[88px]"
|
||||
:label="lead.data.first_name"
|
||||
:image="lead.data.image"
|
||||
/>
|
||||
<ErrorMessage :message="error" />
|
||||
<div class="font-medium text-2xl">{{ lead.data.lead_name }}</div>
|
||||
<div class="flex gap-3">
|
||||
<Dropdown
|
||||
:options="[
|
||||
{
|
||||
icon: 'upload',
|
||||
label: lead.data.image ? 'Change photo' : 'Upload photo',
|
||||
onClick: openFileSelector,
|
||||
},
|
||||
{
|
||||
icon: 'trash-2',
|
||||
label: 'Remove photo',
|
||||
onClick: () => {
|
||||
lead.data.image = ''
|
||||
updateLead('image', '')
|
||||
},
|
||||
},
|
||||
]"
|
||||
class="!absolute bottom-0 left-0 right-0"
|
||||
>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 rounded-b-full z-1 h-11 flex items-center justify-center pt-3 bg-black bg-opacity-40 cursor-pointer opacity-0 group-hover:opacity-100 duration-300 ease-in-out"
|
||||
style="
|
||||
-webkit-clip-path: inset(12px 0 0 0);
|
||||
clip-path: inset(12px 0 0 0);
|
||||
"
|
||||
>
|
||||
<CameraIcon class="h-6 w-6 text-white cursor-pointer" />
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 truncate">
|
||||
<Tooltip :text="lead.data.lead_name">
|
||||
<div class="font-medium text-2xl truncate">
|
||||
{{ lead.data.lead_name }}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="flex gap-1.5">
|
||||
<Tooltip text="Make a call...">
|
||||
<Button
|
||||
class="rounded-full h-8 w-8"
|
||||
class="h-7 w-7"
|
||||
@click="() => makeCall(lead.data.mobile_no)"
|
||||
>
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button class="rounded-full h-8 w-8">
|
||||
<Button class="h-7 w-7">
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Tooltip text="Go to website...">
|
||||
<Button
|
||||
icon="link"
|
||||
@click="openWebsite(lead.data.website)"
|
||||
class="rounded-full h-8 w-8"
|
||||
/>
|
||||
<Button class="h-7 w-7">
|
||||
<LinkIcon
|
||||
class="h-4 w-4"
|
||||
@click="openWebsite(lead.data.website)"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
:options="[
|
||||
{
|
||||
icon: 'upload',
|
||||
label: lead.data.image ? 'Change photo' : 'Upload photo',
|
||||
onClick: openFileSelector,
|
||||
},
|
||||
{
|
||||
icon: 'trash-2',
|
||||
label: 'Remove photo',
|
||||
onClick: () => {
|
||||
lead.data.image = ''
|
||||
updateLead('image', '')
|
||||
},
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Button icon="more-horizontal" class="rounded-full h-8 w-8" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
<ErrorMessage :message="error" />
|
||||
</div>
|
||||
</template>
|
||||
</FileUploader>
|
||||
<div class="flex-1 flex flex-col justify-between overflow-hidden">
|
||||
<div class="flex flex-col gap-6 p-3 overflow-y-auto">
|
||||
<div
|
||||
v-for="section in detailSections"
|
||||
:key="section.label"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }">
|
||||
</div>
|
||||
</template>
|
||||
</FileUploader>
|
||||
<div class="flex-1 flex flex-col justify-between overflow-hidden">
|
||||
<div class="flex flex-col overflow-y-auto">
|
||||
<div
|
||||
v-for="(section, i) in detailSections"
|
||||
:key="section.label"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }">
|
||||
<div class="sticky bg-white top-0 p-3 border-t z-10">
|
||||
<div
|
||||
class="flex items-center gap-2 text-base font-semibold leading-5 pl-2 pr-3 cursor-pointer max-w-fit"
|
||||
class="flex items-center gap-2 text-base font-semibold leading-5 px-2 cursor-pointer max-w-fit"
|
||||
@click="toggle()"
|
||||
>
|
||||
<FeatherIcon
|
||||
@ -162,157 +176,142 @@
|
||||
/>
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="duration-300 ease-in"
|
||||
leave-active-class="duration-300 ease-[cubic-bezier(0, 1, 0.5, 1)]"
|
||||
enter-to-class="max-h-[200px] overflow-hidden"
|
||||
leave-from-class="max-h-[200px] overflow-hidden"
|
||||
enter-from-class="max-h-0 overflow-hidden"
|
||||
leave-to-class="max-h-0 overflow-hidden"
|
||||
>
|
||||
<div v-if="opened" class="flex flex-col gap-1.5">
|
||||
<div
|
||||
v-for="field in section.fields"
|
||||
:key="field.name"
|
||||
class="flex items-center px-3 gap-2 text-base leading-5 first:mt-3"
|
||||
>
|
||||
<div class="text-gray-600 w-[106px]">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<FormControl
|
||||
v-if="field.type === 'select'"
|
||||
type="select"
|
||||
:options="field.options"
|
||||
:value="lead.data[field.name]"
|
||||
@change.stop="
|
||||
updateLead(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="leadStatuses[lead.data[field.name]].color"
|
||||
/>
|
||||
</template>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'email'"
|
||||
type="email"
|
||||
class="form-control"
|
||||
:value="lead.data[field.name]"
|
||||
@change.stop="
|
||||
updateLead(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'link'"
|
||||
:value="lead.data[field.name]"
|
||||
:options="field.options"
|
||||
@change="(e) => field.change(e)"
|
||||
:placeholder="field.placeholder"
|
||||
class="form-control"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'user'"
|
||||
:options="activeAgents"
|
||||
:value="getUser(lead.data[field.name]).full_name"
|
||||
@change="
|
||||
(option) => updateAssignedAgent(option.email)
|
||||
"
|
||||
class="form-control"
|
||||
:placeholder="field.placeholder"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="togglePopover()"
|
||||
:label="getUser(lead.data[field.name]).full_name"
|
||||
class="!justify-start w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserAvatar
|
||||
:user="lead.data[field.name]"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
<template #item-prefix="{ option }">
|
||||
<UserAvatar
|
||||
class="mr-2"
|
||||
:user="option.email"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="
|
||||
statusDropdownOptions(lead.data, 'lead', updateLead)
|
||||
"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="lead.data[field.name]"
|
||||
class="justify-between w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="
|
||||
leadStatuses[lead.data[field.name]].color
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{
|
||||
lead.data[field.name]
|
||||
}}</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<FormControl
|
||||
v-else
|
||||
type="text"
|
||||
:value="lead.data[field.name]"
|
||||
@change.stop="
|
||||
updateLead(field.name, $event.target.value)
|
||||
"
|
||||
class="form-control"
|
||||
:debounce="500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<transition
|
||||
enter-active-class="duration-300 ease-in"
|
||||
leave-active-class="duration-300 ease-[cubic-bezier(0, 1, 0.5, 1)]"
|
||||
enter-to-class="max-h-[200px] overflow-hidden"
|
||||
leave-from-class="max-h-[200px] overflow-hidden"
|
||||
enter-from-class="max-h-0 overflow-hidden"
|
||||
leave-to-class="max-h-0 overflow-hidden"
|
||||
>
|
||||
<div v-if="opened" class="flex flex-col gap-1.5 p-3 pt-0">
|
||||
<div
|
||||
v-for="field in section.fields"
|
||||
:key="field.name"
|
||||
class="flex items-center px-3 gap-2 text-base leading-5"
|
||||
>
|
||||
<div class="text-gray-600 w-[106px]">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<FormControl
|
||||
v-if="field.type === 'select'"
|
||||
type="select"
|
||||
:options="field.options"
|
||||
:value="lead.data[field.name]"
|
||||
@change.stop="
|
||||
updateLead(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="leadStatuses[lead.data[field.name]].color"
|
||||
/>
|
||||
</template>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'email'"
|
||||
type="email"
|
||||
class="form-control"
|
||||
:value="lead.data[field.name]"
|
||||
@change.stop="
|
||||
updateLead(field.name, $event.target.value)
|
||||
"
|
||||
:debounce="500"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'link'"
|
||||
:value="lead.data[field.name]"
|
||||
:options="field.options"
|
||||
@change="(e) => field.change(e)"
|
||||
:placeholder="field.placeholder"
|
||||
class="form-control"
|
||||
/>
|
||||
<Autocomplete
|
||||
v-else-if="field.type === 'user'"
|
||||
:options="activeAgents"
|
||||
:value="getUser(lead.data[field.name]).full_name"
|
||||
@change="(option) => updateAssignedAgent(option.email)"
|
||||
class="form-control"
|
||||
:placeholder="field.placeholder"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="togglePopover()"
|
||||
:label="getUser(lead.data[field.name]).full_name"
|
||||
class="!justify-start w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserAvatar
|
||||
:user="lead.data[field.name]"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
<template #item-prefix="{ option }">
|
||||
<UserAvatar
|
||||
class="mr-2"
|
||||
:user="option.email"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="
|
||||
statusDropdownOptions(lead.data, 'lead', updateLead)
|
||||
"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="lead.data[field.name]"
|
||||
class="justify-between w-full"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="
|
||||
leadStatuses[lead.data[field.name]].color
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{
|
||||
lead.data[field.name]
|
||||
}}</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<FormControl
|
||||
v-else
|
||||
type="text"
|
||||
:value="lead.data[field.name]"
|
||||
@change.stop="
|
||||
updateLead(field.name, $event.target.value)
|
||||
"
|
||||
class="form-control"
|
||||
:debounce="500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-1 text-sm px-6 p-3 leading-5 cursor-pointer"
|
||||
>
|
||||
<span class="text-gray-600">Created </span>
|
||||
<Tooltip :text="dateFormat(lead.data.creation, dateTooltipFormat)">
|
||||
{{ timeAgo(lead.data.creation) }}
|
||||
</Tooltip>
|
||||
<span> · </span>
|
||||
<span class="text-gray-600">Updated </span>
|
||||
<Tooltip :text="dateFormat(lead.data.modified, dateTooltipFormat)">
|
||||
{{ timeAgo(lead.data.modified) }}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabGroup>
|
||||
</div>
|
||||
<NoteModal v-model="showNoteModal" :note="note" @updateNote="updateNote" />
|
||||
</template>
|
||||
<script setup>
|
||||
@ -322,19 +321,16 @@ 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 LinkIcon from '@/components/Icons/LinkIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Toggler from '@/components/Toggler.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import CommunicationArea from '@/components/CommunicationArea.vue'
|
||||
import NoteModal from '@/components/NoteModal.vue'
|
||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
|
||||
import { TransitionPresets, useTransition } from '@vueuse/core'
|
||||
import {
|
||||
dateFormat,
|
||||
timeAgo,
|
||||
dateTooltipFormat,
|
||||
leadStatuses,
|
||||
statusDropdownOptions,
|
||||
openWebsite,
|
||||
@ -345,7 +341,6 @@ import { usersStore } from '@/stores/users'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import {
|
||||
createResource,
|
||||
createDocumentResource,
|
||||
createListResource,
|
||||
FileUploader,
|
||||
ErrorMessage,
|
||||
@ -359,6 +354,7 @@ 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 { getContact, contacts } = contactsStore()
|
||||
@ -423,7 +419,7 @@ const tabs = computed(() => {
|
||||
label: 'Activity',
|
||||
icon: ActivityIcon,
|
||||
content: all_activities(),
|
||||
activityTitle: 'Activity log',
|
||||
activityTitle: 'Activity',
|
||||
},
|
||||
{
|
||||
label: 'Emails',
|
||||
@ -491,7 +487,7 @@ function onTabChange(index) {
|
||||
const detailSections = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: 'About this lead',
|
||||
label: 'Details',
|
||||
opened: true,
|
||||
fields: [
|
||||
{
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<template #suffix
|
||||
><FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4"
|
||||
class="h-4 text-gray-600"
|
||||
/></template>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@ -22,58 +22,49 @@ export function timeAgo(date) {
|
||||
export const dateTooltipFormat = 'ddd, MMM D, YYYY h:mm A'
|
||||
|
||||
export const leadStatuses = {
|
||||
Open: { label: 'Open', color: '!text-gray-600', bgColor: '!bg-gray-200' },
|
||||
Open: { label: 'Open', color: '!text-gray-600' },
|
||||
Contacted: {
|
||||
label: 'Contacted',
|
||||
color: '!text-orange-600',
|
||||
bgColor: '!bg-orange-200',
|
||||
},
|
||||
Nurture: {
|
||||
label: 'Nurture',
|
||||
color: '!text-blue-600',
|
||||
bgColor: '!bg-blue-200',
|
||||
},
|
||||
Qualified: {
|
||||
label: 'Qualified',
|
||||
color: '!text-green-600',
|
||||
bgColor: '!bg-green-200',
|
||||
},
|
||||
Unqualified: {
|
||||
label: 'Unqualified',
|
||||
color: '!text-red-600',
|
||||
bgColor: '!bg-red-200',
|
||||
},
|
||||
Junk: { label: 'Junk', color: '!text-purple-600', bgColor: '!bg-purple-200' },
|
||||
Junk: { label: 'Junk', color: '!text-purple-600' },
|
||||
}
|
||||
|
||||
export const dealStatuses = {
|
||||
Qualification: {
|
||||
label: 'Qualification',
|
||||
color: '!text-gray-600',
|
||||
bgColor: '!bg-gray-200',
|
||||
},
|
||||
'Demo/Making': {
|
||||
label: 'Demo/Making',
|
||||
color: '!text-orange-600',
|
||||
bgColor: '!bg-orange-200',
|
||||
},
|
||||
'Proposal/Quotation': {
|
||||
label: 'Proposal/Quotation',
|
||||
color: '!text-blue-600',
|
||||
bgColor: '!bg-blue-200',
|
||||
},
|
||||
Negotiation: {
|
||||
label: 'Negotiation',
|
||||
color: '!text-yellow-600',
|
||||
bgColor: '!bg-yellow-100',
|
||||
},
|
||||
'Ready to Close': {
|
||||
label: 'Ready to Close',
|
||||
color: '!text-purple-600',
|
||||
bgColor: '!bg-purple-200',
|
||||
},
|
||||
Won: { label: 'Won', color: '!text-green-600', bgColor: '!bg-green-200' },
|
||||
Lost: { label: 'Lost', color: '!text-red-600', bgColor: '!bg-red-200' },
|
||||
Won: { label: 'Won', color: '!text-green-600' },
|
||||
Lost: { label: 'Lost', color: '!text-red-600' },
|
||||
}
|
||||
|
||||
export function statusDropdownOptions(data, doctype, action) {
|
||||
|
||||
@ -4662,10 +4662,10 @@ fraction.js@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
|
||||
|
||||
frappe-ui@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.1.tgz#aa878105d7b11209efa05f1c3965c91f0dcb220b"
|
||||
integrity sha512-FlAxNzJmAf8B5NGrQYyDdznoF5MRB9bTaHacrmpgaXWuoVSknLeW02eHIoyYMBd1Q3Fwp7GvEhz6kk1ZRzUsIQ==
|
||||
frappe-ui@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.5.tgz#59e5576ad1511e6d097a88fc065d5990a2ce9624"
|
||||
integrity sha512-05Ikc0xLt7Pq2OP95wmQWdMzEJwJ1k8NSX57uiAMJLRNkjQTykvsn1G70/XcHxQnOhd5QOs+zKNMrapvmKuwDw==
|
||||
dependencies:
|
||||
"@headlessui/vue" "^1.7.14"
|
||||
"@popperjs/core" "^2.11.2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user