Merge pull request #319 from shariquerik/view-selector

fix: Show view selector dropdown in breadcrumb
This commit is contained in:
Shariq Ansari 2024-08-21 20:16:03 +05:30 committed by GitHub
commit 0edde767fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 606 additions and 326 deletions

View File

@ -0,0 +1,21 @@
<template>
<div v-if="isEmoji(icon)" v-bind="$attrs">
{{ icon }}
</div>
<FeatherIcon
v-else-if="typeof icon == 'string'"
:name="icon"
v-bind="$attrs"
/>
<component v-else :is="icon" v-bind="$attrs" />
</template>
<script setup>
import { isEmoji } from '@/utils'
const props = defineProps({
icon: {
type: [String, Object],
required: true,
},
})
</script>

View File

@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.64645 1.89645C1.84171 1.70118 2.15829 1.70118 2.35355 1.89645L4.35355 3.89645C4.54882 4.09171 4.54882 4.40829 4.35355 4.60355L2.35355 6.60355C2.15829 6.79882 1.84171 6.79882 1.64645 6.60355C1.45118 6.40829 1.45118 6.09171 1.64645 5.89645L3.29289 4.25L1.64645 2.60355C1.45118 2.40829 1.45118 2.09171 1.64645 1.89645ZM5.5 3.2002C5.5 2.92405 5.72386 2.7002 6 2.7002H14C14.2761 2.7002 14.5 2.92405 14.5 3.2002C14.5 3.47634 14.2761 3.7002 14 3.7002H6C5.72386 3.7002 5.5 3.47634 5.5 3.2002ZM5.5 8.00024C5.5 7.7241 5.72386 7.50024 6 7.50024H14C14.2761 7.50024 14.5 7.7241 14.5 8.00024C14.5 8.27639 14.2761 8.50024 14 8.50024H6C5.72386 8.50024 5.5 8.27639 5.5 8.00024ZM6 12.3003C5.72386 12.3003 5.5 12.5242 5.5 12.8003C5.5 13.0764 5.72386 13.3003 6 13.3003H14C14.2761 13.3003 14.5 13.0764 14.5 12.8003C14.5 12.5242 14.2761 12.3003 14 12.3003H6ZM2.35355 9.39645C2.15829 9.20118 1.84171 9.20118 1.64645 9.39645C1.45118 9.59171 1.45118 9.90829 1.64645 10.1036L3.29289 11.75L1.64645 13.3964C1.45118 13.5917 1.45118 13.9083 1.64645 14.1036C1.84171 14.2988 2.15829 14.2988 2.35355 14.1036L4.35355 12.1036C4.54882 11.9083 4.54882 11.5917 4.35355 11.3964L2.35355 9.39645Z"
fill="currentColor"
/>
</svg>
</template>

View File

@ -1,18 +1,16 @@
<template> <template>
<svg <svg
xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 16 16"
fill="none" fill="none"
stroke="currentColor" xmlns="http://www.w3.org/2000/svg"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-kanban"
> >
<path d="M6 5v11" /> <path
<path d="M12 5v6" /> fill-rule="evenodd"
<path d="M18 5v14" /> clip-rule="evenodd"
d="M3.69971 3.00098C3.69971 2.72483 3.47585 2.50098 3.19971 2.50098C2.92356 2.50098 2.69971 2.72483 2.69971 3.00098V11.001C2.69971 11.2771 2.92356 11.501 3.19971 11.501C3.47585 11.501 3.69971 11.2771 3.69971 11.001V3.00098ZM8 2.50098C8.27614 2.50098 8.5 2.72483 8.5 3.00098V13.001C8.5 13.2771 8.27614 13.501 8 13.501C7.72386 13.501 7.5 13.2771 7.5 13.001V3.00098C7.5 2.72483 7.72386 2.50098 8 2.50098ZM12.7998 2.50098C13.0759 2.50098 13.2998 2.72483 13.2998 3.00098V8.00098C13.2998 8.27712 13.0759 8.50098 12.7998 8.50098C12.5237 8.50098 12.2998 8.27712 12.2998 8.00098V3.00098C12.2998 2.72483 12.5237 2.50098 12.7998 2.50098Z"
fill="currentColor"
/>
</svg> </svg>
</template> </template>

View File

@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.5 3.2002C2.5 2.92405 2.72386 2.7002 3 2.7002H13C13.2761 2.7002 13.5 2.92405 13.5 3.2002C13.5 3.47634 13.2761 3.7002 13 3.7002H3C2.72386 3.7002 2.5 3.47634 2.5 3.2002ZM2.5 8.00024C2.5 7.7241 2.72386 7.50024 3 7.50024H13C13.2761 7.50024 13.5 7.7241 13.5 8.00024C13.5 8.27639 13.2761 8.50024 13 8.50024H3C2.72386 8.50024 2.5 8.27639 2.5 8.00024ZM3 12.3003C2.72386 12.3003 2.5 12.5242 2.5 12.8003C2.5 13.0764 2.72386 13.3003 3 13.3003H13C13.2761 13.3003 13.5 13.0764 13.5 12.8003C13.5 12.5242 13.2761 12.3003 13 12.3003H3Z"
fill="currentColor"
/>
</svg>
</template>

View File

@ -1,7 +1,7 @@
<template> <template>
<Teleport to="#app-header" v-if="showHeader"> <Teleport to="#app-header" v-if="showHeader">
<slot> <slot>
<header class="flex h-12 items-center justify-between py-2.5 pl-5"> <header class="flex h-10.5 items-center justify-between py-[7px] pl-5">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<slot name="left-header" /> <slot name="left-header" />
</div> </div>

View File

@ -7,6 +7,7 @@
getRowRoute: (row) => ({ getRowRoute: (row) => ({
name: 'Contact', name: 'Contact',
params: { contactId: row.name }, params: { contactId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}), }),
selectable: options.selectable, selectable: options.selectable,
showTooltip: options.showTooltip, showTooltip: options.showTooltip,
@ -174,6 +175,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -205,6 +207,8 @@ const emit = defineEmits([
'likeDoc', 'likeDoc',
]) ])
const route = useRoute()
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
@ -230,7 +234,7 @@ const listBulkActionsRef = ref(null)
defineExpose({ defineExpose({
customListActions: computed( customListActions: computed(
() => listBulkActionsRef.value?.customListActions () => listBulkActionsRef.value?.customListActions,
), ),
}) })
</script> </script>

