Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: contact page #17

Merged
merged 13 commits into from
Nov 5, 2023
2 changes: 1 addition & 1 deletion crm/api/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def get_contacts():

contacts = frappe.qb.get_query(
"Contact",
fields=['name', 'full_name', 'image', 'email_id', 'mobile_no', 'phone', 'salutation'],
fields=['name', 'first_name', 'last_name', 'full_name', 'image', 'email_id', 'mobile_no', 'phone', 'salutation', 'company_name', 'modified'],
order_by="first_name asc",
distinct=True,
).run(as_dict=1)
Expand Down
20 changes: 11 additions & 9 deletions frontend/src/components/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
class="flex h-full flex-col justify-between transition-all duration-300 ease-in-out"
:class="isSidebarCollapsed ? 'w-12' : 'w-56'"
>
<div class="flex flex-col">
<div class="flex flex-col overflow-hidden">
<UserDropdown class="p-2" :isCollapsed="isSidebarCollapsed" />
<SidebarLink
v-for="link in links"
:icon="link.icon"
:label="link.label"
:to="link.to"
:isCollapsed="isSidebarCollapsed"
class="mx-2 my-0.5"
/>
<div class="flex flex-col overflow-y-auto">
<SidebarLink
v-for="link in links"
:icon="link.icon"
:label="link.label"
:to="link.to"
:isCollapsed="isSidebarCollapsed"
class="mx-2 my-0.5"
/>
</div>
</div>
<SidebarLink
:label="isSidebarCollapsed ? 'Expand' : 'Collapse'"
Expand Down
136 changes: 136 additions & 0 deletions frontend/src/components/ContactModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<template>
<Dialog
v-model="show"
:options="{
title: editMode ? 'Edit contact' : 'New contact',
size: 'xl',
actions: [
{
label: editMode ? 'Update' : 'Create',
variant: 'solid',
disabled: !dirty,
onClick: ({ close }) => updateContact(close),
},
],
}"
>
<template #body-content>
<div class="flex flex-col gap-4">
<FormControl
type="text"
size="md"
variant="outline"
label="Salutation"
v-model="_contact.salutation"
/>
<div class="flex gap-4">
<FormControl
class="flex-1"
variant="outline"
size="md"
type="text"
label="First Name"
v-model="_contact.first_name"
/>
<FormControl
class="flex-1"
variant="outline"
size="md"
type="text"
label="Last Name"
v-model="_contact.last_name"
/>
</div>
<FormControl
type="text"
variant="outline"
size="md"
label="Organisation"
v-model="_contact.company_name"
/>
<div class="flex gap-4">
<FormControl
class="flex-1"
variant="outline"
size="md"
type="text"
label="Mobile no"
v-model="_contact.mobile_no"
/>
<FormControl
class="flex-1"
variant="outline"
size="md"
type="email"
label="Email"
v-model="_contact.email_id"
/>
</div>
</div>
</template>
</Dialog>
</template>

<script setup>
import { FormControl, Dialog, call } from 'frappe-ui'
import { ref, defineModel, nextTick, watch, computed } from 'vue'

const props = defineProps({
contact: {
type: Object,
default: {},
},
})

const show = defineModel()
const contacts = defineModel('reloadContacts')

const editMode = ref(false)
let _contact = ref({})

async function updateContact(close) {
if (JSON.stringify(props.contact) === JSON.stringify(_contact.value)) {
return
}

if (_contact.value.name) {
let d = await call('frappe.client.set_value', {
doctype: 'Contact',
name: _contact.value.name,
fieldname: _contact.value,
})
if (d.name) {
contacts.value.reload()
}
} else {
let d = await call('frappe.client.insert', {
doc: {
doctype: 'Contact',
..._contact.value,
},
})
if (d.name) {
contacts.value.reload()
}
}
close()
}

const dirty = computed(() => {
return JSON.stringify(props.contact) !== JSON.stringify(_contact.value)
})

watch(
() => show.value,
(value) => {
if (!value) return
editMode.value = false
nextTick(() => {
_contact.value = { ...props.contact }
if (_contact.value.first_name) {
editMode.value = true
}
})
}
)
</script>
59 changes: 55 additions & 4 deletions frontend/src/components/ListViews/ContactsListView.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,65 @@
<template>
<ListView
class="px-5"
v-if="rows"
:columns="columns"
:rows="rows"
:options="{
getRowRoute: (row) => ({ name: 'Contact', params: { contactId: row.name } }),
}"
row-key="name"
/>
>
<ListHeader class="mx-5" />
<ListRows>
<ListRow
class="mx-5"
v-for="row in rows"
:key="row.name"
v-slot="{ column, item }"
:row="row"
>
<ListRowItem :item="item">
<template #prefix>
<div v-if="column.key === 'full_name'">
<Avatar
v-if="item.label"
class="flex items-center"
:image="item.image"
:label="item.image_label"
size="sm"
/>
</div>
<div v-else-if="column.key === 'company_name'">
<Avatar
v-if="item.label"
class="flex items-center"
:image="item.logo"
:label="item.label"
size="sm"
/>
</div>
<div v-else-if="column.key === 'mobile_no'">
<PhoneIcon class="h-4 w-4" />
</div>
</template>
<div v-if="column.key === 'modified'" class="truncate text-base">
{{ item.timeAgo }}
</div>
</ListRowItem>
</ListRow>
</ListRows>
<ListSelectBanner />
</ListView>
</template>
<script setup>
import { ListView } from 'frappe-ui'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import {
Avatar,
ListView,
ListHeader,
ListRows,
ListRow,
ListSelectBanner,
ListRowItem,
} from 'frappe-ui'

const props = defineProps({
rows: {
Expand Down
Loading
Loading