fix: show view action in view selector dropdown itself
This commit is contained in:
parent
d2c2254230
commit
c1882f7280
@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user