fix: created Section Component using Toggler component and used in lead/deal

This commit is contained in:
Shariq Ansari 2023-11-22 14:34:58 +05:30
parent 36455b8faf
commit b157f255f8
5 changed files with 223 additions and 249 deletions

View 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>

View File

@ -1,69 +1,71 @@
<template> <template>
<div <div class="flex flex-col gap-1.5">
v-for="field in fields" <div
:key="field.label" v-for="field in fields"
class="flex items-center gap-2 px-3 text-base leading-5 first:mt-3" :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 class="w-[106px] shrink-0 text-gray-600">
</div> {{ field.label }}
<div class="flex-1 overflow-hidden"> </div>
<Link <div class="flex-1 overflow-hidden">
v-if="field.type === 'link'" <Link
class="form-control" v-if="field.type === 'link'"
:value="data[field.name]" class="form-control"
:doctype="field.doctype" :value="data[field.name]"
:placeholder="field.placeholder" :doctype="field.doctype"
@change="(data) => emit('update', field.name, data)" :placeholder="field.placeholder"
:onCreate="field.create" @change="(data) => emit('update', field.name, data)"
/> :onCreate="field.create"
<FormControl />
v-else-if="field.type === 'select'" <FormControl
class="form-control cursor-pointer [&_select]:cursor-pointer" v-else-if="field.type === 'select'"
type="select" class="form-control cursor-pointer [&_select]:cursor-pointer"
:value="data[field.name]" type="select"
:options="field.options" :value="data[field.name]"
:debounce="500" :options="field.options"
@change.stop="emit('update', field.name, $event.target.value)" :debounce="500"
/> @change.stop="emit('update', field.name, $event.target.value)"
<FormControl />
v-else-if="field.type === 'email'" <FormControl
class="form-control" v-else-if="field.type === 'email'"
type="email" class="form-control"
:value="data[field.name]" type="email"
:debounce="500" :value="data[field.name]"
@change.stop="emit('update', field.name, $event.target.value)" :debounce="500"
/> @change.stop="emit('update', field.name, $event.target.value)"
<FormControl />
v-else-if="field.type === 'date'" <FormControl
class="form-control" v-else-if="field.type === 'date'"
type="date" class="form-control"
:value="data[field.name]" type="date"
:debounce="500" :value="data[field.name]"
@change.stop="emit('update', field.name, $event.target.value)" :debounce="500"
/> @change.stop="emit('update', field.name, $event.target.value)"
<Tooltip />
v-else-if="field.type === 'read_only'" <Tooltip
class="flex h-7 cursor-pointer items-center px-2 py-1" v-else-if="field.type === 'read_only'"
:text="field.tooltip" class="flex h-7 cursor-pointer items-center px-2 py-1"
> :text="field.tooltip"
{{ field.value }} >
</Tooltip> {{ field.value }}
<FormControl </Tooltip>
v-else <FormControl
class="form-control" v-else
type="text" class="form-control"
:value="data[field.name]" type="text"
:placeholder="field.placeholder" :value="data[field.name]"
:debounce="500" :placeholder="field.placeholder"
@change.stop="emit('update', field.name, $event.target.value)" :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> </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> </div>
</template> </template>

View File

@ -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>

View File

