fix: created Section Component using Toggler component and used in lead/deal
This commit is contained in:
parent
36455b8faf
commit
b157f255f8
56
frontend/src/components/Section.vue
Normal file
56
frontend/src/components/Section.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<slot name="header" v-bind="{ opened, open, close, toggle }">
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
class="flex h-7 max-w-fit cursor-pointer items-center gap-2 pl-2 pr-3 text-base font-semibold leading-5"
|
||||
@click="toggle()"
|
||||
>
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 text-gray-900 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': opened }"
|
||||
/>
|
||||
{{ label || 'Untitled' }}
|
||||
</div>
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</slot>
|
||||
<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">
|
||||
<slot v-bind="{ opened, open, close, toggle }" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script setup>
|
||||
import { FeatherIcon } from 'frappe-ui'
|
||||
import { ref } from 'vue'
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isOpened: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
function toggle() {
|
||||
opened.value = !opened.value
|
||||
}
|
||||
|
||||
function open() {
|
||||
opened.value = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
opened.value = false
|
||||
}
|
||||
let opened = ref(props.isOpened)
|
||||
</script>
|
||||
@ -1,69 +1,71 @@
|
||||
<template>
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.label"
|
||||
class="flex items-center gap-2 px-3 text-base leading-5 first:mt-3"
|
||||
>
|
||||
<div class="w-[106px] shrink-0 text-gray-600">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<Link
|
||||
v-if="field.type === 'link'"
|
||||
class="form-control"
|
||||
:value="data[field.name]"
|
||||
:doctype="field.doctype"
|
||||
:placeholder="field.placeholder"
|
||||
@change="(data) => emit('update', field.name, data)"
|
||||
:onCreate="field.create"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'select'"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||
type="select"
|
||||
:value="data[field.name]"
|
||||
:options="field.options"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'email'"
|
||||
class="form-control"
|
||||
type="email"
|
||||
:value="data[field.name]"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'date'"
|
||||
class="form-control"
|
||||
type="date"
|
||||
:value="data[field.name]"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
<Tooltip
|
||||
v-else-if="field.type === 'read_only'"
|
||||
class="flex h-7 cursor-pointer items-center px-2 py-1"
|
||||
:text="field.tooltip"
|
||||
>
|
||||
{{ field.value }}
|
||||
</Tooltip>
|
||||
<FormControl
|
||||
v-else
|
||||
class="form-control"
|
||||
type="text"
|
||||
:value="data[field.name]"
|
||||
:placeholder="field.placeholder"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.label"
|
||||
class="flex items-center gap-2 px-3 text-base leading-5 first:mt-3"
|
||||
>
|
||||
<div class="w-[106px] shrink-0 text-gray-600">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<Link
|
||||
v-if="field.type === 'link'"
|
||||
class="form-control"
|
||||
:value="data[field.name]"
|
||||
:doctype="field.doctype"
|
||||
:placeholder="field.placeholder"
|
||||
@change="(data) => emit('update', field.name, data)"
|
||||
:onCreate="field.create"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'select'"
|
||||
class="form-control cursor-pointer [&_select]:cursor-pointer"
|
||||
type="select"
|
||||
:value="data[field.name]"
|
||||
:options="field.options"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'email'"
|
||||
class="form-control"
|
||||
type="email"
|
||||
:value="data[field.name]"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'date'"
|
||||
class="form-control"
|
||||
type="date"
|
||||
:value="data[field.name]"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
<Tooltip
|
||||
v-else-if="field.type === 'read_only'"
|
||||
class="flex h-7 cursor-pointer items-center px-2 py-1"
|
||||
:text="field.tooltip"
|
||||
>
|
||||
{{ field.value }}
|
||||
</Tooltip>
|
||||
<FormControl
|
||||
v-else
|
||||
class="form-control"
|
||||
type="text"
|
||||
:value="data[field.name]"
|
||||
:placeholder="field.placeholder"
|
||||
:debounce="500"
|
||||
@change.stop="emit('update', field.name, $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<ExternalLinkIcon
|
||||
v-if="field.type === 'link' && field.link && data[field.name]"
|
||||
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
|
||||
@click="field.link(data[field.name])"
|
||||
/>
|
||||
</div>
|
||||
<ExternalLinkIcon
|
||||
v-if="field.type === 'link' && field.link && data[field.name]"
|
||||
class="h-4 w-4 shrink-0 cursor-pointer text-gray-600"
|
||||
@click="field.link(data[field.name])"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<slot v-bind="{ opened, open, close, toggle }"></slot>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const props = defineProps({
|
||||
isOpened: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
function toggle() {
|
||||
opened.value = !opened.value
|
||||
}
|
||||
|
||||
function open() {
|
||||
opened.value = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
opened.value = false
|
||||
}
|
||||
let opened = ref(props.isOpened)
|
||||
</script>
|
||||
@ -101,19 +101,8 @@
|
||||
class="flex flex-col p-3"
|
||||
:class="{ 'border-b': i !== detailSections.data.length - 1 }"
|
||||
>
|
||||
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }">
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
class="flex h-7 max-w-fit cursor-pointer items-center gap-2 pl-2 pr-3 text-base font-semibold leading-5"
|
||||
@click="toggle()"
|
||||
>
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 text-gray-900 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': opened }"
|
||||
/>
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<Section :is-opened="section.opened" :label="section.label">
|
||||
<template #actions>
|
||||
<div v-if="section.contacts" class="pr-2">
|
||||
<Link
|
||||
value=""
|
||||
@ -143,129 +132,103 @@
|
||||
</template>
|
||||
</Link>
|
||||
</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">
|
||||
<SectionFields
|
||||
v-if="section.fields"
|
||||
:fields="section.fields"
|
||||
v-model="deal.data"
|
||||
@update="updateField"
|
||||
/>
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="section.contacts.length"
|
||||
v-for="(contact, i) in section.contacts"
|
||||
:key="contact.name"
|
||||
>
|
||||
<div
|
||||
class="px-2 pb-2.5"
|
||||
:class="[i == 0 ? 'pt-5' : 'pt-2.5']"
|
||||
>
|
||||
<Toggler
|
||||
:is-opened="contact.opened"
|
||||
v-slot="{ opened: cOpened, toggle: cToggle }"
|
||||
</template>
|
||||
<SectionFields
|
||||
v-if="section.fields"
|
||||
:fields="section.fields"
|
||||
v-model="deal.data"
|
||||
@update="updateField"
|
||||
/>
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="section.contacts.length"
|
||||
v-for="(contact, i) in section.contacts"
|
||||
:key="contact.name"
|
||||
>
|
||||
<div
|
||||
class="px-2 pb-2.5"
|
||||
:class="[i == 0 ? 'pt-5' : 'pt-2.5']"
|
||||
>
|
||||
<Section :is-opened="contact.opened">
|
||||
<template #header="{ opened, toggle }">
|
||||
<div
|
||||
class="flex cursor-pointer items-center justify-between gap-2 pr-1 text-base leading-5 text-gray-700"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer items-center justify-between gap-2 pr-1 text-base leading-5 text-gray-700"
|
||||
class="flex h-7 items-center gap-2"
|
||||
@click="toggle()"
|
||||
>
|
||||
<div
|
||||
class="flex h-7 items-center gap-2"
|
||||
@click="cToggle()"
|
||||
>
|
||||
<Avatar
|
||||
:label="
|
||||
getContactByName(contact.name).full_name
|
||||
"
|
||||
:image="getContactByName(contact.name).image"
|
||||
size="md"
|
||||
/>
|
||||
{{ getContactByName(contact.name).full_name }}
|
||||
<Badge
|
||||
v-if="contact.is_primary"
|
||||
class="ml-2"
|
||||
variant="outline"
|
||||
label="Primary"
|
||||
theme="green"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Dropdown :options="contactOptions(contact)">
|
||||
<Button variant="ghost">
|
||||
<FeatherIcon
|
||||
name="more-horizontal"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'Contact',
|
||||
params: { contactId: contact.name },
|
||||
})
|
||||
"
|
||||
>
|
||||
<ExternalLinkIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" @click="cToggle()">
|
||||
<Avatar
|
||||
:label="getContactByName(contact.name).full_name"
|
||||
:image="getContactByName(contact.name).image"
|
||||
size="md"
|
||||
/>
|
||||
{{ getContactByName(contact.name).full_name }}
|
||||
<Badge
|
||||
v-if="contact.is_primary"
|
||||
class="ml-2"
|
||||
variant="outline"
|
||||
label="Primary"
|
||||
theme="green"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Dropdown :options="contactOptions(contact)">
|
||||
<Button variant="ghost">
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 w-4 text-gray-900 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': cOpened }"
|
||||
name="more-horizontal"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</Button>
|
||||
</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="cOpened"
|
||||
class="flex flex-col gap-1.5 text-base text-gray-800"
|
||||
</Dropdown>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'Contact',
|
||||
params: { contactId: contact.name },
|
||||
})
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-3 pb-1.5 pl-1 pt-4"
|
||||
>
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
{{ getContactByName(contact.name).email_id }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-1 py-1.5">
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
{{ getContactByName(contact.name).mobile_no }}
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
</div>
|
||||
<ExternalLinkIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" @click="toggle()">
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 w-4 text-gray-900 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': opened }"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-if="i != section.contacts.length - 1"
|
||||
class="mx-2 h-px border-t border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-20 items-center justify-center text-base text-gray-600"
|
||||
>
|
||||
No contacts added
|
||||
</div>
|
||||
class="flex flex-col gap-1.5 text-base text-gray-800"
|
||||
>
|
||||
<div class="flex items-center gap-3 pb-1.5 pl-1 pt-4">
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
{{ getContactByName(contact.name).email_id }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-1 py-1.5">
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
{{ getContactByName(contact.name).mobile_no }}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
<div
|
||||
v-if="i != section.contacts.length - 1"
|
||||
class="mx-2 h-px border-t border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-20 items-center justify-center text-base text-gray-600"
|
||||
>
|
||||
No contacts added
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -302,12 +265,12 @@ import LinkIcon from '@/components/Icons/LinkIcon.vue'
|
||||
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
|
||||
import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Toggler from '@/components/Toggler.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import ContactModal from '@/components/Modals/ContactModal.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import Section from '@/components/Section.vue'
|
||||
import SectionFields from '@/components/SectionFields.vue'
|
||||
import {
|
||||
dealStatuses,
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
:validateFile="validateFile"
|
||||
>
|
||||
<template #default="{ openFileSelector, error }">
|
||||
<div class="flex items-center justify-start gap-5 p-5">
|
||||
<div class="flex items-center justify-start gap-5 border-b p-5">
|
||||
<div class="group relative h-[88px] w-[88px]">
|
||||
<Avatar
|
||||
size="3xl"
|
||||
@ -135,41 +135,18 @@
|
||||
<div class="flex flex-1 flex-col justify-between overflow-hidden">
|
||||
<div class="flex flex-col overflow-y-auto">
|
||||
<div
|
||||
v-for="section in detailSections.data"
|
||||
v-for="(section, i) in detailSections.data"
|
||||
:key="section.label"
|
||||
class="flex flex-col"
|
||||
class="flex flex-col p-3"
|
||||
:class="{ 'border-b': i !== detailSections.data.length - 1 }"
|
||||
>
|
||||
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }">
|
||||
<div class="sticky top-0 z-10 border-t bg-white p-3">
|
||||
<div
|
||||
class="flex max-w-fit cursor-pointer items-center gap-2 px-2 text-base font-semibold leading-5"
|
||||
@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>
|
||||
</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 px-3">
|
||||
<SectionFields
|
||||
:fields="section.fields"
|
||||
v-model="lead.data"
|
||||
@update="updateField"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</Toggler>
|
||||
<Section :is-opened="section.opened" :label="section.label">
|
||||
<SectionFields
|
||||
:fields="section.fields"
|
||||
v-model="lead.data"
|
||||
@update="updateField"
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -197,10 +174,10 @@ 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 UserAvatar from '@/components/UserAvatar.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
|
||||
import Section from '@/components/Section.vue'
|
||||
import SectionFields from '@/components/SectionFields.vue'
|
||||
import {
|
||||
leadStatuses,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user