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
nextTick(() => {
_view.value = { ...view.value }
if (_view.value.name) {
if (_view.value.mode === 'edit') {
editMode.value = true
} else if (_view.value.label) {
} else if (_view.value.mode === 'duplicate') {
duplicateMode.value = true
}
})

View File

@ -11,7 +11,7 @@
<template #default="{ open }">
<Button
variant="ghost"
class="text-lg font-medium"
class="text-lg font-medium text-nowrap"
:label="__(viewControls.currentView.label)"
>
<template #prefix>
@ -25,17 +25,62 @@
</template>
</Button>
</template>
</Dropdown>
<Dropdown :options="viewControls.viewActions">
<template #default>
<Button variant="ghost" icon="more-horizontal" />
<template #item="{ item, active }">
<button
:class="[
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>
</Dropdown>
</div>
</template>
<script setup>
import Icon from '@/components/Icon.vue'
import { Dropdown } from 'frappe-ui'
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
const props = defineProps({
routeName: {
@ -45,4 +90,8 @@ const props = defineProps({
})
const viewControls = defineModel()
const isCurrentView = (item) => {
return item.name === viewControls.value.currentView.name
}
</script>

View File

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

View File

@ -5,9 +5,9 @@
:show="open"
:placement="popoverPlacement"
>
<template #target>
<MenuButton as="div">
<slot v-if="$slots.default" v-bind="{ open }" />
<template #target="{ togglePopover }">
<MenuButton as="template">
<slot v-if="$slots.default" v-bind="{ open, togglePopover }" />
<Button v-else :active="open" v-bind="button">
{{ button ? button?.label || null : 'Options' }}
</Button>
@ -17,13 +17,18 @@
<template #body>
<div
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
class="mt-2 min-w-40 divide-y divide-gray-100"
class="min-w-40 divide-y divide-gray-100"
:class="{
'left-0 origin-top-left': placement == 'left',
'right-0 origin-top-right': placement == 'right',
'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">
@ -38,34 +43,36 @@
:key="item.label"
v-slot="{ active }"
>
<component
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"
/>
<slot name="item" v-bind="{ item, active }">
<component
class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
v-else-if="item.icon"
:is="item.icon"
v-if="item.component"
:is="item.component"
:active="active"
/>
<span class="whitespace-nowrap">
{{ item.label }}
</span>
</button>
<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
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>
</div>
</MenuItems>
@ -130,6 +137,7 @@ const popoverPlacement = computed(() => {
if (props.placement === 'left') return 'bottom-start'
if (props.placement === 'right') return 'bottom-end'
if (props.placement === 'center') return 'bottom-center'
if (props.placement === 'right-start') return 'right-start'
return 'bottom'
})
@ -140,6 +148,7 @@ function normalizeDropdownItem(option) {
}
return {
name: option.name,
label: option.label,
icon: option.icon,
group: option.group,