fix: implemented list group view in lead & deal

This commit is contained in:
Shariq Ansari 2024-05-31 18:12:26 +05:30
parent c86e78267d
commit ed47d87315
6 changed files with 359 additions and 256 deletions

@ -1 +1 @@
Subproject commit 1394a12b6de105649c8ca5beeead62a38ef1b18e
Subproject commit 90c75affe85b2a1abfce68d61c0c5043f093a8a0

View File

@ -29,18 +29,74 @@
</Button>
</ListHeaderItem>
</ListHeader>
<ListRows id="list-rows">
<ListRow
class="mx-5"
v-for="row in rows"
:key="row.name"
v-slot="{ idx, column, item }"
:row="row"
>
<div v-if="column.key === '_assign'" class="flex items-center">
<MultipleAvatar
:avatars="item"
size="sm"
<ListRows :rows="rows" v-slot="{ idx, column, item }">
<div v-if="column.key === '_assign'" class="flex items-center">
<MultipleAvatar
:avatars="item"
size="sm"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
/>
</div>
<ListRowItem v-else :item="item">
<template #prefix>
<div v-if="column.key === 'status'">
<IndicatorIcon :class="item.color" />
</div>
<div v-else-if="column.key === 'organization'">
<Avatar
v-if="item.label"
class="flex items-center"
:image="item.logo"
:label="item.label"
size="sm"
/>
</div>
<div v-else-if="column.key === 'deal_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>
<div v-else-if="column.key === '_liked_by'">
<Button
v-if="column.key == '_liked_by'"
variant="ghosted"
:class="isLiked(item) ? 'fill-red-500' : 'fill-white'"
@click.stop.prevent="
() => emit('likeDoc', { name: row.name, liked: isLiked(item) })
"
>
<HeartIcon class="h-4 w-4" />
</Button>
</div>
</template>
<template #default="{ label }">
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(column.key)
"
class="truncate text-base"
@click="
(event) =>
emit('applyFilter', {
@ -51,60 +107,21 @@
firstColumn: columns[0],
})
"
/>
</div>
<ListRowItem v-else :item="item">
<template #prefix>
<div v-if="column.key === 'status'">
<IndicatorIcon :class="item.color" />
</div>
<div v-else-if="column.key === 'organization'">
<Avatar
v-if="item.label"
class="flex items-center"
:image="item.logo"
:label="item.label"
size="sm"
/>
</div>
<div v-else-if="column.key === 'deal_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>
<div v-else-if="column.key === '_liked_by'">
<Button
v-if="column.key == '_liked_by'"
variant="ghosted"
:class="isLiked(item) ? 'fill-red-500' : 'fill-white'"
@click.stop.prevent="
() =>
emit('likeDoc', { name: row.name, liked: isLiked(item) })
"
>
<HeartIcon class="h-4 w-4" />
</Button>
</div>
</template>
<template #default="{ label }">
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(column.key)
"
class="truncate text-base"
>
<Tooltip :text="item.label">
<div>{{ item.timeAgo }}</div>
</Tooltip>
</div>
<div
v-else-if="column.key === 'sla_status'"
class="truncate text-base"
>
<Badge
v-if="item.value"
:variant="'subtle'"
:theme="item.color"
size="md"
:label="item.value"
@click="
(event) =>
emit('applyFilter', {
@ -115,60 +132,34 @@
firstColumn: columns[0],
})
"
>
<Tooltip :text="item.label">
<div>{{ item.timeAgo }}</div>
</Tooltip>
</div>
<div
v-else-if="column.key === 'sla_status'"
class="truncate text-base"
>
<Badge
v-if="item.value"
:variant="'subtle'"
:theme="item.color"
size="md"
:label="item.value"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
/>
</div>
<div v-else-if="column.type === 'Check'">
<FormControl
type="checkbox"
:modelValue="item"
:disabled="true"
class="text-gray-900"
/>
</div>
<div
v-else
class="truncate text-base"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
>
{{ label }}
</div>
</template>
</ListRowItem>
</ListRow>
/>
</div>
<div v-else-if="column.type === 'Check'">
<FormControl
type="checkbox"
:modelValue="item"
:disabled="true"
class="text-gray-900"
/>
</div>
<div
v-else
class="truncate text-base"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
>
{{ label }}
</div>
</template>
</ListRowItem>
</ListRows>
<ListSelectBanner>
<template #actions="{ selections, unselectAll }">
@ -199,13 +190,12 @@ import MultipleAvatar from '@/components/MultipleAvatar.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import ListBulkActions from '@/components/ListBulkActions.vue'
import ListRows from '@/components/ListViews/ListRows.vue'
import {
Avatar,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListRowItem,
ListSelectBanner,
ListFooter,

View File

@ -29,18 +29,71 @@
</Button>
</ListHeaderItem>
</ListHeader>
<ListRows id="list-rows">
<ListRow
class="mx-5"
v-for="row in rows"
:key="row.name"
v-slot="{ idx, column, item }"
:row="row"
>
<div v-if="column.key === '_assign'" class="flex items-center">
<MultipleAvatar
:avatars="item"
size="sm"
<ListRows :rows="rows" v-slot="{ idx, column, item }">
<div v-if="column.key === '_assign'" class="flex items-center">
<MultipleAvatar
:avatars="item"
size="sm"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
/>
</div>
<ListRowItem v-else :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'">
<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>
<template #default="{ label }">
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(column.key)
"
class="truncate text-base"
@click="
(event) =>
emit('applyFilter', {
@ -51,56 +104,37 @@
firstColumn: columns[0],
})
"
/>
</div>
<ListRowItem v-else :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'">
<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>
<template #default="{ label }">
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(column.key)
>
<Tooltip :text="item.label">
<div>{{ item.timeAgo }}</div>
</Tooltip>
</div>
<div v-else-if="column.key === '_liked_by'">
<Button
v-if="column.key == '_liked_by'"
variant="ghosted"
:class="isLiked(item) ? 'fill-red-500' : 'fill-white'"
@click.stop.prevent="
() =>
emit('likeDoc', {
name: row.name,
liked: isLiked(item),
})
"
class="truncate text-base"
>
<HeartIcon class="h-4 w-4" />
</Button>
</div>
<div
v-else-if="column.key === 'sla_status'"
class="truncate text-base"
>
<Badge
v-if="item.value"
:variant="'subtle'"
:theme="item.color"
size="md"
:label="item.value"
@click="
(event) =>
emit('applyFilter', {
@ -111,73 +145,34 @@
firstColumn: columns[0],
})
"
>
<Tooltip :text="item.label">
<div>{{ item.timeAgo }}</div>
</Tooltip>
</div>
<div v-else-if="column.key === '_liked_by'">
<Button
v-if="column.key == '_liked_by'"
variant="ghosted"
:class="isLiked(item) ? 'fill-red-500' : 'fill-white'"
@click.stop.prevent="
() =>
emit('likeDoc', { name: row.name, liked: isLiked(item) })
"
>
<HeartIcon class="h-4 w-4" />
</Button>
</div>
<div
v-else-if="column.key === 'sla_status'"
class="truncate text-base"
>
<Badge
v-if="item.value"
:variant="'subtle'"
:theme="item.color"
size="md"
:label="item.value"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
/>
</div>
<div v-else-if="column.type === 'Check'">
<FormControl
type="checkbox"
:modelValue="item"
:disabled="true"
class="text-gray-900"
/>
</div>
<div
v-else
class="truncate text-base"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
>
{{ label }}
</div>
</template>
</ListRowItem>
</ListRow>
/>
</div>
<div v-else-if="column.type === 'Check'">
<FormControl
type="checkbox"
:modelValue="item"
:disabled="true"
class="text-gray-900"
/>
</div>
<div
v-else
class="truncate text-base"
@click="
(event) =>
emit('applyFilter', {
event,
idx,
column,
item,
firstColumn: columns[0],
})
"
>
{{ label }}
</div>
</template>
</ListRowItem>
</ListRows>
<ListSelectBanner>
<template #actions="{ selections, unselectAll }">
@ -208,13 +203,12 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue'
import ListBulkActions from '@/components/ListBulkActions.vue'
import ListRows from '@/components/ListViews/ListRows.vue'
import {
Avatar,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListSelectBanner,
ListRowItem,
ListFooter,

View File

@ -0,0 +1,64 @@
<template>
<div class="mx-5 mt-2 h-full overflow-y-auto" v-if="showGroupedRows">
<div v-for="group in reactivieRows" :key="group.group">
<ListGroupHeader :group="group">
<div
class="my-2 flex items-center gap-2 text-base font-medium text-gray-800"
>
<div>{{ __('Status') }} -</div>
<div class="flex items-center gap-1">
<IndicatorIcon :class="group.color" />
<div>{{ group.group }}</div>
</div>
</div>
</ListGroupHeader>
<ListGroupRows :group="group" id="list-rows">
<ListRow
v-for="row in group.rows"
:key="row.name"
v-slot="{ idx, column, item }"
:row="row"
>
<slot v-bind="{ idx, column, item }" />
</ListRow>
</ListGroupRows>
</div>
</div>
<ListRows class="mx-5" v-else id="list-rows">
<ListRow
v-for="row in reactivieRows"
:key="row.name"
v-slot="{ idx, column, item }"
:row="row"
>
<slot v-bind="{ idx, column, item }" />
</ListRow>
</ListRows>
</template>
<script setup>
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import { ListRows, ListRow, ListGroupHeader, ListGroupRows } from 'frappe-ui'
import { ref, computed, watch } from 'vue'
const props = defineProps({
rows: {
type: Array,
required: true,
},
})
const reactivieRows = ref(props.rows)
watch(
() => props.rows,
(val) => (reactivieRows.value = val)
)
let showGroupedRows = computed(() => {
return props.rows.every(
(row) => row.group && row.rows && Array.isArray(row.rows)
)
})
</script>

View File

@ -77,6 +77,7 @@ import {
formatTime,
} from '@/utils'
import { Breadcrumbs } from 'frappe-ui'
import { useRoute } from 'vue-router'
import { ref, computed } from 'vue'
const breadcrumbs = [{ label: __('Deals'), route: { name: 'Deals' } }]
@ -85,6 +86,8 @@ const { getUser } = usersStore()
const { getOrganization } = organizationsStore()
const { getDealStatus } = statusesStore()
const route = useRoute()
const dealsListView = ref(null)
const showDealModal = ref(false)
@ -98,7 +101,7 @@ const viewControls = ref(null)
// Rows
const rows = computed(() => {
if (!deals.value?.data?.data) return []
return deals.value.data.data.map((deal) => {
let listRows = deals.value.data.data.map((deal) => {
let _rows = {}
deals.value.data.rows.forEach((row) => {
_rows[row] = deal[row]
@ -174,5 +177,31 @@ const rows = computed(() => {
})
return _rows
})
if (route.params.viewType === 'group_by') {
return getGroupedByRows(listRows)
}
return listRows
})
function getGroupedByRows(listRows) {
let groupedRows = []
listRows.forEach((row) => {
if (!groupedRows.some((group) => group.group === row.status.label)) {
groupedRows.push({
group: row.status.label,
color: getDealStatus(row.status.label)?.iconColorClass,
collapsed: false,
rows: [],
})
}
groupedRows.filter((group) => {
if (group.group === row.status.label) {
group.rows.push(row)
}
})
})
return groupedRows || listRows
}
</script>

View File

@ -78,7 +78,7 @@ import {
createToast,
} from '@/utils'
import { createResource, Breadcrumbs } from 'frappe-ui'
import { useRouter } from 'vue-router'
import { useRouter, useRoute } from 'vue-router'
import { ref, computed, reactive } from 'vue'
const breadcrumbs = [{ label: __('Leads'), route: { name: 'Leads' } }]
@ -88,6 +88,7 @@ const { getOrganization } = organizationsStore()
const { getLeadStatus } = statusesStore()
const router = useRouter()
const route = useRoute()
const leadsListView = ref(null)
const showLeadModal = ref(false)
@ -102,7 +103,7 @@ const viewControls = ref(null)
// Rows
const rows = computed(() => {
if (!leads.value?.data?.data) return []
return leads.value?.data.data.map((lead) => {
let listRows = leads.value?.data.data.map((lead) => {
let _rows = {}
leads.value?.data.rows.forEach((row) => {
_rows[row] = lead[row]
@ -182,8 +183,33 @@ const rows = computed(() => {
})
return _rows
})
if (route.params.viewType === 'group_by') {
return getGroupedByRows(listRows)
}
return listRows
})
function getGroupedByRows(listRows) {
let groupedRows = []
listRows.forEach((row) => {
if (!groupedRows.some((group) => group.group === row.status.label)) {
groupedRows.push({
group: row.status.label,
color: getLeadStatus(row.status.label)?.iconColorClass,
collapsed: false,
rows: [],
})
}
groupedRows.filter((group) => {
if (group.group === row.status.label) {
group.rows.push(row)
}
})
})
return groupedRows || listRows
}
let newLead = reactive({
salutation: '',
first_name: '',