fix: create new lead
This commit is contained in:
parent
f6eb651ed7
commit
dc9858ff87
216
frontend/src/components/NewLead.vue
Normal file
216
frontend/src/components/NewLead.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div v-for="section in allFields" :key="section.section">
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<div v-for="field in section.fields" :key="field.name">
|
||||||
|
<div class="text-gray-600 text-sm mb-2">{{ field.label }}</div>
|
||||||
|
<FormControl
|
||||||
|
v-if="field.type === 'select'"
|
||||||
|
type="select"
|
||||||
|
:options="field.options"
|
||||||
|
v-model="newLead[field.name]"
|
||||||
|
>
|
||||||
|
<template v-if="field.name == 'status'" #prefix>
|
||||||
|
<IndicatorIcon :class="indicatorColor[newLead[field.name]]" />
|
||||||
|
</template>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
v-else-if="field.type === 'email'"
|
||||||
|
type="email"
|
||||||
|
v-model="newLead[field.name]"
|
||||||
|
/>
|
||||||
|
<Autocomplete
|
||||||
|
v-else-if="field.type === 'link'"
|
||||||
|
:options="activeAgents"
|
||||||
|
:value="getUser(newLead[field.name]).full_name"
|
||||||
|
@change="(option) => (newLead[field.name] = option.email)"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<UserAvatar class="mr-2" :user="newLead[field.name]" size="sm" />
|
||||||
|
</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"
|
||||||
|
class="w-full flex-1"
|
||||||
|
>
|
||||||
|
<template #default="{ open }">
|
||||||
|
<Button
|
||||||
|
:label="newLead[field.name]"
|
||||||
|
class="justify-between w-full"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<IndicatorIcon :class="indicatorColor[newLead[field.name]]" />
|
||||||
|
</template>
|
||||||
|
<template #default>{{ newLead[field.name] }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<FeatherIcon
|
||||||
|
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||||
|
class="h-4"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
|
<FormControl v-else type="text" v-model="newLead[field.name]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||||
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
|
import { usersStore } from '@/stores/users'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
Button,
|
||||||
|
Autocomplete,
|
||||||
|
Dropdown,
|
||||||
|
FeatherIcon,
|
||||||
|
} from 'frappe-ui'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const { getUser, users } = usersStore()
|
||||||
|
const props = defineProps({
|
||||||
|
newLead: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusDropdownOptions = [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
icon: () => h(IndicatorIcon, { class: '!text-gray-600' }),
|
||||||
|
onClick: () => {
|
||||||
|
newLead.status = 'New'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Contact made',
|
||||||
|
icon: () => h(IndicatorIcon, { class: 'text-orange-600' }),
|
||||||
|
onClick: () => {
|
||||||
|
newLead.status = 'Contact made'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Proposal made',
|
||||||
|
icon: () => h(IndicatorIcon, { class: '!text-blue-600' }),
|
||||||
|
onClick: () => {
|
||||||
|
newLead.status = 'Proposal made'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Negotiation',
|
||||||
|
icon: () => h(IndicatorIcon, { class: 'text-red-600' }),
|
||||||
|
onClick: () => {
|
||||||
|
newLead.status = 'Negotiation'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Converted',
|
||||||
|
icon: () => h(IndicatorIcon, { class: 'text-green-600' }),
|
||||||
|
onClick: () => {
|
||||||
|
newLead.status = 'Converted'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const allFields = [
|
||||||
|
{
|
||||||
|
section: 'Lead Details',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Salutation',
|
||||||
|
name: 'salutation',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Mr',
|
||||||
|
value: 'Mr',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Ms',
|
||||||
|
value: 'Ms',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mrs',
|
||||||
|
value: 'Mrs',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Last Name',
|
||||||
|
name: 'last_name',
|
||||||
|
type: 'data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mobile no',
|
||||||
|
name: 'mobile_no',
|
||||||
|
type: 'data',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: 'Other Details',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Organization',
|
||||||
|
name: 'organization_name',
|
||||||
|
type: 'data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Status',
|
||||||
|
name: 'status',
|
||||||
|
type: 'select',
|
||||||
|
options: statusDropdownOptions,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lead owner',
|
||||||
|
name: 'lead_owner',
|
||||||
|
type: 'link',
|
||||||
|
placeholder: 'Lead owner',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const indicatorColor = {
|
||||||
|
New: 'text-gray-600',
|
||||||
|
'Contact made': 'text-orange-500',
|
||||||
|
'Proposal made': 'text-blue-600',
|
||||||
|
Negotiation: 'text-red-600',
|
||||||
|
Converted: 'text-green-600',
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeAgents = computed(() => {
|
||||||
|
const nonAgents = ['Administrator', 'Guest']
|
||||||
|
return users.data
|
||||||
|
.filter((user) => !nonAgents.includes(user.name))
|
||||||
|
.sort((a, b) => a.full_name - b.full_name)
|
||||||
|
.map((user) => {
|
||||||
|
return {
|
||||||
|
label: user.full_name,
|
||||||
|
value: user.email,
|
||||||
|
...user,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
@ -4,7 +4,7 @@
|
|||||||
<Breadcrumbs :items="[{ label: list.title }]" />
|
<Breadcrumbs :items="[{ label: list.title }]" />
|
||||||
</template>
|
</template>
|
||||||
<template #right-header>
|
<template #right-header>
|
||||||
<Button variant="solid" label="Create">
|
<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>
|
||||||
@ -35,6 +35,23 @@
|
|||||||
</template>
|
</template>
|
||||||
</LayoutHeader>
|
</LayoutHeader>
|
||||||
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
||||||
|
<Dialog
|
||||||
|
v-model="showNewDialog"
|
||||||
|
:options="{
|
||||||
|
size: '3xl',
|
||||||
|
title: 'New Lead',
|
||||||
|
actions: [{ label: 'Save', variant: 'solid' }],
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #body-content>
|
||||||
|
<NewLead :newLead="newLead" />
|
||||||
|
</template>
|
||||||
|
<template #actions="{ close }">
|
||||||
|
<div class="flex flex-row-reverse gap-2">
|
||||||
|
<Button variant="solid" label="Save" @click="createNewLead(close)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -43,9 +60,18 @@ import LayoutHeader from '@/components/LayoutHeader.vue'
|
|||||||
import Breadcrumbs from '@/components/Breadcrumbs.vue'
|
import Breadcrumbs from '@/components/Breadcrumbs.vue'
|
||||||
import SortIcon from '@/components/Icons/SortIcon.vue'
|
import SortIcon from '@/components/Icons/SortIcon.vue'
|
||||||
import FilterIcon from '@/components/Icons/FilterIcon.vue'
|
import FilterIcon from '@/components/Icons/FilterIcon.vue'
|
||||||
|
import NewLead from '@/components/NewLead.vue'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
import { FeatherIcon, Button, Dropdown, createListResource } from 'frappe-ui'
|
import {
|
||||||
import { ref, computed } from 'vue'
|
FeatherIcon,
|
||||||
|
Dialog,
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
createListResource,
|
||||||
|
createResource,
|
||||||
|
} from 'frappe-ui'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ref, computed, reactive } from 'vue'
|
||||||
|
|
||||||
const list = {
|
const list = {
|
||||||
title: 'Leads',
|
title: 'Leads',
|
||||||
@ -194,4 +220,52 @@ const indicatorColor = {
|
|||||||
Negotiation: 'text-red-600',
|
Negotiation: 'text-red-600',
|
||||||
Converted: 'text-green-600',
|
Converted: 'text-green-600',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showNewDialog = ref(false)
|
||||||
|
|
||||||
|
let newLead = reactive({
|
||||||
|
salutation: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
lead_name: '',
|
||||||
|
organization_name: '',
|
||||||
|
status: 'New',
|
||||||
|
email: '',
|
||||||
|
mobile_no: '',
|
||||||
|
lead_owner: getUser().email,
|
||||||
|
})
|
||||||
|
|
||||||
|
const createLead = createResource({
|
||||||
|
url: 'frappe.client.insert',
|
||||||
|
makeParams(values) {
|
||||||
|
return {
|
||||||
|
doc: {
|
||||||
|
doctype: 'CRM Lead',
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
function createNewLead(close) {
|
||||||
|
createLead
|
||||||
|
.submit(newLead, {
|
||||||
|
validate() {
|
||||||
|
if (!newLead.first_name) {
|
||||||
|
return 'First name is required'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess(data) {
|
||||||
|
router.push({
|
||||||
|
name: 'Lead',
|
||||||
|
params: {
|
||||||
|
leadId: data.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(close)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user