Merge pull request #191 from shariquerik/bulk-assign

feat: Bulk Assign & Clear Assignments
This commit is contained in:
Shariq Ansari 2024-05-21 00:16:40 +05:30 committed by GitHub
commit f892861739
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 329 additions and 654 deletions

View File

@ -376,7 +376,7 @@ def get_type(field):
return "read_only" return "read_only"
return field.fieldtype.lower() return field.fieldtype.lower()
def get_assigned_users(doctype, name): def get_assigned_users(doctype, name, default_assigned_to=None):
assigned_users = frappe.get_all( assigned_users = frappe.get_all(
"ToDo", "ToDo",
fields=["allocated_to"], fields=["allocated_to"],
@ -388,7 +388,12 @@ def get_assigned_users(doctype, name):
pluck="allocated_to", pluck="allocated_to",
) )
return list(set(assigned_users)) users = list(set(assigned_users))
# if users is empty, add default_assigned_to
if not users and default_assigned_to:
users = [default_assigned_to]
return users
@frappe.whitelist() @frappe.whitelist()

View File

@ -30,7 +30,7 @@ def get_deal(name):
deal["doctype_fields"], deal["all_fields"] = get_doctype_fields("CRM Deal", name) deal["doctype_fields"], deal["all_fields"] = get_doctype_fields("CRM Deal", name)
deal["doctype"] = "CRM Deal" deal["doctype"] = "CRM Deal"
deal["_form_script"] = get_form_script('CRM Deal') deal["_form_script"] = get_form_script('CRM Deal')
deal["_assign"] = get_assigned_users("CRM Deal", deal.name) deal["_assign"] = get_assigned_users("CRM Deal", deal.name, deal.owner)
return deal return deal
@frappe.whitelist() @frappe.whitelist()

View File

@ -18,5 +18,5 @@ def get_lead(name):
lead["doctype_fields"], lead["all_fields"] = get_doctype_fields("CRM Lead", name) lead["doctype_fields"], lead["all_fields"] = get_doctype_fields("CRM Lead", name)
lead["doctype"] = "CRM Lead" lead["doctype"] = "CRM Lead"
lead["_form_script"] = get_form_script('CRM Lead') lead["_form_script"] = get_form_script('CRM Lead')
lead["_assign"] = get_assigned_users("CRM Lead", lead.name) lead["_assign"] = get_assigned_users("CRM Lead", lead.name, lead.owner)
return lead return lead

View File