@ -101,19 +101,8 @@
class="flex flex-col p-3" class="flex flex-col p-3"
:class="{ 'border-b': i !== detailSections.data.length - 1 }" :class="{ 'border-b': i !== detailSections.data.length - 1 }"
> >
<Toggler :is-opened="section.opened" v-slot="{ opened, toggle }"> <Section :is-opened="section.opened" :label="section.label">
<div class="flex items-center justify-between"> <template #actions>
<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>
<div v-if="section.contacts" class="pr-2"> <div v-if="section.contacts" class="pr-2">
<Link <Link
value="" value=""
@ -143,129 +132,103 @@
</template> </template>
</Link> </Link>
</div> </div>
</div> </template>
<transition <SectionFields
enter-active-class="duration-300 ease-in" v-if="section.fields"
leave-active-class="duration-300 ease-[cubic-bezier(0, 1, 0.5, 1)]" :fields="section.fields"
enter-to-class="max-h-[200px] overflow-hidden" v-model="deal.data"
leave-from-class="max-h-[200px] overflow-hidden" @update="updateField"
enter-from-class="max-h-0 overflow-hidden" />
leave-to-class="max-h-0 overflow-hidden" <div v-else>
> <div
<div v-if="opened" class="flex flex-col gap-1.5"> v-if="section.contacts.length"
<SectionFields v-for="(contact, i) in section.contacts"
v-if="section.fields" :key="contact.name"
:fields="section.fields" >
v-model="deal.data" <div
@update="updateField" class="px-2 pb-2.5"
/> :class="[i == 0 ? 'pt-5' : 'pt-2.5']"
<div v-else> >
<div <Section :is-opened="contact.opened">
v-if="section.contacts.length" <template #header="{ opened, toggle }">
v-for="(contact, i) in section.contacts" <div
:key="contact.name" class="flex cursor-pointer items-center justify-between gap-2 pr-1 text-base leading-5 text-gray-700"
>
<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 }"
> >
<div <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 <Avatar
class="flex h-7 items-center gap-2" :label="getContactByName(contact.name).full_name"
@click="cToggle()" :image="getContactByName(contact.name).image"
> size="md"
<Avatar />
:label=" {{ getContactByName(contact.name).full_name }}
getContactByName(contact.name).full_name <Badge
" v-if="contact.is_primary"
:image="getContactByName(contact.name).image" class="ml-2"
size="md" variant="outline"
/> label="Primary"
{{ getContactByName(contact.name).full_name }} theme="green"
<Badge />
v-if="contact.is_primary" </div>
class="ml-2" <div class="flex items-center">
variant="outline" <Dropdown :options="contactOptions(contact)">
label="Primary" <Button variant="ghost">
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()">
<FeatherIcon <FeatherIcon
name="chevron-right" name="more-horizontal"
class="h-4 w-4 text-gray-900 transition-all duration-300 ease-in-out" class="h-4 text-gray-600"
:class="{ 'rotate-90': cOpened }"
/> />
</Button> </Button>
</div> </Dropdown>
</div> <Button
<transition variant="ghost"
enter-active-class="duration-300 ease-in" @click="
leave-active-class="duration-300 ease-[cubic-bezier(0, 1, 0.5, 1)]" router.push({
enter-to-class="max-h-[200px] overflow-hidden" name: 'Contact',
leave-from-class="max-h-[200px] overflow-hidden" params: { contactId: contact.name },
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"
> >
<div <ExternalLinkIcon class="h-4 w-4" />
class="flex items-center gap-3 pb-1.5 pl-1 pt-4" </Button>
> <Button variant="ghost" @click="toggle()">
<EmailIcon class="h-4 w-4" /> <FeatherIcon
{{ getContactByName(contact.name).email_id }} name="chevron-right"
</div> class="h-4 w-4 text-gray-900 transition-all duration-300 ease-in-out"
<div class="flex items-center gap-3 p-1 py-1.5"> :class="{ 'rotate-90': opened }"
<PhoneIcon class="h-4 w-4" /> />
{{ getContactByName(contact.name).mobile_no }} </Button>
</div> </div>
</div> </div>
</transition> </template>
</Toggler>
</div>
<div <div
v-if="i != section.contacts.length - 1" class="flex flex-col gap-1.5 text-base text-gray-800"
class="mx-2 h-px border-t border-gray-200" >
/> <div class="flex items-center gap-3 pb-1.5 pl-1 pt-4">
</div> <EmailIcon class="h-4 w-4" />
<div {{ getContactByName(contact.name).email_id }}
v-else </div>
class="flex h-20 items-center justify-center text-base text-gray-600" <div class="flex items-center gap-3 p-1 py-1.5">
> <PhoneIcon class="h-4 w-4" />
No contacts added {{ getContactByName(contact.name).mobile_no }}
</div> </div>
</div>
</Section>
</div> </div>
<div
v-if="i != section.contacts.length - 1"
class="mx-2 h-px border-t border-gray-200"
/>
</div> </div>
</transition> <div
</Toggler> v-else
class="flex h-20 items-center justify-center text-base text-gray-600"
>
No contacts added
</div>
</div>
</Section>
</div> </div>
</div> </div>
</div> </div>
@ -302,12 +265,12 @@ import LinkIcon from '@/components/Icons/LinkIcon.vue'
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue' import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
import SuccessIcon from '@/components/Icons/SuccessIcon.vue' import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue' import Activities from '@/components/Activities.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue' import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import ContactModal from '@/components/Modals/ContactModal.vue' import ContactModal from '@/components/Modals/ContactModal.vue'
import Link from '@/components/Controls/Link.vue' import Link from '@/components/Controls/Link.vue'
import Section from '@/components/Section.vue'
import SectionFields from '@/components/SectionFields.vue' import SectionFields from '@/components/SectionFields.vue'
import { import {
dealStatuses, dealStatuses,

View File

@ -57,7 +57,7 @@
:validateFile="validateFile" :validateFile="validateFile"
> >
<template #default="{ openFileSelector, error }"> <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]"> <div class="group relative h-[88px] w-[88px]">
<Avatar <Avatar
size="3xl" size="3xl"
@ -135,41 +135,18 @@
<div class="flex flex-1 flex-col justify-between overflow-hidden"> <div class="flex flex-1 flex-col justify-between overflow-hidden">
<div class="flex flex-col overflow-y-auto"> <div class="flex flex-col overflow-y-auto">
<div <div
v-for="section in detailSections.data" v-for="(section, i) in detailSections.data"
:key="section.label" :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 }"> <Section :is-opened="section.opened" :label="section.label">
<div class="sticky top-0 z-10 border-t bg-white p-3"> <SectionFields
<div :fields="section.fields"
class="flex max-w-fit cursor-pointer items-center gap-2 px-2 text-base font-semibold leading-5" v-model="lead.data"
@click="toggle()" @update="updateField"
> />
<FeatherIcon </Section>
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>
</div> </div>
</div> </div>
</div> </div>
@ -197,10 +174,10 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue' import CameraIcon from '@/components/Icons/CameraIcon.vue'
import LinkIcon from '@/components/Icons/LinkIcon.vue' import LinkIcon from '@/components/Icons/LinkIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import Toggler from '@/components/Toggler.vue'
import Activities from '@/components/Activities.vue' import Activities from '@/components/Activities.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue' import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import Section from '@/components/Section.vue'
import SectionFields from '@/components/SectionFields.vue' import SectionFields from '@/components/SectionFields.vue'
import { import {
leadStatuses, leadStatuses,