Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Tasks ListView #66

Merged
merged 4 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crm/api/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def get_filterable_fields(doctype: str):
"Float",
"Int",
"Currency",
"Dynamic Link",
"Link",
"Long Text",
"Select",
Expand Down
55 changes: 54 additions & 1 deletion crm/fcrm/doctype/crm_task/crm_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,57 @@


class CRMTask(Document):
pass
@staticmethod
def default_list_data():
columns = [
{
'label': 'Title',
'type': 'Data',
'key': 'title',
'width': '16rem',
},
{
'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",
"reference_doctype",
"reference_docname",
"modified",
]
return {'columns': columns, 'rows': rows}
5 changes: 4 additions & 1 deletion frontend/src/components/Filter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ import { FormControl, Autocomplete, createResource } from 'frappe-ui'
import { h, defineModel, computed } from 'vue'

const typeCheck = ['Check']
const typeLink = ['Link']
const typeLink = ['Link', 'Dynamic Link']
const typeNumber = ['Float', 'Int', 'Currency', 'Percent']
const typeSelect = ['Select']
const typeString = ['Data', 'Long Text', 'Small Text', 'Text Editor', 'Text']
Expand Down Expand Up @@ -324,6 +324,9 @@ function getValSelect(f) {
})),
})
} else if (typeLink.includes(fieldtype)) {
if (field.fieldtype === 'Dynamic Link') {
return h(FormControl, { type: 'text' })
}
return h(Link, { class: 'form-control', doctype: options })
} else if (typeNumber.includes(fieldtype)) {
return h(FormControl, { type: 'number' })
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/Layouts/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import DealsIcon from '@/components/Icons/DealsIcon.vue'
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue'
Expand Down Expand Up @@ -146,6 +147,11 @@ const links = [
icon: NoteIcon,
to: 'Notes',
},
{
label: 'Tasks',
icon: TaskIcon,
to: 'Tasks',
},
{
label: 'Call Logs',
icon: PhoneIcon,
Expand Down
164 changes: 164 additions & 0 deletions frontend/src/components/ListViews/TasksListView.vue
Original file line number Diff line number Diff line change
@@ -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>
33 changes: 33 additions & 0 deletions frontend/src/components/Modals/TaskModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@
],
}"
>
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ editMode ? 'Edit Task' : 'Create Task' }}
</h3>
<Button
v-if="task?.reference_docname"
variant="outline"
size="sm"
:label="task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'"
@click="redirect()"
>
<template #suffix>
<ArrowUpRightIcon class="h-4 w-4" />
</template>
</Button>
</div>
</template>
<template #body-content>
<div class="flex flex-col gap-4">
<div>
Expand Down Expand Up @@ -86,12 +104,14 @@
<script setup>
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import Link from '@/components/Controls/Link.vue'
import { taskStatusOptions, taskPriorityOptions } from '@/utils'
import { usersStore } from '@/stores/users'
import { TextEditor, Dropdown, Tooltip, DatePicker, call } from 'frappe-ui'
import { ref, defineModel, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'

const props = defineProps({
task: {
Expand All @@ -113,6 +133,7 @@ const tasks = defineModel('reloadTasks')

const emit = defineEmits(['updateTask'])

const router = useRouter()
const { getUser } = usersStore()

const title = ref(null)
Expand All @@ -124,6 +145,8 @@ const _task = ref({
due_date: '',
status: 'Backlog',
priority: 'Low',
reference_doctype: props.doctype,
reference_docname: null,
})

function updateTaskStatus(status) {
Expand All @@ -134,6 +157,16 @@ function updateTaskPriority(priority) {
_task.value.priority = priority
}

function redirect() {
if (!props.task?.reference_docname) return
let name = props.task.reference_doctype == 'CRM Deal' ? 'Deal' : 'Lead'
let params = { leadId: props.task.reference_docname }
if (name == 'Deal') {
params = { dealId: props.task.reference_docname }
}
router.push({ name: name, params: params })
}

async function updateTask() {
if (!_task.value.assigned_to) {
_task.value.assigned_to = getUser().email
Expand Down
Loading
Loading