View File

@ -4,14 +4,21 @@
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
:options="{ :options="{
getRowRoute: (row) => ({ name: 'Deal', params: { dealId: row.name } }), getRowRoute: (row) => ({
name: 'Deal',
params: { dealId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}),
selectable: options.selectable, selectable: options.selectable,
showTooltip: options.showTooltip, showTooltip: options.showTooltip,
resizeColumn: options.resizeColumn, resizeColumn: options.resizeColumn,
}" }"
row-key="name" row-key="name"
> >
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')"> <ListHeader
class="sm:mx-5 mx-3"
@columnWidthUpdated="emit('columnWidthUpdated')"
>
<ListHeaderItem <ListHeaderItem
v-for="column in columns" v-for="column in columns"
:key="column.key" :key="column.key"
@ -204,6 +211,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -235,6 +243,8 @@ const emit = defineEmits([
'likeDoc', 'likeDoc',
]) ])
const route = useRoute()
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
@ -260,7 +270,7 @@ const listBulkActionsRef = ref(null)
defineExpose({ defineExpose({
customListActions: computed( customListActions: computed(
() => listBulkActionsRef.value?.customListActions () => listBulkActionsRef.value?.customListActions,
), ),
}) })
</script> </script>

View File

@ -4,14 +4,21 @@
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
:options="{ :options="{
getRowRoute: (row) => ({ name: 'Lead', params: { leadId: row.name } }), getRowRoute: (row) => ({
name: 'Lead',
params: { leadId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}),
selectable: options.selectable, selectable: options.selectable,
showTooltip: options.showTooltip, showTooltip: options.showTooltip,
resizeColumn: options.resizeColumn, resizeColumn: options.resizeColumn,
}" }"
row-key="name" row-key="name"
> >
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')"> <ListHeader
class="sm:mx-5 mx-3"
@columnWidthUpdated="emit('columnWidthUpdated')"
>
<ListHeaderItem <ListHeaderItem
v-for="column in columns" v-for="column in columns"
:key="column.key" :key="column.key"
@ -217,6 +224,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -248,6 +256,8 @@ const emit = defineEmits([
'likeDoc', 'likeDoc',
]) ])
const route = useRoute()
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')
@ -273,7 +283,7 @@ const listBulkActionsRef = ref(null)
defineExpose({ defineExpose({
customListActions: computed( customListActions: computed(
() => listBulkActionsRef.value?.customListActions () => listBulkActionsRef.value?.customListActions,
), ),
}) })
</script> </script>

View File

