1
0
forked from test/crm

fix: added custom renderers for Lead/Deal/Task fields using slots for column title & kanban fields

This commit is contained in:
Shariq Ansari 2024-06-26 19:36:35 +05:30
parent d469a84f46
commit d8252cae19
4 changed files with 431 additions and 22 deletions

View File

@ -85,25 +85,40 @@
: undefined,
}"
>
<div class="h-5 flex items-center">
<div v-if="fields[titleField]">{{ fields[titleField] }}</div>
<div class="text-gray-500" v-else>{{ __('No Title') }}</div>
</div>
<div class="border-b h-px my-2.5" />
<div class="flex flex-col gap-2">
<div v-for="value in column.fields" :key="value">
<div class="truncate">{{ fields[value] || '-' }}</div>
<slot
name="item-title"
v-bind="{ fields, titleField, itemName: fields.name }"
>
<div class="h-5 flex items-center">
<div v-if="fields[titleField]">{{ fields[titleField] }}</div>
<div class="text-gray-500" v-else>{{ __('No Title') }}</div>
</div>
</slot>
<div class="border-b h-px my-2.5" />
<div class="flex flex-col gap-3.5">
<template v-for="value in column.fields" :key="value">
<slot
name="item-fields"
v-bind="{
fields,
fieldName: value,
itemName: fields.name,
}"
>
<div v-if="fields[value]" class="truncate">
{{ fields[value] }}
</div>
</slot>
</template>
</div>
<div class="border-b h-px mt-2.5 mb-2" />
<div class="flex gap-2 items-center justify-between">
<div></div>
<Button
icon="plus"
variant="ghost"
@click.stop.prevent
/>
</div>
<slot name="item-actions">
<div class="flex gap-2 items-center justify-between">
<div></div>
<Button icon="plus" variant="ghost" @click.stop.prevent />
</div>
</slot>
</component>
</template>
</Draggable>

View File

