1
0
forked from test/crm

fix: show view action in view selector dropdown itself

This commit is contained in:
Shariq Ansari 2024-08-21 19:56:07 +05:30
parent d2c2254230
commit c1882f7280
4 changed files with 149 additions and 82 deletions

View File

@ -108,9 +108,9 @@ watch(show, (value) => {
duplicateMode.value = false duplicateMode.value = false
nextTick(() => { nextTick(() => {
_view.value = { ...view.value } _view.value = { ...view.value }
if (_view.value.name) { if (_view.value.mode === 'edit') {
editMode.value = true editMode.value = true
} else if (_view.value.label) { } else if (_view.value.mode === 'duplicate') {
duplicateMode.value = true duplicateMode.value = true
} }
}) })

View File

@ -11,7 +11,7 @@
<template #default="{ open }"> <template #default="{ open }">
<Button <Button
variant="ghost" variant="ghost"
class="text-lg font-medium" class="text-lg font-medium text-nowrap"
:label="__(viewControls.currentView.label)" :label="__(viewControls.currentView.label)"
> >
<template #prefix> <template #prefix>
@ -25,17 +25,62 @@
</template> </template>
</Button> </Button>
</template> </template>
</Dropdown> <template #item="{ item, active }">
<Dropdown :options="viewControls.viewActions"> <button
<template #default> :class="[
<Button variant="ghost" icon="more-horizontal" /> active ? 'bg-gray-100' : 'text-gray-800',
'group flex gap-4 h-7 w-full justify-between items-center rounded px-2 text-base',
]"
@click="item.onClick"
>
<div class="flex items-center">
<FeatherIcon
v-if="item.icon && typeof item.icon === 'string'"
:name="item.icon"
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
aria-hidden="true"
/>
<component
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
v-else-if="item.icon"
:is="item.icon"
/>
<span class="whitespace-nowrap">
{{ item.label }}
</span>
</div>
<div
v-if="item.name"
class="flex flex-row-reverse gap-2 items-center min-w-11"
>
<Dropdown
:class="active ? 'block' : 'hidden'"
placement="right-start"
:options="viewControls.viewActions(item)"
>
<template #default="{ togglePopover }">
<Button
variant="ghost"
class="!size-5"
icon="more-horizontal"
@click.stop="togglePopover()"
/>
</template>
</Dropdown>
<FeatherIcon
v-if="isCurrentView(item)"
name="check"
class="size-4 text-gray-700"
/>
</div>
</button>
</template> </template>
</Dropdown> </Dropdown>
</div> </div>
</template> </template>
<script setup> <script setup>
import Icon from '@/components/Icon.vue' import Icon from '@/components/Icon.vue'
import { Dropdown } from 'frappe-ui' import Dropdown from '@/components/frappe-ui/Dropdown.vue'
const props = defineProps({ const props = defineProps({
routeName: { routeName: {
@ -45,4 +90,8 @@ const props = defineProps({
}) })
const viewControls = defineModel() const viewControls = defineModel()
const isCurrentView = (item) => {
return item.name === viewControls.value.currentView.name
}
</script> </script>

View File

@ -162,6 +162,7 @@
afterUpdate: () => { afterUpdate: () => {
viewUpdated = false viewUpdated = false
reloadView() reloadView()
list.reload()
}, },
}" }"
/> />
@ -280,14 +281,17 @@ function getViewType() {
let viewType = route.params.viewType || 'list' let viewType = route.params.viewType || 'list'
let types = { let types = {
list: { list: {
name: 'list',
label: __('List'), label: __('List'),
icon: markRaw(ListIcon), icon: markRaw(ListIcon),
}, },
group_by: { group_by: {
name: 'group_by',
label: __('Group By'), label: __('Group By'),
icon: markRaw(GroupByIcon), icon: markRaw(GroupByIcon),
}, },
kanban: { kanban: {
name: 'kanban',
label: __('Kanban'), label: __('Kanban'),
icon: markRaw(KanbanIcon), icon: markRaw(KanbanIcon),
}, },
@ -299,6 +303,7 @@ function getViewType() {
const currentView = computed(() => { const currentView = computed(() => {
let _view = getView(route.query.view, route.params.viewType, props.doctype) let _view = getView(route.query.view, route.params.viewType, props.doctype)
return { return {
name: _view?.name || getViewType().name,
label: label:
_view?.label || props.options?.defaultViewName || getViewType().label, _view?.label || props.options?.defaultViewName || getViewType().label,
icon: _view?.icon || getViewType().icon, icon: _view?.icon || getViewType().icon,
@ -471,6 +476,7 @@ let allowedViews = props.options.allowedViews || ['list']
if (allowedViews.includes('list')) { if (allowedViews.includes('list')) {
defaultViews.push({ defaultViews.push({
name: 'list',
label: __(props.options?.defaultViewName) || __('List'), label: __(props.options?.defaultViewName) || __('List'),
icon: markRaw(ListIcon), icon: markRaw(ListIcon),
onClick() { onClick() {
@ -481,6 +487,7 @@ if (allowedViews.includes('list')) {
} }
if (allowedViews.includes('kanban')) { if (allowedViews.includes('kanban')) {
defaultViews.push({ defaultViews.push({
name: 'kanban',
label: __(props.options?.defaultViewName) || __('Kanban'), label: __(props.options?.defaultViewName) || __('Kanban'),
icon: markRaw(KanbanIcon), icon: markRaw(KanbanIcon),
onClick() { onClick() {
@ -491,6 +498,7 @@ if (allowedViews.includes('kanban')) {
} }
if (allowedViews.includes('group_by')) { if (allowedViews.includes('group_by')) {
defaultViews.push({ defaultViews.push({
name: 'group_by',
label: __(props.options?.defaultViewName) || __('Group By'), label: __(props.options?.defaultViewName) || __('Group By'),
icon: markRaw(GroupByIcon), icon: markRaw(GroupByIcon),
onClick() { onClick() {
@ -522,6 +530,7 @@ const viewsDropdownOptions = computed(() => {
if (list.value?.data?.views) { if (list.value?.data?.views) {
list.value.data.views.forEach((view) => { list.value.data.views.forEach((view) => {
view.name = view.name
view.label = __(view.label) view.label = __(view.label)
view.type = view.type || 'list' view.type = view.type || 'list'
view.icon = getIcon(view.icon, view.type) view.icon = getIcon(view.icon, view.type)
@ -544,17 +553,16 @@ const viewsDropdownOptions = computed(() => {
) )
let pinnedViews = list.value.data.views.filter((v) => v.pinned) let pinnedViews = list.value.data.views.filter((v) => v.pinned)
publicViews.length &&
_views.push({
group: __('Public Views'),
items: publicViews,
})
savedViews.length && savedViews.length &&
_views.push({ _views.push({
group: __('Saved Views'), group: __('Saved Views'),
items: savedViews, items: savedViews,
}) })
publicViews.length &&
_views.push({
group: __('Public Views'),
items: publicViews,
})
pinnedViews.length && pinnedViews.length &&
_views.push({ _views.push({
group: __('Pinned Views'), group: __('Pinned Views'),
@ -866,7 +874,10 @@ function updatePageLength(value, loadMore = false) {
} }
// View Actions // View Actions
const viewActions = computed(() => { const viewActions = (view) => {
let isDefault = typeof view.name === 'string'
let _view = getView(view.name)
let actions = [ let actions = [
{ {
group: __('Default Views'), group: __('Default Views'),
@ -875,37 +886,36 @@ const viewActions = computed(() => {
{ {
label: __('Duplicate'), label: __('Duplicate'),
icon: () => h(DuplicateIcon, { class: 'h-4 w-4' }), icon: () => h(DuplicateIcon, { class: 'h-4 w-4' }),
onClick: () => duplicateView(), onClick: () => duplicateView(_view),
}, },
], ],
}, },
] ]
if (route.query.view && (!view.value.public || isManager())) { if (!isDefault && (!_view.public || isManager())) {
actions[0].items.push({ actions[0].items.push({
label: __('Edit'), label: __('Edit'),
icon: () => h(EditIcon, { class: 'h-4 w-4' }), icon: () => h(EditIcon, { class: 'h-4 w-4' }),
onClick: () => editView(), onClick: () => editView(_view),
}) })
if (!view.value.public) { if (!_view.public) {
actions[0].items.push({ actions[0].items.push({
label: view.value.pinned ? __('Unpin View') : __('Pin View'), label: _view.pinned ? __('Unpin View') : __('Pin View'),
icon: () => icon: () => h(_view.pinned ? UnpinIcon : PinIcon, { class: 'h-4 w-4' }),
h(view.value.pinned ? UnpinIcon : PinIcon, { class: 'h-4 w-4' }), onClick: () => pinView(_view),
onClick: () => pinView(),
}) })
} }
if (isManager()) { if (isManager()) {
actions[0].items.push({ actions[0].items.push({
label: view.value.public ? __('Make Private') : __('Make Public'), label: _view.public ? __('Make Private') : __('Make Public'),
icon: () => icon: () =>
h(FeatherIcon, { h(FeatherIcon, {
name: view.value.public ? 'lock' : 'unlock', name: _view.public ? 'lock' : 'unlock',
class: 'h-4 w-4', class: 'h-4 w-4',
}), }),
onClick: () => publicView(), onClick: () => publicView(_view),
}) })
} }
@ -926,7 +936,7 @@ const viewActions = computed(() => {
label: __('Delete'), label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => deleteView(close), onClick: (close) => deleteView(_view, close),
}, },
], ],
}), }),
@ -935,63 +945,61 @@ const viewActions = computed(() => {
}) })
} }
return actions return actions
}) }
const viewModalObj = ref({}) const viewModalObj = ref({})
function createView() { function createView() {
view.value.name = '' view.value.name = ''
view.value.label = '' view.value.label = ''
view.value.icon = ''
viewModalObj.value = view.value viewModalObj.value = view.value
viewModalObj.value.mode = 'create'
showViewModal.value = true showViewModal.value = true
} }
function duplicateView() { function duplicateView(v) {
let label = v.label = v.label + __(' (New)')
__( viewModalObj.value = v
getView(route.query.view, route.params.viewType, props.doctype)?.label, viewModalObj.value.mode = 'duplicate'
) || getViewType().label
view.value.name = ''
view.value.label = label + __(' (New)')
viewModalObj.value = view.value
showViewModal.value = true showViewModal.value = true
} }
function editView() { function editView(v) {
let cView = getView(route.query.view, route.params.viewType, props.doctype) viewModalObj.value = v
view.value.name = route.query.view viewModalObj.value.mode = 'edit'
view.value.label = __(cView?.label) || getViewType().label
view.value.icon = cView?.icon || ''
viewModalObj.value = view.value
showViewModal.value = true showViewModal.value = true
} }
function publicView() { function publicView(v) {
call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.public', { call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.public', {
name: route.query.view, name: v.name,
value: !view.value.public, value: !v.public,
}).then(() => { }).then(() => {
view.value.public = !view.value.public v.public = !v.public
reloadView() reloadView()
list.value.reload()
}) })
} }
function pinView() { function pinView(v) {
call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.pin', { call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.pin', {
name: route.query.view, name: v.name,
value: !view.value.pinned, value: !v.pinned,
}).then(() => { }).then(() => {
view.value.pinned = !view.value.pinned v.pinned = !v.pinned
reloadView() reloadView()
list.value.reload()
}) })
} }
function deleteView(close) { function deleteView(v, close) {
call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.delete', { call('crm.fcrm.doctype.crm_view_settings.crm_view_settings.delete', {
name: route.query.view, name: v.name,
}).then(() => { }).then(() => {
router.push({ name: route.name }) router.push({ name: route.name })
reloadView() reloadView()
list.value.reload()
}) })
close() close()
} }
@ -1020,6 +1028,7 @@ function saveView() {
load_default_columns: view.value.load_default_columns, load_default_columns: view.value.load_default_columns,
} }
viewModalObj.value = view.value viewModalObj.value = view.value
viewModalObj.value.mode = 'edit'
showViewModal.value = true showViewModal.value = true
} }

View File

@ -5,9 +5,9 @@
:show="open" :show="open"
:placement="popoverPlacement" :placement="popoverPlacement"
> >
<template #target> <template #target="{ togglePopover }">
<MenuButton as="div"> <MenuButton as="template">
<slot v-if="$slots.default" v-bind="{ open }" /> <slot v-if="$slots.default" v-bind="{ open, togglePopover }" />
<Button v-else :active="open" v-bind="button"> <Button v-else :active="open" v-bind="button">
{{ button ? button?.label || null : 'Options' }} {{ button ? button?.label || null : 'Options' }}
</Button> </Button>
@ -17,13 +17,18 @@
<template #body> <template #body>
<div <div
class="rounded-lg bg-white shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none" class="rounded-lg bg-white shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
:class="{
'mt-2': ['bottom', 'left', 'right'].includes(placement),
'ml-2': placement == 'right-start',
}"
> >
<MenuItems <MenuItems
class="mt-2 min-w-40 divide-y divide-gray-100" class="min-w-40 divide-y divide-gray-100"
:class="{ :class="{
'left-0 origin-top-left': placement == 'left', 'left-0 origin-top-left': placement == 'left',
'right-0 origin-top-right': placement == 'right', 'right-0 origin-top-right': placement == 'right',
'inset-x-0 origin-top': placement == 'center', 'inset-x-0 origin-top': placement == 'center',
'mt-0 origin-top-right': placement == 'right-start',
}" }"
> >
<div v-for="group in groups" :key="group.key" class="p-1.5"> <div v-for="group in groups" :key="group.key" class="p-1.5">
@ -38,34 +43,36 @@
:key="item.label" :key="item.label"
v-slot="{ active }" v-slot="{ active }"
> >
<component <slot name="item" v-bind="{ item, active }">
v-if="item.component"
:is="item.component"
:active="active"
/>
<button
v-else
:class="[
active ? 'bg-gray-100' : 'text-gray-800',
'group flex h-7 w-full items-center rounded px-2 text-base',
]"
@click="item.onClick"
>
<FeatherIcon
v-if="item.icon && typeof item.icon === 'string'"
:name="item.icon"
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
aria-hidden="true"
/>
<component <component
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700" v-if="item.component"
v-else-if="item.icon" :is="item.component"
:is="item.icon" :active="active"
/> />
<span class="whitespace-nowrap"> <button
{{ item.label }} v-else
</span> :class="[
</button> active ? 'bg-gray-100' : 'text-gray-800',
'group flex h-7 w-full items-center rounded px-2 text-base',
]"
@click="item.onClick"
>
<FeatherIcon
v-if="item.icon && typeof item.icon === 'string'"
:name="item.icon"
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
aria-hidden="true"
/>
<component
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
v-else-if="item.icon"
:is="item.icon"
/>
<span class="whitespace-nowrap">
{{ item.label }}
</span>
</button>
</slot>
</MenuItem> </MenuItem>
</div> </div>
</MenuItems> </MenuItems>
@ -130,6 +137,7 @@ const popoverPlacement = computed(() => {
if (props.placement === 'left') return 'bottom-start' if (props.placement === 'left') return 'bottom-start'
if (props.placement === 'right') return 'bottom-end' if (props.placement === 'right') return 'bottom-end'
if (props.placement === 'center') return 'bottom-center' if (props.placement === 'center') return 'bottom-center'
if (props.placement === 'right-start') return 'right-start'
return 'bottom' return 'bottom'
}) })
@ -140,6 +148,7 @@ function normalizeDropdownItem(option) {
} }
return { return {
name: option.name,
label: option.label, label: option.label,
icon: option.icon, icon: option.icon,
group: option.group, group: option.group,