@ -0,0 +1,205 @@
<template>
<EditValueModal
v-model="showEditModal"
:doctype="doctype"
:selectedValues="selectedValues"
@reload="reload"
/>
<AssignmentModal
v-if="selectedValues"
:docs="selectedValues"
:doctype="doctype"
v-model="showAssignmentModal"
v-model:assignees="bulkAssignees"
@reload="reload"
/>
</template>
<script setup>
import EditValueModal from '@/components/Modals/EditValueModal.vue'
import AssignmentModal from '@/components/Modals/AssignmentModal.vue'
import { setupListActions, createToast } from '@/utils'
import { globalStore } from '@/stores/global'
import { call } from 'frappe-ui'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
doctype: {
type: String,
default: '',
},
options: {
type: Object,
default: () => ({
hideEdit: false,
hideDelete: false,
hideAssign: false,
}),
},
})
const list = defineModel()
const router = useRouter()
const { $dialog } = globalStore()
const showEditModal = ref(false)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'solid',
theme: 'red',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: props.doctype,
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const showAssignmentModal = ref(false)
const bulkAssignees = ref([])
function assignValues(selections, unselectAll) {
showAssignmentModal.value = true
selectedValues.value = selections
unselectAllAction.value = unselectAll
}
function clearAssignemnts(selections, unselectAll) {
$dialog({
title: __('Clear Assignment'),
message: __('Are you sure you want to clear assignment for {0} item(s)?', [
selections.size,
]),
variant: 'solid',
theme: 'red',
actions: [
{
label: __('Clear Assignment'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.form.assign_to.remove_multiple', {
doctype: props.doctype,
names: JSON.stringify(Array.from(selections)),
ignore_permissions: true,
}).then(() => {
createToast({
title: __('Assignment cleared successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
reload(unselectAll)
close()
})
},
},
],
})
}
const customBulkActions = ref([])
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = []
if (!props.options.hideEdit) {
actions.push({
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
})
}
if (!props.options.hideDelete) {
actions.push({
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
})
}
if (!props.options.hideAssign) {
actions.push({
label: __('Assign To'),
onClick: () => assignValues(selections, unselectAll),
})
actions.push({
label: __('Clear Assignment'),
onClick: () => clearAssignemnts(selections, unselectAll),
})
}
customBulkActions.value.forEach((action) => {
actions.push({
label: __(action.label),
onClick: () =>
action.onClick({
list: list.value,
selections,
unselectAll,
call,
createToast,
$dialog,
router,
}),
})
})
return actions
}
function reload(unselectAll) {
unselectAllAction.value?.()
unselectAll?.()
list.value?.reload()
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({
bulkActions,
customListActions,
})
</script>

View File

@ -80,7 +80,9 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown
:options="listBulkActionsRef.bulkActions(selections, unselectAll)"
>
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -95,8 +97,18 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<ListBulkActions
ref="listBulkActionsRef"
v-model="list"
doctype="CRM Call Log"
:options="{
hideEdit: true,
hideAssign: true,
}"
/>
</template> </template>
<script setup> <script setup>
import ListBulkActions from '@/components/ListBulkActions.vue'
import { import {
Avatar, Avatar,
ListView, ListView,
@ -108,12 +120,8 @@ import {
ListFooter, ListFooter,
Tooltip, Tooltip,
Dropdown, Dropdown,
call,
} from 'frappe-ui' } from 'frappe-ui'
import { setupListActions, createToast } from '@/utils' import { ref, watch } from 'vue'
import { globalStore } from '@/stores/global'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -146,89 +154,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
function deleteValues(selections, unselectAll) { const listBulkActionsRef = ref(null)
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
variant: 'danger',
actions: [
{
label: 'Delete',
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'CRM Call Log',
}).then(() => {
createToast({
title: 'Deleted successfully',
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customBulkActions = ref([])
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Delete',
onClick: () => deleteValues(selections, unselectAll),
},
]
customBulkActions.value.forEach((action) => {
actions.push({
label: action.label,
onClick: () =>
action.onClick({
list: list.value,
selections,
unselectAll,
call,
createToast,
$dialog,
router,
}),
})
})
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -82,7 +82,9 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown
:options="listBulkActionsRef.bulkActions(selections, unselectAll)"
>
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -98,19 +100,18 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<EditValueModal <ListBulkActions
v-model="showEditModal" ref="listBulkActionsRef"
v-model:unselectAll="unselectAllAction" v-model="list"
doctype="Contact" doctype="Contact"
:selectedValues="selectedValues" :options="{
@reload="list.reload()" hideAssign: true,
}"
/> />
</template> </template>
<script setup> <script setup>
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import EditValueModal from '@/components/Modals/EditValueModal.vue' import ListBulkActions from '@/components/ListBulkActions.vue'
import { globalStore } from '@/stores/global'
import { setupListActions, createToast } from '@/utils'
import { import {
Avatar, Avatar,
ListView, ListView,
@ -122,10 +123,8 @@ import {
ListFooter, ListFooter,
Tooltip, Tooltip,
Dropdown, Dropdown,
call,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, watch, onMounted } from 'vue' import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -158,87 +157,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
const showEditModal = ref(false) const listBulkActionsRef = ref(null)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'Contact',
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
// customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -114,7 +114,9 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown
:options="listBulkActionsRef.bulkActions(selections, unselectAll)"
>
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -130,20 +132,14 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<EditValueModal <ListBulkActions ref="listBulkActionsRef" v-model="list" doctype="CRM Deal" />
v-model="showEditModal"
v-model:unselectAll="unselectAllAction"
doctype="CRM Deal"
:selectedValues="selectedValues"
@reload="list.reload()"
/>
</template> </template>
<script setup> <script setup>
import MultipleAvatar from '@/components/MultipleAvatar.vue' import MultipleAvatar from '@/components/MultipleAvatar.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import EditValueModal from '@/components/Modals/EditValueModal.vue' import ListBulkActions from '@/components/ListBulkActions.vue'
import { import {
Avatar, Avatar,
ListView, ListView,
@ -154,13 +150,9 @@ import {
ListSelectBanner, ListSelectBanner,
ListFooter, ListFooter,
Dropdown, Dropdown,
call,
Tooltip, Tooltip,
} from 'frappe-ui' } from 'frappe-ui'
import { setupListActions, createToast } from '@/utils' import { ref, watch } from 'vue'
import { globalStore } from '@/stores/global'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -193,103 +185,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
const showEditModal = ref(false) const listBulkActionsRef = ref(null)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'CRM Deal',
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customBulkActions = ref([])
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
customBulkActions.value.forEach((action) => {
actions.push({
label: __(action.label),
onClick: () =>
action.onClick({
list: list.value,
selections,
unselectAll,
call,
createToast,
$dialog,
router,
}),
})
})
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -69,7 +69,7 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown :options="listBulkActionsRef.bulkActions(selections, unselectAll)">
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -84,18 +84,17 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<EditValueModal <ListBulkActions
v-model="showEditModal" ref="listBulkActionsRef"
v-model:unselectAll="unselectAllAction" v-model="list"
doctype="Email Template" doctype="Email Template"
:selectedValues="selectedValues" :options="{
@reload="list.reload()" hideAssign: true,
}"
/> />
</template> </template>
<script setup> <script setup>
import EditValueModal from '@/components/Modals/EditValueModal.vue' import ListBulkActions from '@/components/ListBulkActions.vue'
import { globalStore } from '@/stores/global'
import { setupListActions, createToast } from '@/utils'
import { import {
ListView, ListView,
ListHeader, ListHeader,
@ -105,11 +104,9 @@ import {
ListRowItem, ListRowItem,
ListFooter, ListFooter,
Dropdown, Dropdown,
call,
Tooltip, Tooltip,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, watch, onMounted } from 'vue' import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -143,87 +140,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
const showEditModal = ref(false) const listBulkActionsRef = ref(null)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'Email Template',
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
// customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -123,7 +123,7 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown :options="listBulkActionsRef.bulkActions(selections, unselectAll)">
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -139,20 +139,14 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<EditValueModal <ListBulkActions ref="listBulkActionsRef" v-model="list" doctype="CRM Lead" />
v-model="showEditModal"
v-model:unselectAll="unselectAllAction"
doctype="CRM Lead"
:selectedValues="selectedValues"
@reload="list.reload()"
/>
</template> </template>
<script setup> <script setup>
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue' import MultipleAvatar from '@/components/MultipleAvatar.vue'
import EditValueModal from '@/components/Modals/EditValueModal.vue' import ListBulkActions from '@/components/ListBulkActions.vue'
import { import {
Avatar, Avatar,
ListView, ListView,
@ -163,13 +157,9 @@ import {
ListRowItem, ListRowItem,
ListFooter, ListFooter,
Dropdown, Dropdown,
call,
Tooltip, Tooltip,
} from 'frappe-ui' } from 'frappe-ui'
import { setupListActions, createToast } from '@/utils' import { ref, watch } from 'vue'
import { globalStore } from '@/stores/global'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -202,103 +192,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
const showEditModal = ref(false) const listBulkActionsRef = ref(null)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'CRM Lead',
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customBulkActions = ref([])
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
customBulkActions.value.forEach((action) => {
actions.push({
label: __(action.label),
onClick: () =>
action.onClick({
list: list.value,
selections,
unselectAll,
call,
createToast,
$dialog,
router,
}),
})
})
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -69,7 +69,9 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown
:options="listBulkActionsRef.bulkActions(selections, unselectAll)"
>
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -84,18 +86,17 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<EditValueModal <ListBulkActions
v-model="showEditModal" ref="listBulkActionsRef"
v-model:unselectAll="unselectAllAction" v-model="list"
doctype="CRM Organization" doctype="CRM Organization"
:selectedValues="selectedValues" :options="{
@reload="list.reload()" hideAssign: true,
}"
/> />
</template> </template>
<script setup> <script setup>
import EditValueModal from '@/components/Modals/EditValueModal.vue' import ListBulkActions from '@/components/ListBulkActions.vue'
import { globalStore } from '@/stores/global'
import { setupListActions, createToast } from '@/utils'
import { import {
Avatar, Avatar,
ListView, ListView,
@ -107,10 +108,8 @@ import {
ListFooter, ListFooter,
Tooltip, Tooltip,
Dropdown, Dropdown,
call,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, watch, onMounted } from 'vue' import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -143,87 +142,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
const showEditModal = ref(false) const listBulkActionsRef = ref(null)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'CRM Organization',
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
// customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -82,7 +82,7 @@
</ListRows> </ListRows>
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ selections, unselectAll }"> <template #actions="{ selections, unselectAll }">
<Dropdown :options="bulkActions(selections, unselectAll)"> <Dropdown :options="listBulkActionsRef.bulkActions(selections, unselectAll)">
<Button icon="more-horizontal" variant="ghost" /> <Button icon="more-horizontal" variant="ghost" />
</Dropdown> </Dropdown>
</template> </template>
@ -97,22 +97,21 @@
}" }"
@loadMore="emit('loadMore')" @loadMore="emit('loadMore')"
/> />
<EditValueModal <ListBulkActions
v-model="showEditModal" ref="listBulkActionsRef"
v-model:unselectAll="unselectAllAction" v-model="list"
doctype="CRM Task" doctype="CRM Task"
:selectedValues="selectedValues" :options="{
@reload="list.reload()" hideAssign: true,
}"
/> />
</template> </template>
<script setup> <script setup>
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue' import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue' import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
import CalendarIcon from '@/components/Icons/CalendarIcon.vue' import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
import EditValueModal from '@/components/Modals/EditValueModal.vue' import ListBulkActions from '@/components/ListBulkActions.vue'
import { dateFormat } from '@/utils' import { dateFormat } from '@/utils'
import { globalStore } from '@/stores/global'
import { setupListActions, createToast } from '@/utils'
import { import {
Avatar, Avatar,
ListView, ListView,
@ -123,11 +122,9 @@ import {
ListRowItem, ListRowItem,
ListFooter, ListFooter,
Dropdown, Dropdown,
call,
Tooltip, Tooltip,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, watch, onMounted } from 'vue' import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -161,87 +158,14 @@ const emit = defineEmits([
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
const router = useRouter()
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => { watch(pageLengthCount, (val, old_value) => {
if (val === old_value) return if (val === old_value) return
emit('updatePageCount', val) emit('updatePageCount', val)
}) })
const showEditModal = ref(false) const listBulkActionsRef = ref(null)
const selectedValues = ref([])
const unselectAllAction = ref(() => {})
function editValues(selections, unselectAll) {
selectedValues.value = selections
showEditModal.value = true
unselectAllAction.value = unselectAll
}
function deleteValues(selections, unselectAll) {
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
call('frappe.desk.reportview.delete_items', {
items: JSON.stringify(Array.from(selections)),
doctype: 'CRM Task',
}).then(() => {
createToast({
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
unselectAll()
list.value.reload()
close()
})
},
},
],
})
}
const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
return actions
}
onMounted(() => {
if (!list.value?.data) return
setupListActions(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
router,
})
// customBulkActions.value = list.value?.data?.bulkActions || []
customListActions.value = list.value?.data?.listActions || []
})
defineExpose({ defineExpose({
customListActions, customListActions: listBulkActionsRef.value?.customListActions,
}) })
</script> </script>

View File

@ -27,6 +27,7 @@
value="" value=""
doctype="User" doctype="User"
@change="(option) => addValue(option) && ($refs.input.value = '')" @change="(option) => addValue(option) && ($refs.input.value = '')"
:placeholder="__('John Doe')"
:hideMe="true" :hideMe="true"
> >
<template #item-prefix="{ option }"> <template #item-prefix="{ option }">
@ -83,8 +84,18 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
docs: {
type: Array,
default: () => [],
},
doctype: {
type: String,
default: '',
},
}) })
const emit = defineEmits(['reload'])
const show = defineModel() const show = defineModel()
const assignees = defineModel('assignees') const assignees = defineModel('assignees')
const oldAssignees = ref([]) const oldAssignees = ref([])
@ -101,7 +112,7 @@ const removeValue = (value) => {
const owner = computed(() => { const owner = computed(() => {
if (!props.doc) return '' if (!props.doc) return ''
if (props.doc.doctype == 'CRM Lead') return props.doc.lead_owner if (props.doctype == 'CRM Lead') return props.doc.lead_owner
return props.doc.deal_owner return props.doc.deal_owner
}) })
@ -137,7 +148,7 @@ function updateAssignees() {
if (removedAssignees.length) { if (removedAssignees.length) {
for (let a of removedAssignees) { for (let a of removedAssignees) {
call('frappe.desk.form.assign_to.remove', { call('frappe.desk.form.assign_to.remove', {
doctype: props.doc.doctype, doctype: props.doctype,
name: props.doc.name, name: props.doc.name,
assign_to: a, assign_to: a,
}) })
@ -145,11 +156,23 @@ function updateAssignees() {
} }
if (addedAssignees.length) { if (addedAssignees.length) {
call('frappe.desk.form.assign_to.add', { if (props.docs.size) {
doctype: props.doc.doctype, call('frappe.desk.form.assign_to.add_multiple', {
name: props.doc.name, doctype: props.doctype,
assign_to: addedAssignees, name: JSON.stringify(Array.from(props.docs)),
}) assign_to: addedAssignees,
bulk_assign: true,
re_assign: true,
}).then(() => {
emit('reload')
})
} else {
call('frappe.desk.form.assign_to.add', {
doctype: props.doctype,
name: props.doc.name,
assign_to: addedAssignees,
})
}
} }
show.value = false show.value = false
} }

View File

@ -59,7 +59,6 @@ const props = defineProps({
}) })
const show = defineModel() const show = defineModel()
const unselectAll = defineModel('unselectAll')
const emit = defineEmits(['reload']) const emit = defineEmits(['reload'])
@ -114,7 +113,6 @@ function updateValues() {
newValue.value = '' newValue.value = ''
loading.value = false loading.value = false
show.value = false show.value = false
unselectAll.value()
emit('reload') emit('reload')
}) })
} }
@ -130,6 +128,10 @@ function updateValue(v) {
newValue.value = value newValue.value = value
} }
function getSelectOptions(options) {
return options.split('\n')
}
function getValueComponent(f) { function getValueComponent(f) {
const { type, options } = f const { type, options } = f
if (typeSelect.includes(type) || typeCheck.includes(type)) { if (typeSelect.includes(type) || typeCheck.includes(type)) {
@ -140,6 +142,7 @@ function getValueComponent(f) {
label: o, label: o,
value: o, value: o,
})), })),
modelValue: newValue.value,
}) })
} else if (typeLink.includes(type)) { } else if (typeLink.includes(type)) {
if (type == 'Dynamic Link') { if (type == 'Dynamic Link') {

View File

@ -280,6 +280,7 @@
<AssignmentModal <AssignmentModal
v-if="deal.data" v-if="deal.data"
:doc="deal.data" :doc="deal.data"
doctype="CRM Deal"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="deal.data._assignedTo" v-model:assignees="deal.data._assignedTo"
/> />

View File

@ -186,6 +186,7 @@
<AssignmentModal <AssignmentModal
v-if="lead.data" v-if="lead.data"
:doc="lead.data" :doc="lead.data"
doctype="CRM Lead"
v-model="showAssignmentModal" v-model="showAssignmentModal"
v-model:assignees="lead.data._assignedTo" v-model:assignees="lead.data._assignedTo"
/> />