@ -6,6 +6,7 @@
getRowRoute: (row) => ({ getRowRoute: (row) => ({
name: 'Organization', name: 'Organization',
params: { organizationId: row.name }, params: { organizationId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}), }),
selectable: options.selectable, selectable: options.selectable,
showTooltip: options.showTooltip, showTooltip: options.showTooltip,
@ -156,6 +157,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
const props = defineProps({ const props = defineProps({
rows: { rows: {
@ -187,6 +189,8 @@ const emit = defineEmits([
'likeDoc', 'likeDoc',
]) ])
const route = useRoute()
const pageLengthCount = defineModel() const pageLengthCount = defineModel()
const list = defineModel('list') const list = defineModel('list')

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

@ -0,0 +1,97 @@
<template>
<div class="flex items-center">
<router-link
:to="{ name: routeName }"
class="px-0.5 py-1 text-lg font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 text-gray-600 hover:text-gray-700"
>
{{ __(routeName) }}
</router-link>
<span class="mx-0.5 text-base text-gray-500" aria-hidden="true"> / </span>
<Dropdown v-if="viewControls" :options="viewControls.viewsDropdownOptions">
<template #default="{ open }">
<Button
variant="ghost"
class="text-lg font-medium text-nowrap"
:label="__(viewControls.currentView.label)"
>
<template #prefix>
<Icon :icon="viewControls.currentView.icon" class="h-4" />
</template>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 text-gray-800"
/>
</template>
</Button>
</template>
<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 '@/components/frappe-ui/Dropdown.vue'
const props = defineProps({
routeName: {
type: String,
required: true,
},
})
const viewControls = defineModel()
const isCurrentView = (item) => {
return item.name === viewControls.value.currentView.name
}
</script>

View File

@ -3,43 +3,6 @@
v-if="isMobileView" v-if="isMobileView"
class="flex flex-col justify-between gap-2 sm:px-5 px-3 py-4" class="flex flex-col justify-between gap-2 sm:px-5 px-3 py-4"
> >
<div class="flex items-center justify-between gap-2 overflow-x-auto">
<div class="flex gap-2">
<Dropdown :options="viewsDropdownOptions">
<template #default="{ open }">
<Button :label="__(currentView.label)">
<template #prefix>
<div v-if="isEmoji(currentView.icon)">
{{ currentView.icon }}
</div>
<FeatherIcon
v-else-if="typeof currentView.icon == 'string'"
:name="currentView.icon"
class="h-4"
/>
<component v-else :is="currentView.icon" class="h-4" />
</template>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 text-gray-600"
/>
</template>
</Button>
</template>
</Dropdown>
<Dropdown :options="viewActions">
<template #default>
<Button icon="more-horizontal" />
</template>
</Dropdown>
</div>
<Button :label="__('Refresh')" @click="reload()" :loading="isLoading">
<template #icon>
<RefreshIcon class="h-4 w-4" />
</template>
</Button>
</div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex items-center justify-between gap-2 overflow-x-auto"> <div class="flex items-center justify-between gap-2 overflow-x-auto">
<div class="flex gap-2"> <div class="flex gap-2">
@ -59,6 +22,11 @@
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<Button :label="__('Refresh')" @click="reload()" :loading="isLoading">
<template #icon>
<RefreshIcon class="h-4 w-4" />
</template>
</Button>
<SortBy <SortBy
v-if="route.params.viewType !== 'kanban'" v-if="route.params.viewType !== 'kanban'"
v-model="list" v-model="list"
@ -91,37 +59,8 @@
</div> </div>
</div> </div>
<div v-else class="flex items-center justify-between gap-2 px-5 py-4"> <div v-else class="flex items-center justify-between gap-2 px-5 py-4">
<div class="flex items-center gap-2">
<Dropdown :options="viewsDropdownOptions">
<template #default="{ open }">
<Button :label="__(currentView.label)">
<template #prefix>
<div v-if="isEmoji(currentView.icon)">{{ currentView.icon }}</div>
<FeatherIcon
v-else-if="typeof currentView.icon == 'string'"
:name="currentView.icon"
class="h-4"
/>
<component v-else :is="currentView.icon" class="h-4" />
</template>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 text-gray-600"
/>
</template>
</Button>
</template>
</Dropdown>
<Dropdown :options="viewActions">
<template #default>
<Button icon="more-horizontal" />
</template>
</Dropdown>
</div>
<div class="-mr-2 h-[70%] border-l" />
<FadedScrollableDiv <FadedScrollableDiv
class="flex flex-1 items-center overflow-x-auto px-1" class="flex flex-1 items-center overflow-x-auto -ml-1"
orientation="horizontal" orientation="horizontal"
> >
<div <div
@ -223,6 +162,7 @@
afterUpdate: () => { afterUpdate: () => {
viewUpdated = false viewUpdated = false
reloadView() reloadView()
list.reload()
}, },
}" }"
/> />
@ -268,8 +208,9 @@
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import DetailsIcon from '@/components/Icons/DetailsIcon.vue' import ListIcon from '@/components/Icons/ListIcon.vue'
import KanbanIcon from '@/components/Icons/KanbanIcon.vue' import KanbanIcon from '@/components/Icons/KanbanIcon.vue'
import GroupByIcon from '@/components/Icons/GroupByIcon.vue'
import QuickFilterField from '@/components/QuickFilterField.vue' import QuickFilterField from '@/components/QuickFilterField.vue'
import RefreshIcon from '@/components/Icons/RefreshIcon.vue' import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
@ -340,15 +281,18 @@ function getViewType() {
let viewType = route.params.viewType || 'list' let viewType = route.params.viewType || 'list'
let types = { let types = {
list: { list: {
label: __('List View'), name: 'list',
icon: 'list', label: __('List'),
icon: markRaw(ListIcon),
}, },
group_by: { group_by: {
label: __('Group By View'), name: 'group_by',
icon: markRaw(DetailsIcon), label: __('Group By'),
icon: markRaw(GroupByIcon),
}, },
kanban: { kanban: {
label: __('Kanban View'), name: 'kanban',
label: __('Kanban'),
icon: markRaw(KanbanIcon), icon: markRaw(KanbanIcon),
}, },
} }
@ -359,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,
@ -531,27 +476,19 @@ let allowedViews = props.options.allowedViews || ['list']
if (allowedViews.includes('list')) { if (allowedViews.includes('list')) {
defaultViews.push({ defaultViews.push({
label: __(props.options?.defaultViewName) || __('List View'), name: 'list',
icon: 'list', label: __(props.options?.defaultViewName) || __('List'),
icon: markRaw(ListIcon),
onClick() { onClick() {
viewUpdated.value = false viewUpdated.value = false
router.push({ name: route.name }) router.push({ name: route.name })
}, },
}) })
} }
if (allowedViews.includes('group_by')) {
defaultViews.push({
label: __(props.options?.defaultViewName) || __('Group By View'),
icon: markRaw(DetailsIcon),
onClick() {
viewUpdated.value = false
router.push({ name: route.name, params: { viewType: 'group_by' } })
},
})
}
if (allowedViews.includes('kanban')) { if (allowedViews.includes('kanban')) {
defaultViews.push({ defaultViews.push({
label: __(props.options?.defaultViewName) || __('Kanban View'), name: 'kanban',
label: __(props.options?.defaultViewName) || __('Kanban'),
icon: markRaw(KanbanIcon), icon: markRaw(KanbanIcon),
onClick() { onClick() {
viewUpdated.value = false viewUpdated.value = false
@ -559,14 +496,27 @@ if (allowedViews.includes('kanban')) {
}, },
}) })
} }
if (allowedViews.includes('group_by')) {
defaultViews.push({
name: 'group_by',
label: __(props.options?.defaultViewName) || __('Group By'),
icon: markRaw(GroupByIcon),
onClick() {
viewUpdated.value = false
router.push({ name: route.name, params: { viewType: 'group_by' } })
},
})
}
function getIcon(icon, type) { function getIcon(icon, type) {
if (isEmoji(icon)) { if (isEmoji(icon)) {
return h('div', icon) return h('div', icon)
} else if (!icon && type === 'group_by') { } else if (!icon && type === 'group_by') {
return markRaw(DetailsIcon) return markRaw(GroupByIcon)
} else if (!icon && type === 'kanban') {
return markRaw(KanbanIcon)
} }
return icon || 'list' return icon || markRaw(ListIcon)
} }
const viewsDropdownOptions = computed(() => { const viewsDropdownOptions = computed(() => {
@ -580,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)
@ -602,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'),
@ -620,6 +570,18 @@ const viewsDropdownOptions = computed(() => {
}) })
} }
_views.push({
group: __('Actions'),
hideLabel: true,
items: [
{
label: __('Create View'),
icon: 'plus',
onClick: () => createView(),
},
],
})
return _views return _views
}) })
@ -912,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'),
@ -921,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),
}) })
} }
@ -965,14 +929,16 @@ const viewActions = computed(() => {
onClick: () => onClick: () =>
$dialog({ $dialog({
title: __('Delete View'), title: __('Delete View'),
message: __('Are you sure you want to delete this view?'), message: __('Are you sure you want to delete "{0}" view?', [
_view.label,
]),
variant: 'danger', variant: 'danger',
actions: [ actions: [
{ {
label: __('Delete'), label: __('Delete'),
variant: 'solid', variant: 'solid',
theme: 'red', theme: 'red',
onClick: (close) => deleteView(close), onClick: (close) => deleteView(_view, close),
}, },
], ],
}), }),
@ -981,56 +947,61 @@ const viewActions = computed(() => {
}) })
} }
return actions return actions
}) }
const viewModalObj = ref({}) const viewModalObj = ref({})
function duplicateView() { function createView() {
let label =
__(
getView(route.query.view, route.params.viewType, props.doctype)?.label,
) || getViewType().label
view.value.name = '' view.value.name = ''
view.value.label = label + __(' (New)') 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 editView() { function duplicateView(v) {
let cView = getView(route.query.view, route.params.viewType, props.doctype) v.label = v.label + __(' (New)')
view.value.name = route.query.view viewModalObj.value = v
view.value.label = __(cView?.label) || getViewType().label viewModalObj.value.mode = 'duplicate'
view.value.icon = cView?.icon || ''
viewModalObj.value = view.value
showViewModal.value = true showViewModal.value = true
} }
function publicView() { function editView(v) {
viewModalObj.value = v
viewModalObj.value.mode = 'edit'
showViewModal.value = true
}
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()
} }
@ -1059,6 +1030,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
} }
@ -1119,6 +1091,9 @@ defineExpose({
likeDoc, likeDoc,
updateKanbanSettings, updateKanbanSettings,
loadMoreKanban, loadMoreKanban,
viewActions,
viewsDropdownOptions,
currentView,
}) })
// Watchers // Watchers

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,

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Call Logs" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -54,6 +54,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
@ -61,11 +62,8 @@ import ViewControls from '@/components/ViewControls.vue'
import CallLogsListView from '@/components/ListViews/CallLogsListView.vue' import CallLogsListView from '@/components/ListViews/CallLogsListView.vue'
import CallLogModal from '@/components/Modals/CallLogModal.vue' import CallLogModal from '@/components/Modals/CallLogModal.vue'
import { getCallLogDetail } from '@/utils/callLog' import { getCallLogDetail } from '@/utils/callLog'
import { Breadcrumbs } from 'frappe-ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
const callLogsListView = ref(null) const callLogsListView = ref(null)
// callLogs data is loaded in the ViewControls component // callLogs data is loaded in the ViewControls component

