fix: added task listview and dialog
This commit is contained in:
parent
e06d110cbc
commit
531e5710b8
@ -6,4 +6,55 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMTask(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def default_list_data():
|
||||
columns = [
|
||||
{
|
||||
'label': 'Title',
|
||||
'type': 'Data',
|
||||
'key': 'title',
|
||||
'width': '12rem',
|
||||
},
|
||||
{
|
||||
'label': 'Status',
|
||||
'type': 'Select',
|
||||
'key': 'status',
|
||||
'width': '8rem',
|
||||
},
|
||||
{
|
||||
'label': 'Priority',
|
||||
'type': 'Select',
|
||||
'key': 'priority',
|
||||
'width': '8rem',
|
||||
},
|
||||
{
|
||||
'label': 'Due Date',
|
||||
'type': 'Date',
|
||||
'key': 'due_date',
|
||||
'width': '8rem',
|
||||
},
|
||||
{
|
||||
'label': 'Assigned To',
|
||||
'type': 'Link',
|
||||
'key': 'assigned_to',
|
||||
'width': '10rem',
|
||||
},
|
||||
{
|
||||
'label': 'Last Modified',
|
||||
'type': 'Datetime',
|
||||
'key': 'modified',
|
||||
'width': '8rem',
|
||||
},
|
||||
]
|
||||
|
||||
rows = [
|
||||
"name",
|
||||
"title",
|
||||
"description",
|
||||
"assigned_to",
|
||||
"due_date",
|
||||
"status",
|
||||
"priority",
|
||||
"modified",
|
||||
]
|
||||
return {'columns': columns, 'rows': rows}
|
||||
|
||||
164
frontend/src/components/ListViews/TasksListView.vue
Normal file
164
frontend/src/components/ListViews/TasksListView.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<ListView
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:options="{
|
||||
onRowClick: (row) => emit('showTask', row.name),
|
||||
selectable: options.selectable,
|
||||
}"
|
||||
row-key="name"
|
||||
>
|
||||
<ListHeader class="mx-5" />
|
||||
<ListRows id="list-rows">
|
||||
<ListRow
|
||||
class="mx-5"
|
||||
v-for="row in rows"
|
||||
:key="row.name"
|
||||
v-slot="{ column, item }"
|
||||
:row="row"
|
||||
>
|
||||
<div
|
||||
v-if="column.key === 'due_date'"
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<CalendarIcon />
|
||||
<div v-if="item">
|
||||
<Tooltip :text="dateFormat(item, 'ddd, MMM D, YYYY')">
|
||||
{{ dateFormat(item, 'D MMM') }}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<ListRowItem v-else :item="item">
|
||||
<template #prefix>
|
||||
<div v-if="column.key === 'status'">
|
||||
<TaskStatusIcon :status="item" />
|
||||
</div>
|
||||
<div v-else-if="column.key === 'priority'">
|
||||
<TaskPriorityIcon :priority="item" />
|
||||
</div>
|
||||
<div v-else-if="column.key === 'assigned_to'">
|
||||
<Avatar
|
||||
v-if="item.full_name"
|
||||
class="flex items-center"
|
||||
:image="item.user_image"
|
||||
:label="item.full_name"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-if="['modified', 'creation'].includes(column.key)"
|
||||
class="truncate text-base"
|
||||
>
|
||||
{{ item.timeAgo }}
|
||||
</div>
|
||||
<div v-else-if="column.type === 'Check'">
|
||||
<FormControl
|
||||
type="checkbox"
|
||||
:modelValue="item"
|
||||
:disabled="true"
|
||||
class="text-gray-900"
|
||||
/>
|
||||
</div>
|
||||
</ListRowItem>
|
||||
</ListRow>
|
||||
</ListRows>
|
||||
<ListSelectBanner>
|
||||
<template #actions="{ selections, unselectAll }">
|
||||
<Button
|
||||
theme="red"
|
||||
variant="subtle"
|
||||
label="Delete"
|
||||
@click="deleteTask(selections, unselectAll)"
|
||||
/>
|
||||
</template>
|
||||
</ListSelectBanner>
|
||||
</ListView>
|
||||
<ListFooter
|
||||
class="border-t px-5 py-2"
|
||||
v-model="pageLengthCount"
|
||||
:options="{
|
||||
rowCount: options.rowCount,
|
||||
totalCount: options.totalCount,
|
||||
}"
|
||||
@loadMore="emit('loadMore')"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
|
||||
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
|
||||
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
|
||||
import { dateFormat } from '@/utils'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import {
|
||||
Avatar,
|
||||
ListView,
|
||||
ListHeader,
|
||||
ListRows,
|
||||
ListRow,
|
||||
ListSelectBanner,
|
||||
ListRowItem,
|
||||
ListFooter,
|
||||
call,
|
||||
Tooltip,
|
||||
} from 'frappe-ui'
|
||||
import { defineModel } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
selectable: true,
|
||||
totalCount: 0,
|
||||
rowCount: 0,
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['loadMore', 'showTask', 'reload'])
|
||||
|
||||
const pageLengthCount = defineModel()
|
||||
|
||||
const { $dialog } = globalStore()
|
||||
|
||||
function deleteTask(selections, unselectAll) {
|
||||
let title = 'Delete task'
|
||||
let message = 'Are you sure you want to delete this task?'
|
||||
|
||||
if (selections.size > 1) {
|
||||
title = 'Delete tasks'
|
||||
message = 'Are you sure you want to delete these tasks?'
|
||||
}
|
||||
|
||||
$dialog({
|
||||
title: title,
|
||||
message: message,
|
||||
actions: [
|
||||
{
|
||||
label: 'Delete',
|
||||
theme: 'red',
|
||||
variant: 'solid',
|
||||
async onClick(close) {
|
||||
for (const selection of selections) {
|
||||
await call('frappe.client.delete', {
|
||||
doctype: 'CRM Task',
|
||||
name: selection,
|
||||
})
|
||||
}
|
||||
close()
|
||||
unselectAll()
|
||||
emit('reload')
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
</script>
|
||||
106
frontend/src/pages/Tasks.vue
Normal file
106
frontend/src/pages/Tasks.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<Button variant="solid" label="Create" @click="showTaskModal = true">
|
||||
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
|
||||
</Button>
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<ViewControls
|
||||
v-model="tasks"
|
||||
v-model:loadMore="loadMore"
|
||||
doctype="CRM Task"
|
||||
/>
|
||||
<TasksListView
|
||||
v-if="tasks.data && rows.length"
|
||||
v-model="tasks.data.page_length_count"
|
||||
:rows="rows"
|
||||
:columns="tasks.data.columns"
|
||||
:options="{
|
||||
rowCount: tasks.data.row_count,
|
||||
totalCount: tasks.data.total_count,
|
||||
}"
|
||||
@loadMore="() => loadMore++"
|
||||
@showTask="showTask"
|
||||
@reload="() => tasks.reload()"
|
||||
/>
|
||||
<div v-else-if="tasks.data" class="flex h-full items-center justify-center">
|
||||
<div
|
||||
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
|
||||
>
|
||||
<EmailIcon class="h-10 w-10" />
|
||||
<span>No Tasks Found</span>
|
||||
</div>
|
||||
</div>
|
||||
<TaskModal v-model="showTaskModal" v-model:reloadTasks="tasks" :task="task" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import TasksListView from '@/components/ListViews/TasksListView.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 { computed, ref } from 'vue'
|
||||
|
||||
const breadcrumbs = [{ label: 'Tasks', route: { name: 'Tasks' } }]
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
// tasks data is loaded in the ViewControls component
|
||||
const tasks = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
const rows = computed(() => {
|
||||
if (!tasks.value?.data?.data) return []
|
||||
return tasks.value?.data.data.map((task) => {
|
||||
let _rows = {}
|
||||
tasks.value?.data.rows.forEach((row) => {
|
||||
_rows[row] = task[row]
|
||||
|
||||
if (['modified', 'creation'].includes(row)) {
|
||||
_rows[row] = {
|
||||
label: dateFormat(task[row], dateTooltipFormat),
|
||||
timeAgo: timeAgo(task[row]),
|
||||
}
|
||||
} else if (row == 'assigned_to') {
|
||||
_rows[row] = {
|
||||
label: task.assigned_to && getUser(task.assigned_to).full_name,
|
||||
...(task.assigned_to && getUser(task.assigned_to)),
|
||||
}
|
||||
}
|
||||
})
|
||||
return _rows
|
||||
})
|
||||
})
|
||||
|
||||
const showTaskModal = ref(false)
|
||||
|
||||
const task = ref({
|
||||
title: '',
|
||||
description: '',
|
||||
assigned_to: '',
|
||||
due_date: '',
|
||||
status: 'Backlog',
|
||||
priority: 'Low',
|
||||
})
|
||||
|
||||
function showTask(name) {
|
||||
let t = rows.value?.find((row) => row.name === name)
|
||||
task.value = {
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
assigned_to: t.assigned_to?.email || '',
|
||||
due_date: t.due_date,
|
||||
status: t.status,
|
||||
priority: t.priority,
|
||||
}
|
||||
showTaskModal.value = true
|
||||
}
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user