@ -36,7 +36,129 @@
onNewClick: (column) => onNewClick(column),
}"
@update="(data) => viewControls.updateKanbanSettings(data)"
/>
>
<template #item-title="{ titleField, itemName }">
<div class="flex gap-2 items-center">
<div v-if="titleField === 'status'">
<IndicatorIcon :class="getRow(itemName, titleField).color" />
</div>
<div
v-else-if="
titleField === 'organization' && getRow(itemName, titleField).label
"
>
<Avatar
class="flex items-center"
:image="getRow(itemName, titleField).logo"
:label="getRow(itemName, titleField).label"
size="sm"
/>
</div>
<div
v-else-if="
titleField === 'deal_owner' &&
getRow(itemName, titleField).full_name
"
>
<Avatar
class="flex items-center"
:image="getRow(itemName, titleField).user_image"
:label="getRow(itemName, titleField).full_name"
size="sm"
/>
</div>
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(titleField)
"
class="truncate text-base"
>
<Tooltip :text="getRow(itemName, titleField).label">
<div>{{ getRow(itemName, titleField).timeAgo }}</div>
</Tooltip>
</div>
<div v-else-if="titleField === 'sla_status'" class="truncate text-base">
<Badge
v-if="getRow(itemName, titleField).value"
:variant="'subtle'"
:theme="getRow(itemName, titleField).color"
size="md"
:label="getRow(itemName, titleField).value"
/>
</div>
<div
v-else-if="getRow(itemName, titleField).label"
class="truncate text-base"
>
{{ getRow(itemName, titleField).label }}
</div>
<div class="text-gray-500" v-else>{{ __('No Title') }}</div>
</div>
</template>
<template #item-fields="{ fieldName, itemName }">
<div
v-if="getRow(itemName, fieldName).label"
class="truncate flex items-center gap-2"
>
<div v-if="fieldName === 'status'">
<IndicatorIcon :class="getRow(itemName, fieldName).color" />
</div>
<div v-else-if="fieldName === 'organization'">
<Avatar
v-if="getRow(itemName, fieldName).label"
class="flex items-center"
:image="getRow(itemName, fieldName).logo"
:label="getRow(itemName, fieldName).label"
size="xs"
/>
</div>
<div v-else-if="fieldName === 'deal_owner'">
<Avatar
v-if="getRow(itemName, fieldName).full_name"
class="flex items-center"
:image="getRow(itemName, fieldName).user_image"
:label="getRow(itemName, fieldName).full_name"
size="xs"
/>
</div>
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(fieldName)
"
class="truncate text-base"
>
<Tooltip :text="getRow(itemName, fieldName).label">
<div>{{ getRow(itemName, fieldName).timeAgo }}</div>
</Tooltip>
</div>
<div v-else-if="fieldName === 'sla_status'" class="truncate text-base">
<Badge
v-if="getRow(itemName, fieldName).value"
:variant="'subtle'"
:theme="getRow(itemName, fieldName).color"
size="md"
:label="getRow(itemName, fieldName).value"
/>
</div>
<div v-else class="truncate text-base">
{{ getRow(itemName, fieldName).label }}
</div>
</div>
</template>
</KanbanView>
<DealsListView
ref="dealsListView"
v-else-if="deals.data && rows.length"
@ -94,7 +216,7 @@ import {
formatNumberIntoCurrency,
formatTime,
} from '@/utils'
import { Breadcrumbs } from 'frappe-ui'
import { Breadcrumbs, Tooltip, Avatar } from 'frappe-ui'
import { useRoute } from 'vue-router'
import { ref, reactive, computed, h } from 'vue'
@ -118,6 +240,16 @@ const triggerResize = ref(1)
const updatedPageCount = ref(20)
const viewControls = ref(null)
function getRow(name, field) {
function getValue(value) {
if (value && typeof value === 'object') {
return value
}
return { label: value }
}
return getValue(rows.value?.find((row) => row.name == name)[field])
}
// Rows
const rows = computed(() => {
if (!deals.value?.data?.data) return []

View File

@ -37,7 +37,163 @@
onNewClick: (column) => onNewClick(column),
}"
@update="(data) => viewControls.updateKanbanSettings(data)"
/>
>
<template #item-title="{ titleField, itemName }">
<div class="flex items-center gap-2">
<div v-if="titleField === 'status'">
<IndicatorIcon :class="getRow(itemName, titleField).color" />
</div>
<div
v-else-if="
titleField === 'organization' && getRow(itemName, titleField).label
"
>
<Avatar
class="flex items-center"
:image="getRow(itemName, titleField).logo"
:label="getRow(itemName, titleField).label"
size="sm"
/>
</div>
<div
v-else-if="
titleField === 'lead_name' && getRow(itemName, titleField).label
"
>
<Avatar
class="flex items-center"
:image="getRow(itemName, titleField).image"
:label="getRow(itemName, titleField).image_label"
size="sm"
/>
</div>
<div
v-else-if="
titleField === 'lead_owner' &&
getRow(itemName, titleField).full_name
"
>
<Avatar
class="flex items-center"
:image="getRow(itemName, titleField).user_image"
:label="getRow(itemName, titleField).full_name"
size="sm"
/>
</div>
<div v-else-if="titleField === 'mobile_no'">
<PhoneIcon class="h-4 w-4" />
</div>
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(titleField)
"
class="truncate text-base"
>
<Tooltip :text="getRow(itemName, titleField).label">
<div>{{ getRow(itemName, titleField).timeAgo }}</div>
</Tooltip>
</div>
<div v-else-if="titleField === 'sla_status'" class="truncate text-base">
<Badge
v-if="getRow(itemName, titleField).value"
:variant="'subtle'"
:theme="getRow(itemName, titleField).color"
size="md"
:label="getRow(itemName, titleField).value"
/>
</div>
<div
v-else-if="getRow(itemName, titleField).label"
class="truncate text-base"
>
{{ getRow(itemName, titleField).label }}
</div>
<div class="text-gray-500" v-else>{{ __('No Title') }}</div>
</div>
</template>
<template #item-fields="{ fieldName, itemName }">
<div
v-if="getRow(itemName, fieldName).label"
class="truncate flex items-center gap-2"
>
<div v-if="fieldName === 'status'">
<IndicatorIcon :class="getRow(itemName, fieldName).color" />
</div>
<div
v-else-if="
fieldName === 'organization' && getRow(itemName, fieldName).label
"
>
<Avatar
class="flex items-center"
:image="getRow(itemName, fieldName).logo"
:label="getRow(itemName, fieldName).label"
size="xs"
/>
</div>
<div v-else-if="fieldName === 'lead_name'">
<Avatar
v-if="getRow(itemName, fieldName).label"
class="flex items-center"
:image="getRow(itemName, fieldName).image"
:label="getRow(itemName, fieldName).image_label"
size="xs"
/>
</div>
<div v-else-if="fieldName === 'lead_owner'">
<Avatar
v-if="getRow(itemName, fieldName).full_name"
class="flex items-center"
:image="getRow(itemName, fieldName).user_image"
:label="getRow(itemName, fieldName).full_name"
size="xs"
/>
</div>
<div
v-if="
[
'modified',
'creation',
'first_response_time',
'first_responded_on',
'response_by',
].includes(fieldName)
"
class="truncate text-base"
>
<Tooltip :text="getRow(itemName, fieldName).label">
<div>{{ getRow(itemName, fieldName).timeAgo }}</div>
</Tooltip>
</div>
<div v-else-if="fieldName === 'sla_status'" class="truncate text-base">
<Badge
v-if="getRow(itemName, fieldName).value"
:variant="'subtle'"
:theme="getRow(itemName, fieldName).color"
size="md"
:label="getRow(itemName, fieldName).value"
/>
</div>
<div v-else-if="fieldName === 'Check'">
<FormControl
type="checkbox"
:modelValue="getRow(itemName, fieldName)"
:disabled="true"
class="text-gray-900"
/>
</div>
<div v-else class="truncate text-base">
{{ getRow(itemName, fieldName).label }}
</div>
</div>
</template>
</KanbanView>
<LeadsListView
ref="leadsListView"
v-else-if="leads.data && rows.length"
@ -78,6 +234,7 @@
<script setup>
import CustomActions from '@/components/CustomActions.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
@ -89,7 +246,7 @@ import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses'
import { dateFormat, dateTooltipFormat, timeAgo, formatTime } from '@/utils'
import { Breadcrumbs } from 'frappe-ui'
import { Breadcrumbs, Avatar, Tooltip } from 'frappe-ui'
import { useRoute } from 'vue-router'
import { ref, computed, reactive, h } from 'vue'
@ -113,6 +270,16 @@ const triggerResize = ref(1)
const updatedPageCount = ref(20)
const viewControls = ref(null)
function getRow(name, field) {
function getValue(value) {
if (value && typeof value === 'object') {
return value
}
return { label: value }
}
return getValue(rows.value?.find((row) => row.name == name)[field])
}
// Rows
const rows = computed(() => {
if (!leads.value?.data?.data) return []

View File

@ -32,7 +32,90 @@
onNewClick: (column) => createTask(column),
}"
@update="(data) => viewControls.updateKanbanSettings(data)"
/>
>
<template #item-title="{ titleField, itemName }">
<div class="flex items-center gap-2">
<div v-if="titleField === 'status'">
<TaskStatusIcon :status="getRow(itemName, titleField).label" />
</div>
<div v-else-if="titleField === 'priority'">
<TaskPriorityIcon :priority="getRow(itemName, titleField).label" />
</div>
<div v-else-if="titleField === 'assigned_to'">
<Avatar
v-if="getRow(itemName, titleField).full_name"
class="flex items-center"
:image="getRow(itemName, titleField).user_image"
:label="getRow(itemName, titleField).full_name"
size="sm"
/>
</div>
<div
v-if="['modified', 'creation'].includes(titleField)"
class="truncate text-base"
>
<Tooltip :text="getRow(itemName, titleField).label">
<div>{{ getRow(itemName, titleField).timeAgo }}</div>
</Tooltip>
</div>
<div
v-else-if="getRow(itemName, titleField).label"
class="truncate text-base"
>
{{ getRow(itemName, titleField).label }}
</div>
<div class="text-gray-500" v-else>{{ __('No Title') }}</div>
</div>
</template>
<template #item-fields="{ fieldName, itemName }">
<div
v-if="getRow(itemName, fieldName).label"
class="truncate flex items-center gap-2"
>
<div v-if="fieldName === 'status'">
<TaskStatusIcon
class="size-3"
:status="getRow(itemName, fieldName).label"
/>
</div>
<div v-else-if="fieldName === 'priority'">
<TaskPriorityIcon :priority="getRow(itemName, fieldName).label" />
</div>
<div v-else-if="fieldName === 'assigned_to'">
<Avatar
v-if="getRow(itemName, fieldName).full_name"
class="flex items-center"
:image="getRow(itemName, fieldName).user_image"
:label="getRow(itemName, fieldName).full_name"
size="sm"
/>
</div>
<div
v-if="['modified', 'creation'].includes(fieldName)"
class="truncate text-base"
>
<Tooltip :text="getRow(itemName, fieldName).label">
<div>{{ getRow(itemName, fieldName).timeAgo }}</div>
</Tooltip>
</div>
<div
v-else-if="fieldName == 'description'"
class="truncate text-base max-h-44"
>
<TextEditor
v-if="getRow(itemName, fieldName).label"
:content="getRow(itemName, fieldName).label"
:editable="false"
editor-class="!prose-sm max-w-none focus:outline-none"
class="flex-1 overflow-hidden"
/>
</div>
<div v-else class="truncate text-base">
{{ getRow(itemName, fieldName).label }}
</div>
</div>
</template>
</KanbanView>
<TasksListView
ref="tasksListView"
v-else-if="tasks.data && rows.length"
@ -75,6 +158,8 @@
<script setup>
import CustomActions from '@/components/CustomActions.vue'
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
import EmailIcon from '@/components/Icons/EmailIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import ViewControls from '@/components/ViewControls.vue'
@ -83,7 +168,7 @@ import KanbanView from '@/components/Kanban/KanbanView.vue'
import TaskModal from '@/components/Modals/TaskModal.vue'
import { usersStore } from '@/stores/users'
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
import { Breadcrumbs } from 'frappe-ui'
import { Breadcrumbs, Tooltip, Avatar, TextEditor } from 'frappe-ui'
import { computed, ref } from 'vue'
const breadcrumbs = [{ label: __('Tasks'), route: { name: 'Tasks' } }]
@ -99,6 +184,16 @@ const triggerResize = ref(1)
const updatedPageCount = ref(20)
const viewControls = ref(null)
function getRow(name, field) {
function getValue(value) {
if (value && typeof value === 'object') {
return value
}
return { label: value }
}
return getValue(rows.value?.find((row) => row.name == name)[field])
}
const rows = computed(() => {
if (!tasks.value?.data?.data) return []