View File

@ -1,7 +1,11 @@
<template> <template>
<LayoutHeader v-if="contact.data"> <LayoutHeader v-if="contact.data">
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }">
<Icon v-if="item.icon" :icon="item.icon" class="mr-2 h-4" />
</template>
</Breadcrumbs>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div v-if="contact.data" class="flex h-full flex-col overflow-hidden"> <div v-if="contact.data" class="flex h-full flex-col overflow-hidden">
@ -216,16 +220,7 @@
</template> </template>
<script setup> <script setup>
import { import Icon from '@/components/Icon.vue'
Breadcrumbs,
Avatar,
FileUploader,
Tooltip,
Tabs,
call,
createResource,
usePageMeta,
} from 'frappe-ui'
import Dropdown from '@/components/frappe-ui/Dropdown.vue' import Dropdown from '@/components/frappe-ui/Dropdown.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue' import Email2Icon from '@/components/Icons/Email2Icon.vue'
@ -242,13 +237,24 @@ import {
timeAgo, timeAgo,
formatNumberIntoCurrency, formatNumberIntoCurrency,
} from '@/utils' } from '@/utils'
import { getView } from '@/utils/view'
import { globalStore } from '@/stores/global.js' import { globalStore } from '@/stores/global.js'
import { usersStore } from '@/stores/users.js' import { usersStore } from '@/stores/users.js'
import { organizationsStore } from '@/stores/organizations.js' import { organizationsStore } from '@/stores/organizations.js'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { callEnabled } from '@/composables/settings' import { callEnabled } from '@/composables/settings'
import {
Breadcrumbs,
Avatar,
FileUploader,
Tooltip,
Tabs,
call,
createResource,
usePageMeta,
} from 'frappe-ui'
import { ref, computed, h } from 'vue' import { ref, computed, h } from 'vue'
import { useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
const { $dialog, makeCall } = globalStore() const { $dialog, makeCall } = globalStore()
@ -263,6 +269,7 @@ const props = defineProps({
}, },
}) })
const route = useRoute()
const router = useRouter() const router = useRouter()
const showContactModal = ref(false) const showContactModal = ref(false)
@ -287,6 +294,22 @@ const contact = createResource({
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }] let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
if (route.query.view || route.query.viewType) {
let view = getView(route.query.view, route.query.viewType, 'Contact')
if (view) {
items.push({
label: __(view.label),
icon: view.icon,
route: {
name: 'Contacts',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
})
}
}
items.push({ items.push({
label: contact.data?.full_name, label: contact.data?.full_name,
route: { name: 'Contact', params: { contactId: props.contactId } }, route: { name: 'Contact', params: { contactId: props.contactId } },
@ -300,7 +323,6 @@ usePageMeta(() => {
} }
}) })
function validateFile(file) { function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase() let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) { if (!['png', 'jpg', 'jpeg'].includes(extn)) {

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Contacts" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -72,6 +72,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import ContactsIcon from '@/components/Icons/ContactsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
@ -79,37 +80,15 @@ import ContactModal from '@/components/Modals/ContactModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue' import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import ContactsListView from '@/components/ListViews/ContactsListView.vue' import ContactsListView from '@/components/ListViews/ContactsListView.vue'
import ViewControls from '@/components/ViewControls.vue' import ViewControls from '@/components/ViewControls.vue'
import { Breadcrumbs } from 'frappe-ui'
import { organizationsStore } from '@/stores/organizations.js' import { organizationsStore } from '@/stores/organizations.js'
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils' import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
const { getOrganization } = organizationsStore() const { getOrganization } = organizationsStore()
const route = useRoute()
const showContactModal = ref(false) const showContactModal = ref(false)
const showQuickEntryModal = ref(false) const showQuickEntryModal = ref(false)
const currentContact = computed(() => {
return contacts.value?.data?.data?.find(
(contact) => contact.name === route.params.contactId,
)
})
const breadcrumbs = computed(() => {
let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
if (!currentContact.value) return items
items.push({
label: __(currentContact.value.full_name),
route: {
name: 'Contact',
params: { contactId: currentContact.value.name },
},
})
return items
})
const contactsListView = ref(null) const contactsListView = ref(null)
// contacts data is loaded in the ViewControls component // contacts data is loaded in the ViewControls component

View File

@ -1,7 +1,11 @@
<template> <template>
<LayoutHeader v-if="deal.data"> <LayoutHeader v-if="deal.data">
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }">
<Icon v-if="item.icon" :icon="item.icon" class="mr-2 h-4" />
</template>
</Breadcrumbs>
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -302,6 +306,7 @@
/> />
</template> </template>
<script setup> <script setup>
import Icon from '@/components/Icon.vue'
import Resizer from '@/components/Resizer.vue' import Resizer from '@/components/Resizer.vue'
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue' import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
@ -337,6 +342,7 @@ import {
errorMessage, errorMessage,
copyToClipboard, copyToClipboard,
} from '@/utils' } from '@/utils'
import { getView } from '@/utils/view'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { organizationsStore } from '@/stores/organizations' import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
@ -353,12 +359,13 @@ import {
usePageMeta, usePageMeta,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed, h, onMounted } from 'vue' import { ref, computed, h, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
const { $dialog, makeCall } = globalStore() const { $dialog, makeCall } = globalStore()
const { organizations, getOrganization } = organizationsStore() const { organizations, getOrganization } = organizationsStore()
const { statusOptions, getDealStatus } = statusesStore() const { statusOptions, getDealStatus } = statusesStore()
const { isManager } = usersStore() const { isManager } = usersStore()
const route = useRoute()
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@ -452,6 +459,22 @@ function validateRequired(fieldname, value) {
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Deals'), route: { name: 'Deals' } }] let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
if (route.query.view || route.query.viewType) {
let view = getView(route.query.view, route.query.viewType, 'CRM Deal')
if (view) {
items.push({
label: __(view.label),
icon: view.icon,
route: {
name: 'Deals',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
})
}
}
items.push({ items.push({
label: organization.value?.name || __('Untitled'), label: organization.value?.name || __('Untitled'),
route: { name: 'Deal', params: { dealId: deal.data.name } }, route: { name: 'Deal', params: { dealId: deal.data.name } },

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Deals" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -32,7 +32,11 @@
v-if="route.params.viewType == 'kanban'" v-if="route.params.viewType == 'kanban'"
v-model="deals" v-model="deals"
:options="{ :options="{
getRoute: (row) => ({ name: 'Deal', params: { dealId: row.name } }), getRoute: (row) => ({
name: 'Deal',
params: { dealId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}),
onNewClick: (column) => onNewClick(column), onNewClick: (column) => onNewClick(column),
}" }"
@update="(data) => viewControls.updateKanbanSettings(data)" @update="(data) => viewControls.updateKanbanSettings(data)"
@ -259,6 +263,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue' import MultipleAvatar from '@/components/MultipleAvatar.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue' import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue'
@ -288,12 +293,10 @@ import {
formatNumberIntoCurrency, formatNumberIntoCurrency,
formatTime, formatTime,
} from '@/utils' } from '@/utils'
import { Breadcrumbs, Tooltip, Avatar, Dropdown } from 'frappe-ui' import { Tooltip, Avatar, Dropdown } from 'frappe-ui'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ref, reactive, computed, h } from 'vue' import { ref, reactive, computed, h } from 'vue'
const breadcrumbs = [{ label: __('Deals'), route: { name: 'Deals' } }]
const { makeCall } = globalStore() const { makeCall } = globalStore()
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore() const { getOrganization } = organizationsStore()

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Email Templates" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -68,6 +68,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue' import Email2Icon from '@/components/Icons/Email2Icon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
@ -75,13 +76,8 @@ import ViewControls from '@/components/ViewControls.vue'
import EmailTemplatesListView from '@/components/ListViews/EmailTemplatesListView.vue' import EmailTemplatesListView from '@/components/ListViews/EmailTemplatesListView.vue'
import EmailTemplateModal from '@/components/Modals/EmailTemplateModal.vue' import EmailTemplateModal from '@/components/Modals/EmailTemplateModal.vue'
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils' import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
import { Breadcrumbs } from 'frappe-ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
const breadcrumbs = [
{ label: __('Email Templates'), route: { name: 'Email Templates' } },
]
const emailTemplatesListView = ref(null) const emailTemplatesListView = ref(null)
// emailTemplates data is loaded in the ViewControls component // emailTemplates data is loaded in the ViewControls component

View File

@ -1,7 +1,11 @@
<template> <template>
<LayoutHeader v-if="lead.data"> <LayoutHeader v-if="lead.data">
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }">
<Icon v-if="item.icon" :icon="item.icon" class="mr-2 h-4" />
</template>
</Breadcrumbs>
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -273,6 +277,7 @@
/> />
</template> </template>
<script setup> <script setup>
import Icon from '@/components/Icon.vue'
import Resizer from '@/components/Resizer.vue' import Resizer from '@/components/Resizer.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
import ActivityIcon from '@/components/Icons/ActivityIcon.vue' import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
@ -306,6 +311,7 @@ import {
errorMessage, errorMessage,
copyToClipboard, copyToClipboard,
} from '@/utils' } from '@/utils'
import { getView } from '@/utils/view'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { contactsStore } from '@/stores/contacts' import { contactsStore } from '@/stores/contacts'
import { organizationsStore } from '@/stores/organizations' import { organizationsStore } from '@/stores/organizations'
@ -421,6 +427,22 @@ function validateRequired(fieldname, value) {
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Leads'), route: { name: 'Leads' } }] let items = [{ label: __('Leads'), route: { name: 'Leads' } }]
if (route.query.view || route.query.viewType) {
let view = getView(route.query.view, route.query.viewType, 'CRM Lead')
if (view) {
items.push({
label: __(view.label),
icon: view.icon,
route: {
name: 'Leads',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
})
}
}
items.push({ items.push({
label: lead.data.lead_name || __('Untitled'), label: lead.data.lead_name || __('Untitled'),
route: { name: 'Lead', params: { leadId: lead.data.name } }, route: { name: 'Lead', params: { leadId: lead.data.name } },

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Leads" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -33,7 +33,11 @@
v-if="route.params.viewType == 'kanban'" v-if="route.params.viewType == 'kanban'"
v-model="leads" v-model="leads"
:options="{ :options="{
getRoute: (row) => ({ name: 'Lead', params: { leadId: row.name } }), getRoute: (row) => ({
name: 'Lead',
params: { leadId: row.name },
query: { view: route.query.view, viewType: route.params.viewType },
}),
onNewClick: (column) => onNewClick(column), onNewClick: (column) => onNewClick(column),
}" }"
@update="(data) => viewControls.updateKanbanSettings(data)" @update="(data) => viewControls.updateKanbanSettings(data)"
@ -281,6 +285,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue' import MultipleAvatar from '@/components/MultipleAvatar.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue' import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue'
@ -304,12 +309,10 @@ import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { callEnabled } from '@/composables/settings' import { callEnabled } from '@/composables/settings'
import { dateFormat, dateTooltipFormat, timeAgo, formatTime } from '@/utils' import { dateFormat, dateTooltipFormat, timeAgo, formatTime } from '@/utils'
import { Breadcrumbs, Avatar, Tooltip, Dropdown } from 'frappe-ui' import { Avatar, Tooltip, Dropdown } from 'frappe-ui'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ref, computed, reactive, h } from 'vue' import { ref, computed, reactive, h } from 'vue'
const breadcrumbs = [{ label: __('Leads'), route: { name: 'Leads' } }]
const { makeCall } = globalStore() const { makeCall } = globalStore()
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore() const { getOrganization } = organizationsStore()

View File

@ -3,7 +3,11 @@
<header <header
class="relative flex h-12 items-center justify-between gap-2 py-2.5 pl-5" class="relative flex h-12 items-center justify-between gap-2 py-2.5 pl-5"
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }">
<Icon v-if="item.icon" :icon="item.icon" class="mr-2 h-4" />
</template>
</Breadcrumbs>
<div class="absolute right-0"> <div class="absolute right-0">
<Dropdown :options="statusOptions('deal', updateField)"> <Dropdown :options="statusOptions('deal', updateField)">
<template #default="{ open }"> <template #default="{ open }">
@ -245,6 +249,7 @@
/> />
</template> </template>
<script setup> <script setup>
import Icon from '@/components/Icon.vue'
import DetailsIcon from '@/components/Icons/DetailsIcon.vue' import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue' import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
import ActivityIcon from '@/components/Icons/ActivityIcon.vue' import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
@ -269,16 +274,16 @@ import Section from '@/components/Section.vue'
import SectionFields from '@/components/SectionFields.vue' import SectionFields from '@/components/SectionFields.vue'
import SLASection from '@/components/SLASection.vue' import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import { import { createToast, setupAssignees, setupCustomActions } from '@/utils'
createToast, import { getView } from '@/utils/view'
setupAssignees,
setupCustomActions,
errorMessage,
} from '@/utils'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { organizationsStore } from '@/stores/organizations' import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { whatsappEnabled, callEnabled, isMobileView } from '@/composables/settings' import {
whatsappEnabled,
callEnabled,
isMobileView,
} from '@/composables/settings'
import { import {
createResource, createResource,
Dropdown, Dropdown,
@ -288,11 +293,12 @@ import {
call, call,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed, h, onMounted } from 'vue' import { ref, computed, h, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
const { $dialog, makeCall } = globalStore() const { $dialog } = globalStore()
const { organizations, getOrganization } = organizationsStore() const { organizations, getOrganization } = organizationsStore()
const { statusOptions, getDealStatus } = statusesStore() const { statusOptions, getDealStatus } = statusesStore()
const route = useRoute()
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@ -385,6 +391,22 @@ function validateRequired(fieldname, value) {
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Deals'), route: { name: 'Deals' } }] let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
if (route.query.view || route.query.viewType) {
let view = getView(route.query.view, route.query.viewType, 'CRM Deal')
if (view) {
items.push({
label: __(view.label),
icon: view.icon,
route: {
name: 'Deals',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
})
}
}
items.push({ items.push({
label: organization.value?.name || __('Untitled'), label: organization.value?.name || __('Untitled'),
route: { name: 'Deal', params: { dealId: deal.data.name } }, route: { name: 'Deal', params: { dealId: deal.data.name } },

View File

@ -3,7 +3,11 @@
<header <header
class="relative flex h-12 items-center justify-between gap-2 py-2.5 pl-5" class="relative flex h-12 items-center justify-between gap-2 py-2.5 pl-5"
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }">
<Icon v-if="item.icon" :icon="item.icon" class="mr-2 h-4" />
</template>
</Breadcrumbs>
<div class="absolute right-0"> <div class="absolute right-0">
<Dropdown :options="statusOptions('lead', updateField)"> <Dropdown :options="statusOptions('lead', updateField)">
<template #default="{ open }"> <template #default="{ open }">
@ -138,7 +142,7 @@
<div v-else class="mt-2.5 text-base"> <div v-else class="mt-2.5 text-base">
{{ {{
__( __(
'New organization will be created based on the data in details section' 'New organization will be created based on the data in details section',
) )
}} }}
</div> </div>
@ -170,6 +174,7 @@
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import Icon from '@/components/Icon.vue'
import DetailsIcon from '@/components/Icons/DetailsIcon.vue' import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
import ActivityIcon from '@/components/Icons/ActivityIcon.vue' import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
import EmailIcon from '@/components/Icons/EmailIcon.vue' import EmailIcon from '@/components/Icons/EmailIcon.vue'
@ -191,11 +196,16 @@ import SectionFields from '@/components/SectionFields.vue'
import SLASection from '@/components/SLASection.vue' import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import { createToast, setupAssignees, setupCustomActions } from '@/utils' import { createToast, setupAssignees, setupCustomActions } from '@/utils'
import { getView } from '@/utils/view'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { contactsStore } from '@/stores/contacts' import { contactsStore } from '@/stores/contacts'
import { organizationsStore } from '@/stores/organizations' import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { whatsappEnabled, callEnabled, isMobileView } from '@/composables/settings' import {
whatsappEnabled,
callEnabled,
isMobileView,
} from '@/composables/settings'
import { import {
createResource, createResource,
Dropdown, Dropdown,
@ -298,6 +308,22 @@ function validateRequired(fieldname, value) {
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Leads'), route: { name: 'Leads' } }] let items = [{ label: __('Leads'), route: { name: 'Leads' } }]
if (route.query.view || route.query.viewType) {
let view = getView(route.query.view, route.query.viewType, 'CRM Lead')
if (view) {
items.push({
label: __(view.label),
icon: view.icon,
route: {
name: 'Leads',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
})
}
}
items.push({ items.push({
label: lead.data.lead_name || __('Untitled'), label: lead.data.lead_name || __('Untitled'),
route: { name: 'Lead', params: { leadId: lead.data.name } }, route: { name: 'Lead', params: { leadId: lead.data.name } },
@ -359,7 +385,7 @@ const tabs = computed(() => {
watch(tabs, (value) => { watch(tabs, (value) => {
if (value && route.params.tabName) { if (value && route.params.tabName) {
let index = value.findIndex( let index = value.findIndex(
(tab) => tab.name.toLowerCase() === route.params.tabName.toLowerCase() (tab) => tab.name.toLowerCase() === route.params.tabName.toLowerCase(),
) )
if (index !== -1) { if (index !== -1) {
tabIndex.value = index tabIndex.value = index
@ -447,7 +473,7 @@ async function convertToDeal(updated) {
organization: lead.data.organization, organization: lead.data.organization,
}, },
'', '',
() => convertToDeal(true) () => convertToDeal(true),
) )
showConvertToDealModal.value = false showConvertToDealModal.value = false
} else { } else {
@ -455,7 +481,7 @@ async function convertToDeal(updated) {
'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', 'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal',
{ {
lead: lead.data.name, lead: lead.data.name,
} },
) )
if (deal) { if (deal) {
if (updated) { if (updated) {

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Notes" />
</template> </template>
<template #right-header> <template #right-header>
<Button variant="solid" :label="__('Create')" @click="createNote"> <Button variant="solid" :label="__('Create')" @click="createNote">
@ -10,6 +10,7 @@
</template> </template>
</LayoutHeader> </LayoutHeader>
<ViewControls <ViewControls
ref="viewControls"
v-model="notes" v-model="notes"
v-model:loadMore="loadMore" v-model:loadMore="loadMore"
v-model:updatedPageCount="updatedPageCount" v-model:updatedPageCount="updatedPageCount"
@ -102,6 +103,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue'
@ -109,33 +111,25 @@ import NoteModal from '@/components/Modals/NoteModal.vue'
import ViewControls from '@/components/ViewControls.vue' import ViewControls from '@/components/ViewControls.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils' import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
import { import { TextEditor, call, Dropdown, Tooltip, ListFooter } from 'frappe-ui'
TextEditor,
call,
Dropdown,
Tooltip,
Breadcrumbs,
ListFooter,
} from 'frappe-ui'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
const { getUser } = usersStore() const { getUser } = usersStore()
const breadcrumbs = [{ label: __('Notes'), route: { name: 'Notes' } }]
const showNoteModal = ref(false) const showNoteModal = ref(false)
const currentNote = ref(null) const currentNote = ref(null)
const notes = ref({}) const notes = ref({})
const loadMore = ref(1) const loadMore = ref(1)
const updatedPageCount = ref(20) const updatedPageCount = ref(20)
const viewControls = ref(null)
watch( watch(
() => notes.value?.data?.page_length_count, () => notes.value?.data?.page_length_count,
(val, old_value) => { (val, old_value) => {
if (!val || val === old_value) return if (!val || val === old_value) return
updatedPageCount.value = val updatedPageCount.value = val
} },
) )
function createNote() { function createNote() {

View File

@ -1,7 +1,11 @@
<template> <template>
<LayoutHeader v-if="organization.doc"> <LayoutHeader v-if="organization.doc">
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }">
<Icon v-if="item.icon" :icon="item.icon" class="mr-2 h-4" />
</template>
</Breadcrumbs>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div v-if="organization.doc" class="flex flex-1 flex-col overflow-hidden"> <div v-if="organization.doc" class="flex flex-1 flex-col overflow-hidden">
@ -226,17 +230,7 @@
</template> </template>
<script setup> <script setup>
import { import Icon from '@/components/Icon.vue'
Breadcrumbs,
Avatar,
FileUploader,
Dropdown,
Tabs,
call,
createListResource,
createDocumentResource,
usePageMeta,
} from 'frappe-ui'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue' import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue' import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
@ -252,14 +246,26 @@ import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { getView } from '@/utils/view'
import { import {
dateFormat, dateFormat,
dateTooltipFormat, dateTooltipFormat,
timeAgo, timeAgo,
formatNumberIntoCurrency, formatNumberIntoCurrency,
} from '@/utils' } from '@/utils'
import {
Breadcrumbs,
Avatar,
FileUploader,
Dropdown,
Tabs,
call,
createListResource,
createDocumentResource,
usePageMeta,
} from 'frappe-ui'
import { h, computed, ref } from 'vue' import { h, computed, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
const props = defineProps({ const props = defineProps({
organizationId: { organizationId: {
@ -274,6 +280,7 @@ const showOrganizationModal = ref(false)
const showQuickEntryModal = ref(false) const showQuickEntryModal = ref(false)
const detailMode = ref(false) const detailMode = ref(false)
const route = useRoute()
const router = useRouter() const router = useRouter()
const organization = createDocumentResource({ const organization = createDocumentResource({
@ -286,6 +293,26 @@ const organization = createDocumentResource({
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }] let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
if (route.query.view || route.query.viewType) {
let view = getView(
route.query.view,
route.query.viewType,
'CRM Organization',
)
if (view) {
items.push({
label: __(view.label),
icon: view.icon,
route: {
name: 'Organizations',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
})
}
}
items.push({ items.push({
label: props.organizationId, label: props.organizationId,
route: { route: {

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Organizations" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -70,6 +70,7 @@
/> />
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue' import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
@ -77,7 +78,6 @@ import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue' import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import OrganizationsListView from '@/components/ListViews/OrganizationsListView.vue' import OrganizationsListView from '@/components/ListViews/OrganizationsListView.vue'
import ViewControls from '@/components/ViewControls.vue' import ViewControls from '@/components/ViewControls.vue'
import { Breadcrumbs } from 'frappe-ui'
import { import {
dateFormat, dateFormat,
dateTooltipFormat, dateTooltipFormat,
@ -85,33 +85,11 @@ import {
formatNumberIntoCurrency, formatNumberIntoCurrency,
} from '@/utils' } from '@/utils'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const organizationsListView = ref(null) const organizationsListView = ref(null)
const showOrganizationModal = ref(false) const showOrganizationModal = ref(false)
const showQuickEntryModal = ref(false) const showQuickEntryModal = ref(false)
const currentOrganization = computed(() => {
return organizations.value?.data?.data?.find(
(organization) => organization.name === route.params.organizationId,
)
})
const breadcrumbs = computed(() => {
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
if (!currentOrganization.value) return items
items.push({
label: __(currentOrganization.value.name),
route: {
name: 'Organization',
params: { organizationId: currentOrganization.value.name },
},
})
return items
})
// organizations data is loaded in the ViewControls component // organizations data is loaded in the ViewControls component
const organizations = ref({}) const organizations = ref({})
const loadMore = ref(1) const loadMore = ref(1)

View File

@ -1,7 +1,7 @@
<template> <template>
<LayoutHeader> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs" /> <ViewBreadcrumbs v-model="viewControls" routeName="Tasks" />
</template> </template>
<template #right-header> <template #right-header>
<CustomActions <CustomActions
@ -193,6 +193,7 @@
</template> </template>
<script setup> <script setup>
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import CustomActions from '@/components/CustomActions.vue' import CustomActions from '@/components/CustomActions.vue'
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue' import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue' import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
@ -205,19 +206,10 @@ import KanbanView from '@/components/Kanban/KanbanView.vue'
import TaskModal from '@/components/Modals/TaskModal.vue' import TaskModal from '@/components/Modals/TaskModal.vue'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils' import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
import { import { Tooltip, Avatar, TextEditor, Dropdown, call } from 'frappe-ui'
Breadcrumbs,
Tooltip,
Avatar,
TextEditor,
Dropdown,
call,
} from 'frappe-ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const breadcrumbs = [{ label: __('Tasks'), route: { name: 'Tasks' } }]
const { getUser } = usersStore() const { getUser } = usersStore()
const router = useRouter() const router = useRouter()

View File

@ -40,7 +40,8 @@ const routes = [
props: true, props: true,
}, },
{ {
path: '/notes', alias: '/notes',
path: '/notes/view/:viewType?',
name: 'Notes', name: 'Notes',
component: () => import('@/pages/Notes.vue'), component: () => import('@/pages/Notes.vue'),
}, },
@ -51,7 +52,8 @@ const routes = [
component: () => import('@/pages/Tasks.vue'), component: () => import('@/pages/Tasks.vue'),
}, },
{ {
path: '/contacts', alias: '/contacts',
path: '/contacts/view/:viewType?',
name: 'Contacts', name: 'Contacts',
component: () => import('@/pages/Contacts.vue'), component: () => import('@/pages/Contacts.vue'),
meta: { scrollPos: { top: 0, left: 0 } }, meta: { scrollPos: { top: 0, left: 0 } },
@ -63,7 +65,8 @@ const routes = [
props: true, props: true,
}, },
{ {
path: '/organizations', alias: '/organizations',
path: '/organizations/view/:viewType?',
name: 'Organizations', name: 'Organizations',
component: () => import('@/pages/Organizations.vue'), component: () => import('@/pages/Organizations.vue'),
meta: { scrollPos: { top: 0, left: 0 } }, meta: { scrollPos: { top: 0, left: 0 } },
@ -75,13 +78,15 @@ const routes = [
props: true, props: true,
}, },
{ {
path: '/call-logs', alias: '/call-logs',
path: '/call-logs/view/:viewType?',
name: 'Call Logs', name: 'Call Logs',
component: () => import('@/pages/CallLogs.vue'), component: () => import('@/pages/CallLogs.vue'),
meta: { scrollPos: { top: 0, left: 0 } }, meta: { scrollPos: { top: 0, left: 0 } },
}, },
{ {
path: '/email-templates', alias: '/email-templates',
path: '/email-templates/view/:viewType?',
name: 'Email Templates', name: 'Email Templates',
component: () => import('@/pages/EmailTemplates.vue'), component: () => import('@/pages/EmailTemplates.vue'),
meta: { scrollPos: { top: 0, left: 0 } }, meta: { scrollPos: { top: 0, left: 0 } },
@ -92,11 +97,6 @@ const routes = [
component: () => import('@/pages/EmailTemplate.vue'), component: () => import('@/pages/EmailTemplate.vue'),
props: true, props: true,
}, },
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/pages/Dashboard.vue'),
},
{ {
path: '/:invalidpath', path: '/:invalidpath',
name: 'Invalid Page', name: 'Invalid Page',

View File

@ -0,0 +1,35 @@
import ListIcon from '@/components/Icons/ListIcon.vue'
import GroupByIcon from '@/components/Icons/GroupByIcon.vue'
import KanbanIcon from '@/components/Icons/KanbanIcon.vue'
import { viewsStore } from '@/stores/views'
import { markRaw } from 'vue'
const { getView: getViewDetails } = viewsStore()
function defaultView(type) {
let types = {
list: {
label: __('List'),
icon: markRaw(ListIcon),
},
group_by: {
label: __('Group By'),
icon: markRaw(GroupByIcon),
},
kanban: {
label: __('Kanban'),
icon: markRaw(KanbanIcon),
},
}
return types[type]
}
export function getView(view, type, doctype) {
let viewType = type || 'list'
let viewDetails = getViewDetails(view, viewType, doctype)
if (viewDetails && !viewDetails.icon) {
viewDetails.icon = defaultView(viewType).icon
}
return viewDetails || defaultView(viewType)
}