Merge pull request #18 from shariquerik/replace-with-frappeui-listview
This commit is contained in:
commit
412565fd1f
@ -1 +1 @@
|
|||||||
Subproject commit 167ff453cb525674f976d53b126900b80b9a80a2
|
Subproject commit 38eb500cb47d6cfc7124817e9c2acdcd560045f1
|
||||||
@ -16,7 +16,7 @@
|
|||||||
"@vueuse/integrations": "^10.3.0",
|
"@vueuse/integrations": "^10.3.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"frappe-ui": "^0.1.6",
|
"frappe-ui": "^0.1.12",
|
||||||
"pinia": "^2.0.33",
|
"pinia": "^2.0.33",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
|
|||||||
@ -1,61 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Tooltip
|
|
||||||
:text="tooltipText"
|
|
||||||
class="flex items-center space-x-2"
|
|
||||||
:class="align == 'text-right' ? 'justify-end' : ''"
|
|
||||||
>
|
|
||||||
<slot name="prefix"></slot>
|
|
||||||
<slot>
|
|
||||||
<div class="text-base truncate">
|
|
||||||
{{ label }}
|
|
||||||
</div>
|
|
||||||
</slot>
|
|
||||||
</Tooltip>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
dateFormat,
|
|
||||||
timeAgo,
|
|
||||||
dateTooltipFormat,
|
|
||||||
htmlToText,
|
|
||||||
formatNumberIntoCurrency,
|
|
||||||
} from '@/utils'
|
|
||||||
import { Tooltip } from 'frappe-ui'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'text',
|
|
||||||
},
|
|
||||||
align: {
|
|
||||||
type: String,
|
|
||||||
default: 'left',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: [String, Number, Object],
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tooltipText = computed(() => {
|
|
||||||
if (props.type === 'html') return ''
|
|
||||||
if (props.type === 'pretty_date') {
|
|
||||||
return dateFormat(props.value, dateTooltipFormat)
|
|
||||||
}
|
|
||||||
return props.value?.toString()
|
|
||||||
})
|
|
||||||
|
|
||||||
const label = computed(() => {
|
|
||||||
if (props.type === 'pretty_date') {
|
|
||||||
return timeAgo(props.value)
|
|
||||||
}
|
|
||||||
if (props.type === 'html') {
|
|
||||||
return htmlToText(props.value?.toString())
|
|
||||||
}
|
|
||||||
if (props.type === 'currency') {
|
|
||||||
return formatNumberIntoCurrency(props.value)
|
|
||||||
}
|
|
||||||
return props.value?.toString()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="content" class="flex flex-col w-full overflow-x-auto flex-1">
|
|
||||||
<div class="flex flex-col overflow-y-hidden w-max min-w-full">
|
|
||||||
<div
|
|
||||||
id="list-header"
|
|
||||||
class="flex space-x-4 items-center mx-5 mb-2 p-2 rounded bg-gray-100"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
class="duration-300 cursor-pointer"
|
|
||||||
:modelValue="allRowsSelected"
|
|
||||||
@click.stop="toggleAllRows"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-for="column in columns"
|
|
||||||
:key="column"
|
|
||||||
class="text-base text-gray-600"
|
|
||||||
:class="[column.size, column.align]"
|
|
||||||
>
|
|
||||||
{{ column.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="list-rows" class="h-full overflow-y-auto">
|
|
||||||
<router-link
|
|
||||||
v-for="(row, i) in rows"
|
|
||||||
:key="row[rowKey]"
|
|
||||||
:to="$router.currentRoute.value.path + '/' + row[rowKey]"
|
|
||||||
class="flex flex-col mx-5 cursor-pointer transition-all duration-300 ease-in-out"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex space-x-4 items-center px-2 py-2.5 rounded"
|
|
||||||
:class="
|
|
||||||
selections.has(row[rowKey])
|
|
||||||
? 'bg-gray-100 hover:bg-gray-200'
|
|
||||||
: 'hover:bg-gray-50'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
:modelValue="selections.has(row[rowKey])"
|
|
||||||
@click.stop="toggleRow(row[rowKey])"
|
|
||||||
class="duration-300 cursor-pointer"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-for="column in columns"
|
|
||||||
:key="column.key"
|
|
||||||
:class="[column.size, column.align]"
|
|
||||||
>
|
|
||||||
<ListRowItem
|
|
||||||
:value="getValue(row[column.key]).label"
|
|
||||||
:type="column.type"
|
|
||||||
:align="column.align"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<div v-if="column.type === 'indicator'">
|
|
||||||
<IndicatorIcon :class="getValue(row[column.key]).color" />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="column.type === 'avatar'">
|
|
||||||
<Avatar
|
|
||||||
v-if="getValue(row[column.key]).label"
|
|
||||||
class="flex items-center"
|
|
||||||
:image="getValue(row[column.key]).image"
|
|
||||||
:label="getValue(row[column.key]).image_label"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="column.type === 'logo'">
|
|
||||||
<Avatar
|
|
||||||
v-if="getValue(row[column.key]).label"
|
|
||||||
class="flex items-center"
|
|
||||||
:image="getValue(row[column.key]).logo"
|
|
||||||
:label="getValue(row[column.key]).image_label"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="column.type === 'icon'">
|
|
||||||
<FeatherIcon
|
|
||||||
:name="getValue(row[column.key]).icon"
|
|
||||||
class="h-3 w-3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="column.type === 'phone'">
|
|
||||||
<PhoneIcon class="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-if="column.type === 'badge'">
|
|
||||||
<Badge
|
|
||||||
:variant="'subtle'"
|
|
||||||
:theme="row[column.key].color"
|
|
||||||
size="md"
|
|
||||||
:label="row[column.key].label"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ListRowItem>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="i < rows.length - 1"
|
|
||||||
class="mx-2 h-px border-t border-gray-200"
|
|
||||||
/>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<transition
|
|
||||||
enter-active-class="duration-300 ease-out"
|
|
||||||
enter-from-class="transform opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-active-class="duration-300 ease-in"
|
|
||||||
leave-from-class="opacity-100"
|
|
||||||
leave-to-class="transform opacity-0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="selections.size"
|
|
||||||
class="fixed inset-x-0 bottom-6 mx-auto w-max text-base"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="w-[596px] flex items-center space-x-3 rounded-lg bg-white px-4 py-2 shadow-2xl"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex flex-1 items-center space-x-3 border-r border-gray-300 text-gray-900"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
:modelValue="true"
|
|
||||||
:disabled="true"
|
|
||||||
class="text-gray-900"
|
|
||||||
/>
|
|
||||||
<div>{{ selectedText }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-1">
|
|
||||||
<Button
|
|
||||||
class="text-gray-700"
|
|
||||||
:disabled="allRowsSelected"
|
|
||||||
:class="allRowsSelected ? 'cursor-not-allowed' : ''"
|
|
||||||
variant="ghost"
|
|
||||||
@click="toggleAllRows(true)"
|
|
||||||
>
|
|
||||||
Select all
|
|
||||||
</Button>
|
|
||||||
<Button icon="x" variant="ghost" @click="toggleAllRows(false)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
|
||||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
|
||||||
import ListRowItem from '@/components/ListRowItem.vue'
|
|
||||||
import { Checkbox, Avatar, Badge, FeatherIcon } from 'frappe-ui'
|
|
||||||
import { reactive, computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
list: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
columns: {
|
|
||||||
type: Array,
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
rows: {
|
|
||||||
type: Array,
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
rowKey: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function getValue(value) {
|
|
||||||
if (value && typeof value === 'object') {
|
|
||||||
value.label = value.full_name || value.label
|
|
||||||
value.image = value.image || value.user_image || value.logo
|
|
||||||
value.image_label = value.image_label || value.label
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return { label: value }
|
|
||||||
}
|
|
||||||
|
|
||||||
let selections = reactive(new Set())
|
|
||||||
let selectedText = computed(() => {
|
|
||||||
let title =
|
|
||||||
selections.size === 1 ? props.list.singular_label : props.list.plural_label
|
|
||||||
return `${selections.size} ${title} selected`
|
|
||||||
})
|
|
||||||
|
|
||||||
const allRowsSelected = computed(() => {
|
|
||||||
if (!props.rows.length) return false
|
|
||||||
return selections.size === props.rows.length
|
|
||||||
})
|
|
||||||
|
|
||||||
function toggleRow(row) {
|
|
||||||
if (!selections.delete(row)) {
|
|
||||||
selections.add(row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAllRows(select) {
|
|
||||||
if (!select || allRowsSelected.value) {
|
|
||||||
selections.clear()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
props.rows.forEach((row) => selections.add(row[props.rowKey]))
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -17,30 +17,90 @@
|
|||||||
<Button icon="more-horizontal" />
|
<Button icon="more-horizontal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
<ListView
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
:options="{
|
||||||
|
getRowRoute: (row) => ({
|
||||||
|
name: 'Call Log',
|
||||||
|
params: { callLogId: 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="['caller', 'receiver'].includes(column.key)">
|
||||||
|
<Avatar
|
||||||
|
v-if="item.label"
|
||||||
|
class="flex items-center"
|
||||||
|
:image="item.image"
|
||||||
|
:label="item.label"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="['type', 'duration'].includes(column.key)">
|
||||||
|
<FeatherIcon :name="item.icon" class="h-3 w-3" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="column.key === 'creation'" class="truncate text-base">
|
||||||
|
{{ item.timeAgo }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="column.key === 'status'" class="truncate text-base">
|
||||||
|
<Badge
|
||||||
|
:variant="'subtle'"
|
||||||
|
:theme="item.color"
|
||||||
|
size="md"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ListRowItem>
|
||||||
|
</ListRow>
|
||||||
|
</ListRows>
|
||||||
|
<ListSelectBanner />
|
||||||
|
</ListView>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ListView from '@/components/ListView.vue'
|
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.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 { secondsToDuration } from '@/utils'
|
import {
|
||||||
|
secondsToDuration,
|
||||||
|
dateFormat,
|
||||||
|
dateTooltipFormat,
|
||||||
|
timeAgo,
|
||||||
|
} from '@/utils'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
import { contactsStore } from '@/stores/contacts'
|
import { contactsStore } from '@/stores/contacts'
|
||||||
import { Button, createListResource, Breadcrumbs } from 'frappe-ui'
|
import {
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
createListResource,
|
||||||
|
Breadcrumbs,
|
||||||
|
ListView,
|
||||||
|
ListHeader,
|
||||||
|
ListRows,
|
||||||
|
ListRow,
|
||||||
|
ListRowItem,
|
||||||
|
ListSelectBanner,
|
||||||
|
FeatherIcon,
|
||||||
|
} from 'frappe-ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
const { getContact } = contactsStore()
|
const { getContact } = contactsStore()
|
||||||
|
|
||||||
const list = {
|
const breadcrumbs = [{ label: 'Call Logs', route: { name: 'Call Logs' } }]
|
||||||
title: 'Call Logs',
|
|
||||||
plural_label: 'Call Logs',
|
|
||||||
singular_label: 'Call Log',
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = [{ label: list.title, route: { name: 'Call Logs' } }]
|
|
||||||
|
|
||||||
const callLogs = createListResource({
|
const callLogs = createListResource({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
@ -69,50 +129,42 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: 'From',
|
label: 'From',
|
||||||
key: 'caller',
|
key: 'caller',
|
||||||
type: 'avatar',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'To',
|
label: 'To',
|
||||||
key: 'receiver',
|
key: 'receiver',
|
||||||
type: 'avatar',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Type',
|
label: 'Type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
type: 'icon',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
type: 'badge',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Duration',
|
label: 'Duration',
|
||||||
key: 'duration',
|
key: 'duration',
|
||||||
type: 'icon',
|
width: '6rem',
|
||||||
size: 'w-20',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'From (number)',
|
label: 'From (number)',
|
||||||
key: 'from',
|
key: 'from',
|
||||||
type: 'data',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'To (number)',
|
label: 'To (number)',
|
||||||
key: 'to',
|
key: 'to',
|
||||||
type: 'data',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Created on',
|
label: 'Created on',
|
||||||
key: 'creation',
|
key: 'creation',
|
||||||
type: 'pretty_date',
|
width: '8rem',
|
||||||
size: 'w-28',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -159,7 +211,10 @@ const rows = computed(() => {
|
|||||||
label: callLog.status,
|
label: callLog.status,
|
||||||
color: callLog.status === 'Completed' ? 'green' : 'gray',
|
color: callLog.status === 'Completed' ? 'green' : 'gray',
|
||||||
},
|
},
|
||||||
creation: callLog.creation,
|
creation: {
|
||||||
|
label: dateFormat(callLog.creation, dateTooltipFormat),
|
||||||
|
timeAgo: timeAgo(callLog.creation),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -36,46 +36,36 @@
|
|||||||
<Button icon="more-horizontal" />
|
<Button icon="more-horizontal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
<ListView class="px-5" v-if="rows" :columns="columns" :rows="rows" row-key="name" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ListView from '@/components/ListView.vue'
|
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.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 { FeatherIcon, Button, Dropdown, Breadcrumbs } from 'frappe-ui'
|
import { FeatherIcon, Button, Dropdown, Breadcrumbs, ListView } from 'frappe-ui'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { contactsStore } from '@/stores/contacts.js'
|
import { contactsStore } from '@/stores/contacts.js'
|
||||||
|
|
||||||
const { contacts } = contactsStore()
|
const { contacts } = contactsStore()
|
||||||
|
|
||||||
const list = {
|
const breadcrumbs = [{ label: 'Contacts', route: { name: 'Contacts' } }]
|
||||||
title: 'Contacts',
|
|
||||||
plural_label: 'Contacts',
|
|
||||||
singular_label: 'Contact',
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = [{ label: list.title, route: { name: 'Contacts' } }]
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
label: 'Full name',
|
label: 'Full name',
|
||||||
key: 'full_name',
|
key: 'full_name',
|
||||||
type: 'avatar',
|
width: '12rem',
|
||||||
size: 'w-44',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
type: 'email',
|
width: '12rem',
|
||||||
size: 'w-44',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Phone',
|
label: 'Phone',
|
||||||
key: 'mobile_no',
|
key: 'mobile_no',
|
||||||
type: 'phone',
|
width: '12rem',
|
||||||
size: 'w-44',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -33,11 +33,62 @@
|
|||||||
<Button icon="more-horizontal" />
|
<Button icon="more-horizontal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
<ListView
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
:options="{
|
||||||
|
getRowRoute: (row) => ({ name: 'Deal', params: { dealId: 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 === 'deal_status'">
|
||||||
|
<IndicatorIcon :class="item.color" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="column.key === 'organization_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 === 'lead_owner'">
|
||||||
|
<Avatar
|
||||||
|
v-if="item.full_name"
|
||||||
|
class="flex items-center"
|
||||||
|
:image="item.user_image"
|
||||||
|
:label="item.full_name"
|
||||||
|
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>
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model="showNewDialog"
|
v-model="showNewDialog"
|
||||||
:options="{
|
:options="{
|
||||||
size: '3xl',
|
width: '3xl',
|
||||||
title: 'New Deal',
|
title: 'New Deal',
|
||||||
actions: [{ label: 'Save', variant: 'solid' }],
|
actions: [{ label: 'Save', variant: 'solid' }],
|
||||||
}"
|
}"
|
||||||
@ -54,17 +105,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ListView from '@/components/ListView.vue'
|
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import NewDeal from '@/components/NewDeal.vue'
|
import NewDeal from '@/components/NewDeal.vue'
|
||||||
import SortBy from '@/components/SortBy.vue'
|
import SortBy from '@/components/SortBy.vue'
|
||||||
import Filter from '@/components/Filter.vue'
|
import Filter from '@/components/Filter.vue'
|
||||||
|
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||||
|
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
import { useOrderBy } from '@/composables/orderby'
|
import { useOrderBy } from '@/composables/orderby'
|
||||||
import { useFilter } from '@/composables/filter'
|
import { useFilter } from '@/composables/filter'
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { dealStatuses } from '@/utils'
|
|
||||||
import {
|
import {
|
||||||
|
dealStatuses,
|
||||||
|
dateFormat,
|
||||||
|
dateTooltipFormat,
|
||||||
|
timeAgo,
|
||||||
|
formatNumberIntoCurrency,
|
||||||
|
} from '@/utils'
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
FeatherIcon,
|
FeatherIcon,
|
||||||
Dialog,
|
Dialog,
|
||||||
Button,
|
Button,
|
||||||
@ -72,17 +131,17 @@ import {
|
|||||||
createListResource,
|
createListResource,
|
||||||
createResource,
|
createResource,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
ListView,
|
||||||
|
ListHeader,
|
||||||
|
ListRows,
|
||||||
|
ListRow,
|
||||||
|
ListRowItem,
|
||||||
|
ListSelectBanner,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ref, computed, reactive, watch } from 'vue'
|
import { ref, computed, reactive, watch } from 'vue'
|
||||||
|
|
||||||
const list = {
|
const breadcrumbs = [{ label: 'Deals', route: { name: 'Deals' } }]
|
||||||
title: 'Deals',
|
|
||||||
plural_label: 'Deals',
|
|
||||||
singular_label: 'Deal',
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = [{ label: list.title, route: { name: 'Deals' } }]
|
|
||||||
|
|
||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
const { get: getOrderBy } = useOrderBy()
|
const { get: getOrderBy } = useOrderBy()
|
||||||
@ -144,44 +203,37 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: 'Organization',
|
label: 'Organization',
|
||||||
key: 'organization_name',
|
key: 'organization_name',
|
||||||
type: 'logo',
|
width: '11rem',
|
||||||
size: 'w-40',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
key: 'annual_revenue',
|
key: 'annual_revenue',
|
||||||
type: 'currency',
|
width: '9rem',
|
||||||
size: 'w-32',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
key: 'deal_status',
|
key: 'deal_status',
|
||||||
type: 'indicator',
|
width: '10rem',
|
||||||
size: 'w-36',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
type: 'email',
|
width: '12rem',
|
||||||
size: 'w-44',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Mobile no',
|
label: 'Mobile no',
|
||||||
key: 'mobile_no',
|
key: 'mobile_no',
|
||||||
type: 'phone',
|
width: '11rem',
|
||||||
size: 'w-40',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Lead owner',
|
label: 'Lead owner',
|
||||||
key: 'lead_owner',
|
key: 'lead_owner',
|
||||||
type: 'avatar',
|
width: '10rem',
|
||||||
size: 'w-36',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last modified',
|
label: 'Last modified',
|
||||||
key: 'modified',
|
key: 'modified',
|
||||||
type: 'pretty_date',
|
width: '8rem',
|
||||||
size: 'w-28',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -193,15 +245,21 @@ const rows = computed(() => {
|
|||||||
label: lead.organization_name,
|
label: lead.organization_name,
|
||||||
logo: lead.organization_logo,
|
logo: lead.organization_logo,
|
||||||
},
|
},
|
||||||
annual_revenue: lead.annual_revenue,
|
annual_revenue: formatNumberIntoCurrency(lead.annual_revenue),
|
||||||
deal_status: {
|
deal_status: {
|
||||||
label: lead.deal_status,
|
label: lead.deal_status,
|
||||||
color: dealStatuses[lead.deal_status]?.color,
|
color: dealStatuses[lead.deal_status]?.color,
|
||||||
},
|
},
|
||||||
email: lead.email,
|
email: lead.email,
|
||||||
mobile_no: lead.mobile_no,
|
mobile_no: lead.mobile_no,
|
||||||
lead_owner: lead.lead_owner && getUser(lead.lead_owner),
|
lead_owner: {
|
||||||
modified: lead.modified,
|
label: lead.lead_owner && getUser(lead.lead_owner).full_name,
|
||||||
|
...(lead.lead_owner && getUser(lead.lead_owner)),
|
||||||
|
},
|
||||||
|
modified: {
|
||||||
|
label: dateFormat(lead.modified, dateTooltipFormat),
|
||||||
|
timeAgo: timeAgo(lead.modified),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -32,7 +32,67 @@
|
|||||||
<Button icon="more-horizontal" />
|
<Button icon="more-horizontal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ListView :list="list" :columns="columns" :rows="rows" row-key="name" />
|
<ListView
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
:options="{
|
||||||
|
getRowRoute: (row) => ({ name: 'Lead', params: { leadId: 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 === 'status'">
|
||||||
|
<IndicatorIcon :class="item.color" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="column.key === 'lead_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 === 'organization_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 === 'lead_owner'">
|
||||||
|
<Avatar
|
||||||
|
v-if="item.full_name"
|
||||||
|
class="flex items-center"
|
||||||
|
:image="item.user_image"
|
||||||
|
:label="item.full_name"
|
||||||
|
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>
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model="showNewDialog"
|
v-model="showNewDialog"
|
||||||
:options="{
|
:options="{
|
||||||
@ -53,17 +113,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ListView from '@/components/ListView.vue'
|
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import NewLead from '@/components/NewLead.vue'
|
import NewLead from '@/components/NewLead.vue'
|
||||||
import SortBy from '@/components/SortBy.vue'
|
import SortBy from '@/components/SortBy.vue'
|
||||||
import Filter from '@/components/Filter.vue'
|
import Filter from '@/components/Filter.vue'
|
||||||
|
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||||
|
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
import { useOrderBy } from '@/composables/orderby'
|
import { useOrderBy } from '@/composables/orderby'
|
||||||
import { useFilter } from '@/composables/filter'
|
import { useFilter } from '@/composables/filter'
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { leadStatuses } from '@/utils'
|
import { leadStatuses, dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
FeatherIcon,
|
FeatherIcon,
|
||||||
Dialog,
|
Dialog,
|
||||||
Button,
|
Button,
|
||||||
@ -71,17 +133,17 @@ import {
|
|||||||
createListResource,
|
createListResource,
|
||||||
createResource,
|
createResource,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
ListView,
|
||||||
|
ListHeader,
|
||||||
|
ListRows,
|
||||||
|
ListRow,
|
||||||
|
ListSelectBanner,
|
||||||
|
ListRowItem,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ref, computed, reactive, watch } from 'vue'
|
import { ref, computed, reactive, watch } from 'vue'
|
||||||
|
|
||||||
const list = {
|
const breadcrumbs = [{ label: 'Leads', route: { name: 'Leads' } }]
|
||||||
title: 'Leads',
|
|
||||||
plural_label: 'Leads',
|
|
||||||
singular_label: 'Lead',
|
|
||||||
}
|
|
||||||
|
|
||||||
const breadcrumbs = [{ label: list.title, route: { name: 'Leads' } }]
|
|
||||||
|
|
||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
const { get: getOrderBy } = useOrderBy()
|
const { get: getOrderBy } = useOrderBy()
|
||||||
@ -149,44 +211,37 @@ const columns = [
|
|||||||
{
|
{
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
key: 'lead_name',
|
key: 'lead_name',
|
||||||
type: 'avatar',
|
width: '12rem',
|
||||||
size: 'w-44',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Organization',
|
label: 'Organization',
|
||||||
key: 'organization_name',
|
key: 'organization_name',
|
||||||
type: 'logo',
|
width: '10rem',
|
||||||
size: 'w-36',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
type: 'indicator',
|
width: '8rem',
|
||||||
size: 'w-28',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
type: 'email',
|
width: '12rem',
|
||||||
size: 'w-44',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Mobile no',
|
label: 'Mobile no',
|
||||||
key: 'mobile_no',
|
key: 'mobile_no',
|
||||||
type: 'phone',
|
width: '11rem',
|
||||||
size: 'w-40',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Lead owner',
|
label: 'Lead owner',
|
||||||
key: 'lead_owner',
|
key: 'lead_owner',
|
||||||
type: 'avatar',
|
width: '10rem',
|
||||||
size: 'w-36',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last modified',
|
label: 'Last modified',
|
||||||
key: 'modified',
|
key: 'modified',
|
||||||
type: 'pretty_date',
|
width: '8rem',
|
||||||
size: 'w-28',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -209,8 +264,14 @@ const rows = computed(() => {
|
|||||||
},
|
},
|
||||||
email: lead.email,
|
email: lead.email,
|
||||||
mobile_no: lead.mobile_no,
|
mobile_no: lead.mobile_no,
|
||||||
lead_owner: lead.lead_owner && getUser(lead.lead_owner),
|
lead_owner: {
|
||||||
modified: lead.modified,
|
label: lead.lead_owner && getUser(lead.lead_owner).full_name,
|
||||||
|
...(lead.lead_owner && getUser(lead.lead_owner)),
|
||||||
|
},
|
||||||
|
modified: {
|
||||||
|
label: dateFormat(lead.modified, dateTooltipFormat),
|
||||||
|
timeAgo: timeAgo(lead.modified),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -943,10 +943,10 @@ fraction.js@^4.3.6:
|
|||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d"
|
||||||
integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==
|
integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==
|
||||||
|
|
||||||
frappe-ui@^0.1.6:
|
frappe-ui@^0.1.12:
|
||||||
version "0.1.6"
|
version "0.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.6.tgz#92e92fd1cab582dde09cf278b311f177364f2877"
|
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.12.tgz#f112931897f75f307f18a3227ab641669cdfdddb"
|
||||||
integrity sha512-QmTt+hRF4/u3GSdCVo5hpUgQQX+Rm9hOvNMd3c1xNfCpZJ17nGODk6O89cnbbcfXWvIcTj6mnU7r/lSxa+qq9A==
|
integrity sha512-gb4aiNdcyiOYhJ1QZw1o+P26HSIM6T/3Dq4tfIq8uwvV2l3mk+xIW51g9lHLIUaYri44wojCrRCuCm8apO8xQw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@headlessui/vue" "^1.7.14"
|
"@headlessui/vue" "^1.7.14"
|
||||||
"@popperjs/core" "^2.11.2"
|
"@popperjs/core" "^2.11.2"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user