fix: added custom renderers for Lead/Deal/Task fields using slots for column title & kanban fields
This commit is contained in:
parent
d469a84f46
commit
d8252cae19
@ -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>
|
||||
|
||||
@ -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 []
|
||||
|
||||
@ -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 []
|
||||
|
||||
@ -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 []
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user