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"),
|
||||
}
|
||||
|
||||
@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()
|
||||
def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
|
||||
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'],
|
||||
}"
|
||||
/>
|
||||
<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
|
||||
ref="leadsListView"
|
||||
v-if="leads.data && rows.length"
|
||||
v-else-if="leads.data && rows.length"
|
||||
v-model="leads.data.page_length_count"
|
||||
v-model:list="leads"
|
||||
:rows="rows"
|
||||
@ -69,6 +84,7 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import LeadsListView from '@/components/ListViews/LeadsListView.vue'
|
||||
import KanbanView from '@/components/Kanban/KanbanView.vue'
|
||||
import LeadModal from '@/components/Modals/LeadModal.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
@ -111,7 +127,7 @@ const rows = computed(() => {
|
||||
if (!leads.value?.data.group_by_field?.name) return []
|
||||
return getGroupedByRows(
|
||||
leads.value?.data.data,
|
||||
leads.value?.data.group_by_field
|
||||
leads.value?.data.group_by_field,
|
||||
)
|
||||
} else {
|
||||
return parseRows(leads.value?.data.data)
|
||||
@ -177,8 +193,8 @@ function parseRows(rows) {
|
||||
lead.sla_status == 'Failed'
|
||||
? 'red'
|
||||
: lead.sla_status == 'Fulfilled'
|
||||
? 'green'
|
||||
: 'orange'
|
||||
? 'green'
|
||||
: 'orange'
|
||||
if (value == 'First Response Due') {
|
||||
value = __(timeAgo(lead.response_by))
|
||||
tooltipText = dateFormat(lead.response_by, dateTooltipFormat)
|
||||
@ -213,7 +229,7 @@ function parseRows(rows) {
|
||||
}
|
||||
} else if (
|
||||
['first_response_time', 'first_responded_on', 'response_by'].includes(
|
||||
row
|
||||
row,
|
||||
)
|
||||
) {
|
||||
let field = row == 'response_by' ? 'response_by' : 'first_responded_on'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user