feat: contact page
This commit is contained in:
parent
35b0bf9a8b
commit
e7bc5a900e
16
frontend/src/components/Icons/EditIcon.vue
Normal file
16
frontend/src/components/Icons/EditIcon.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.5 4.5C2.5 3.39543 3.39543 2.5 4.5 2.5H8.5C8.77614 2.5 9 2.27614 9 2C9 1.72386 8.77614 1.5 8.5 1.5H4.5C2.84315 1.5 1.5 2.84315 1.5 4.5V11.5C1.5 13.1569 2.84315 14.5 4.5 14.5H11.5C13.1569 14.5 14.5 13.1569 14.5 11.5V7.5C14.5 7.22386 14.2761 7 14 7C13.7239 7 13.5 7.22386 13.5 7.5V11.5C13.5 12.6046 12.6046 13.5 11.5 13.5H4.5C3.39543 13.5 2.5 12.6046 2.5 11.5V4.5ZM14.1255 2.58446C14.3207 2.3892 14.3207 2.07261 14.1255 1.87735C13.9302 1.68209 13.6136 1.68209 13.4184 1.87735L6.68616 8.60954C6.4909 8.8048 6.4909 9.12139 6.68616 9.31665C6.88143 9.51191 7.19801 9.51191 7.39327 9.31665L14.1255 2.58446Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
57
frontend/src/pages/Contact.vue
Normal file
57
frontend/src/pages/Contact.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="flex gap-6 p-5">
|
||||
<Avatar
|
||||
size="3xl"
|
||||
:image="contact.image"
|
||||
:label="contact.full_name"
|
||||
class="!h-24 !w-24"
|
||||
/>
|
||||
<div class="flex flex-col justify-center gap-2">
|
||||
<div class="text-3xl font-semibold text-gray-900">
|
||||
{{ contact.salutation + ' ' + contact.full_name }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-base text-gray-700">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<EmailIcon class="h-4 w-4" />
|
||||
<span class="">{{ contact.email_id }}</span>
|
||||
</div>
|
||||
<span class="text-3xl leading-[0] text-gray-600">·</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<PhoneIcon class="h-4 w-4" />
|
||||
<span class="">{{ contact.mobile_no }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 flex gap-2">
|
||||
<Button label="Edit" size="sm">
|
||||
<template #prefix>
|
||||
<EditIcon class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button label="Add lead" size="sm">
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button label="Add deal" size="sm">
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { FeatherIcon, Avatar } from 'frappe-ui'
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
contact: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@ -9,136 +9,79 @@
|
||||
</Button>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<div class="flex items-center justify-between px-5 pb-4 pt-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Dropdown :options="viewsDropdownOptions">
|
||||
<template #default="{ open }">
|
||||
<Button :label="currentView.label">
|
||||
<template #prefix
|
||||
><FeatherIcon :name="currentView.icon" class="h-4"
|
||||
/></template>
|
||||
<template #suffix
|
||||
><FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4 text-gray-600"
|
||||
/></template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<div class="flex h-full overflow-hidden">
|
||||
<div class="flex flex-col overflow-y-auto border-r">
|
||||
<router-link
|
||||
:to="{ name: 'Contact', params: { contactId: contact.name } }"
|
||||
v-for="(contact, i) in contacts.data"
|
||||
:key="i"
|
||||
:class="[
|
||||
current_contact?.name === contact.name
|
||||
? 'bg-gray-50 hover:bg-gray-100'
|
||||
: 'hover:bg-gray-50',
|
||||
]"
|
||||
>
|
||||
<div class="flex w-[352px] items-center gap-3 border-b px-5 py-4">
|
||||
<Avatar :image="contact.image" :label="contact.full_name" size="xl" />
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="text-base font-medium text-gray-900">
|
||||
{{ contact.full_name }}
|
||||
</span>
|
||||
<span class="text-sm text-gray-700">{{ contact.email_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button label="Sort">
|
||||
<template #prefix><SortIcon class="h-4" /></template>
|
||||
</Button>
|
||||
<Button label="Filter">
|
||||
<template #prefix><FilterIcon class="h-4" /></template>
|
||||
</Button>
|
||||
<Button icon="more-horizontal" />
|
||||
<div class="flex-1">
|
||||
<router-view v-if="current_contact" :contact="current_contact" />
|
||||
<div
|
||||
v-else
|
||||
class="grid h-full place-items-center text-xl font-medium text-gray-500"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center space-y-2">
|
||||
<ContactsIcon class="h-10 w-10" />
|
||||
<div>No contact selected</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ListView from '@/components/ListView.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import SortIcon from '@/components/Icons/SortIcon.vue'
|
||||
import FilterIcon from '@/components/Icons/FilterIcon.vue'
|
||||
import { FeatherIcon, Button, Dropdown, Breadcrumbs } from 'frappe-ui'
|
||||
import { ref, computed } from 'vue'
|
||||
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
|
||||
import { FeatherIcon, Breadcrumbs, Avatar } from 'frappe-ui'
|
||||
import { contactsStore } from '@/stores/contacts.js'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { contacts } = contactsStore()
|
||||
const route = useRoute()
|
||||
|
||||
const list = {
|
||||
title: 'Contacts',
|
||||
plural_label: 'Contacts',
|
||||
singular_label: 'Contact',
|
||||
}
|
||||
const current_contact = computed(() => {
|
||||
return contacts.data.find(
|
||||
(contact) => contact.name === route.params.contactId
|
||||
)
|
||||
})
|
||||
|
||||
const breadcrumbs = [{ label: list.title, route: { name: 'Contacts' } }]
|
||||
|
||||
const columns = [
|
||||
{
|
||||
label: 'Full name',
|
||||
key: 'full_name',
|
||||
type: 'avatar',
|
||||
size: 'w-44',
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
type: 'email',
|
||||
size: 'w-44',
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
key: 'mobile_no',
|
||||
type: 'phone',
|
||||
size: 'w-44',
|
||||
},
|
||||
]
|
||||
|
||||
const rows = computed(() => {
|
||||
return contacts.data?.map((contact) => {
|
||||
return {
|
||||
name: contact.name,
|
||||
full_name: {
|
||||
label: contact.full_name,
|
||||
image_label: contact.full_name,
|
||||
image: contact.image,
|
||||
},
|
||||
email: contact.email_id,
|
||||
mobile_no: contact.mobile_no,
|
||||
}
|
||||
const breadcrumbs = computed(() => {
|
||||
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }]
|
||||
if (!current_contact.value) return items
|
||||
items.push({
|
||||
label: current_contact.value.full_name,
|
||||
route: {
|
||||
name: 'Contact',
|
||||
params: { contactId: current_contact.value.name },
|
||||
},
|
||||
})
|
||||
return items
|
||||
})
|
||||
|
||||
const currentView = ref({
|
||||
label: 'List',
|
||||
icon: 'list',
|
||||
onMounted(() => {
|
||||
const el = document.querySelector('.router-link-active')
|
||||
if (el)
|
||||
setTimeout(() => {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
})
|
||||
})
|
||||
|
||||
const viewsDropdownOptions = [
|
||||
{
|
||||
label: 'List',
|
||||
icon: 'list',
|
||||
onClick() {
|
||||
currentView.value = {
|
||||
label: 'List',
|
||||
icon: 'list',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Table',
|
||||
icon: 'grid',
|
||||
onClick() {
|
||||
currentView.value = {
|
||||
label: 'Table',
|
||||
icon: 'grid',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Calender',
|
||||
icon: 'calendar',
|
||||
onClick() {
|
||||
currentView.value = {
|
||||
label: 'Calender',
|
||||
icon: 'calendar',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Board',
|
||||
icon: 'columns',
|
||||
onClick() {
|
||||
currentView.value = {
|
||||
label: 'Board',
|
||||
icon: 'columns',
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
@ -38,6 +38,14 @@ const routes = [
|
||||
path: '/contacts',
|
||||
name: 'Contacts',
|
||||
component: () => import('@/pages/Contacts.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/contacts/:contactId?',
|
||||
name: 'Contact',
|
||||
component: () => import('@/pages/Contact.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/call-logs',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user