feat: Kanban Board View
This commit is contained in:
parent
c71cade089
commit
8fc09341dd
@ -376,6 +376,24 @@ def get_list_data(
|
|||||||
"list_script": get_form_script(doctype, "List"),
|
"list_script": get_form_script(doctype, "List"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_kanban_data(doctype: str, filters: dict, order_by: str, column_field: str, columns=None, rows=None):
|
||||||
|
filters = frappe._dict(filters)
|
||||||
|
columns = frappe.parse_json(columns)
|
||||||
|
data = []
|
||||||
|
for column in columns:
|
||||||
|
column_filters = filters.copy()
|
||||||
|
column_filters.update({column_field: column})
|
||||||
|
column_data = frappe.get_list(
|
||||||
|
doctype,
|
||||||
|
fields=rows,
|
||||||
|
filters=column_filters,
|
||||||
|
order_by=order_by,
|
||||||
|
page_length=20,
|
||||||
|
)
|
||||||
|
data.append({"column": column, "data": column_data, "count": len(column_data)})
|
||||||
|
return data
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
|
def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
|
||||||
not_allowed_fieldtypes = [
|
not_allowed_fieldtypes = [
|
||||||
|
|||||||
62
frontend/src/components/Kanban/KanbanView.vue
Normal file
62
frontend/src/components/Kanban/KanbanView.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex sm:mx-5 mx-3 mb-3 overflow-hidden">
|
||||||
|
<Draggable
|
||||||
|
:list="columns.data"
|
||||||
|
item-key="column"
|
||||||
|
class="flex gap-2 overflow-x-auto"
|
||||||
|
>
|
||||||
|
<template #item="{ element: column }">
|
||||||
|
<div class="flex flex-col gap-2 overflow-hidden min-w-[268px]">
|
||||||
|
<div>{{ column.column }}</div>
|
||||||
|
<Draggable
|
||||||
|
:list="column.data"
|
||||||
|
group="fields"
|
||||||
|
item-key="name"
|
||||||
|
class="flex flex-col gap-2 overflow-y-auto h-full"
|
||||||
|
>
|
||||||
|
<template #item="{ element: fields }">
|
||||||
|
<div class="p-3 rounded border bg-white">
|
||||||
|
<div v-for="value in fields">
|
||||||
|
<div>{{ value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { createResource } from 'frappe-ui'
|
||||||
|
import Draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
doctype: { type: String, required: true },
|
||||||
|
filters: { type: Object, required: true },
|
||||||
|
column_field: { type: String, required: true },
|
||||||
|
columns: { type: Object, required: true },
|
||||||
|
rows: { type: Array, required: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
function getParams() {
|
||||||
|
return {
|
||||||
|
doctype: props.doctype,
|
||||||
|
filters: props.filters,
|
||||||
|
order_by: 'modified',
|
||||||
|
column_field: props.column_field,
|
||||||
|
columns: props.columns,
|
||||||
|
rows: props.rows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = createResource({
|
||||||
|
url: 'crm.api.doc.get_kanban_data',
|
||||||
|
params: getParams(),
|
||||||
|
cache: ['Kanban', props.doctype],
|
||||||
|
auto: true,
|
||||||
|
onSuccess(data) {
|
||||||
|
data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -29,9 +29,24 @@
|
|||||||
allowedViews: ['list', 'group_by', 'kanban'],
|
allowedViews: ['list', 'group_by', 'kanban'],
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
<KanbanView
|
||||||
|
doctype="CRM Lead"
|
||||||
|
:filters="{ converted: 0 }"
|
||||||
|
column_field="status"
|
||||||
|
:columns="[
|
||||||
|
'New',
|
||||||
|
'Contacted',
|
||||||
|
'Nurture',
|
||||||
|
'Qualified',
|
||||||
|
'Unqualified',
|
||||||
|
'Junk',
|
||||||
|
]"
|
||||||
|
:rows="['name', 'status', 'organization', 'lead_owner']"
|
||||||
|
v-if="route.params.viewType == 'kanban'"
|
||||||
|
/>
|
||||||
<LeadsListView
|
<LeadsListView
|
||||||
ref="leadsListView"
|
ref="leadsListView"
|
||||||
v-if="leads.data && rows.length"
|
v-else-if="leads.data && rows.length"
|
||||||
v-model="leads.data.page_length_count"
|
v-model="leads.data.page_length_count"
|
||||||
v-model:list="leads"
|
v-model:list="leads"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
@ -69,6 +84,7 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
|||||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||||
import LeadsListView from '@/components/ListViews/LeadsListView.vue'
|
import LeadsListView from '@/components/ListViews/LeadsListView.vue'
|
||||||
|
import KanbanView from '@/components/Kanban/KanbanView.vue'
|
||||||
import LeadModal from '@/components/Modals/LeadModal.vue'
|
import LeadModal from '@/components/Modals/LeadModal.vue'
|
||||||
import ViewControls from '@/components/ViewControls.vue'
|
import ViewControls from '@/components/ViewControls.vue'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
@ -111,7 +127,7 @@ const rows = computed(() => {
|
|||||||
if (!leads.value?.data.group_by_field?.name) return []
|
if (!leads.value?.data.group_by_field?.name) return []
|
||||||
return getGroupedByRows(
|
return getGroupedByRows(
|
||||||
leads.value?.data.data,
|
leads.value?.data.data,
|
||||||
leads.value?.data.group_by_field
|
leads.value?.data.group_by_field,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return parseRows(leads.value?.data.data)
|
return parseRows(leads.value?.data.data)
|
||||||
@ -177,8 +193,8 @@ function parseRows(rows) {
|
|||||||
lead.sla_status == 'Failed'
|
lead.sla_status == 'Failed'
|
||||||
? 'red'
|
? 'red'
|
||||||
: lead.sla_status == 'Fulfilled'
|
: lead.sla_status == 'Fulfilled'
|
||||||
? 'green'
|
? 'green'
|
||||||
: 'orange'
|
: 'orange'
|
||||||
if (value == 'First Response Due') {
|
if (value == 'First Response Due') {
|
||||||
value = __(timeAgo(lead.response_by))
|
value = __(timeAgo(lead.response_by))
|
||||||
tooltipText = dateFormat(lead.response_by, dateTooltipFormat)
|
tooltipText = dateFormat(lead.response_by, dateTooltipFormat)
|
||||||
@ -213,7 +229,7 @@ function parseRows(rows) {
|
|||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
['first_response_time', 'first_responded_on', 'response_by'].includes(
|
['first_response_time', 'first_responded_on', 'response_by'].includes(
|
||||||
row
|
row,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
let field = row == 'response_by' ? 'response_by' : 'first_responded_on'
|
let field = row == 'response_by' ? 'response_by' : 'first_responded_on'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user