Merge pull request #319 from shariquerik/view-selector
fix: Show view selector dropdown in breadcrumb
This commit is contained in:
commit
0edde767fd
21
frontend/src/components/Icon.vue
Normal file
21
frontend/src/components/Icon.vue
Normal 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>
|
||||
16
frontend/src/components/Icons/GroupByIcon.vue
Normal file
16
frontend/src/components/Icons/GroupByIcon.vue
Normal 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>
|
||||
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-kanban"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M6 5v11" />
|
||||
<path d="M12 5v6" />
|
||||
<path d="M18 5v14" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
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>
|
||||
</template>
|
||||
|
||||
16
frontend/src/components/Icons/ListIcon.vue
Normal file
16
frontend/src/components/Icons/ListIcon.vue
Normal 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>
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Teleport to="#app-header" v-if="showHeader">
|
||||
<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">
|
||||
<slot name="left-header" />
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
getRowRoute: (row) => ({
|
||||
name: 'Contact',
|
||||
params: { contactId: row.name },
|
||||
query: { view: route.query.view, viewType: route.params.viewType },
|
||||
}),
|
||||
selectable: options.selectable,
|
||||
showTooltip: options.showTooltip,
|
||||
@ -174,6 +175,7 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -205,6 +207,8 @@ const emit = defineEmits([
|
||||
'likeDoc',
|
||||
])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
@ -230,7 +234,7 @@ const listBulkActionsRef = ref(null)
|
||||
|
||||
defineExpose({
|
||||
customListActions: computed(
|
||||
() => listBulkActionsRef.value?.customListActions
|
||||
() => listBulkActionsRef.value?.customListActions,
|
||||
),
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -4,14 +4,21 @@
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
: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,
|
||||
showTooltip: options.showTooltip,
|
||||
resizeColumn: options.resizeColumn,
|
||||
}"
|
||||
row-key="name"
|
||||
>
|
||||
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||
<ListHeader
|
||||
class="sm:mx-5 mx-3"
|
||||
@columnWidthUpdated="emit('columnWidthUpdated')"
|
||||
>
|
||||
<ListHeaderItem
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
@ -204,6 +211,7 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -235,6 +243,8 @@ const emit = defineEmits([
|
||||
'likeDoc',
|
||||
])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
@ -260,7 +270,7 @@ const listBulkActionsRef = ref(null)
|
||||
|
||||
defineExpose({
|
||||
customListActions: computed(
|
||||
() => listBulkActionsRef.value?.customListActions
|
||||
() => listBulkActionsRef.value?.customListActions,
|
||||
),
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -4,14 +4,21 @@
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
: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,
|
||||
showTooltip: options.showTooltip,
|
||||
resizeColumn: options.resizeColumn,
|
||||
}"
|
||||
row-key="name"
|
||||
>
|
||||
<ListHeader class="sm:mx-5 mx-3" @columnWidthUpdated="emit('columnWidthUpdated')">
|
||||
<ListHeader
|
||||
class="sm:mx-5 mx-3"
|
||||
@columnWidthUpdated="emit('columnWidthUpdated')"
|
||||
>
|
||||
<ListHeaderItem
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
@ -217,6 +224,7 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -248,6 +256,8 @@ const emit = defineEmits([
|
||||
'likeDoc',
|
||||
])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
@ -273,7 +283,7 @@ const listBulkActionsRef = ref(null)
|
||||
|
||||
defineExpose({
|
||||
customListActions: computed(
|
||||
() => listBulkActionsRef.value?.customListActions
|
||||
() => listBulkActionsRef.value?.customListActions,
|
||||
),
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
getRowRoute: (row) => ({
|
||||
name: 'Organization',
|
||||
params: { organizationId: row.name },
|
||||
query: { view: route.query.view, viewType: route.params.viewType },
|
||||
}),
|
||||
selectable: options.selectable,
|
||||
showTooltip: options.showTooltip,
|
||||
@ -156,6 +157,7 @@ import {
|
||||
} from 'frappe-ui'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
@ -187,6 +189,8 @@ const emit = defineEmits([
|
||||
'likeDoc',
|
||||
])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const pageLengthCount = defineModel()
|
||||
const list = defineModel('list')
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
97
frontend/src/components/ViewBreadcrumbs.vue
Normal file
97
frontend/src/components/ViewBreadcrumbs.vue
Normal 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>
|
||||
@ -3,43 +3,6 @@
|
||||
v-if="isMobileView"
|
||||
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 items-center justify-between gap-2 overflow-x-auto">
|
||||
<div class="flex gap-2">
|
||||
@ -59,6 +22,11 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button :label="__('Refresh')" @click="reload()" :loading="isLoading">
|
||||
<template #icon>
|
||||
<RefreshIcon class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<SortBy
|
||||
v-if="route.params.viewType !== 'kanban'"
|
||||
v-model="list"
|
||||
@ -91,37 +59,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
class="flex flex-1 items-center overflow-x-auto px-1"
|
||||
class="flex flex-1 items-center overflow-x-auto -ml-1"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<div
|
||||
@ -223,6 +162,7 @@
|
||||
afterUpdate: () => {
|
||||
viewUpdated = false
|
||||
reloadView()
|
||||
list.reload()
|
||||
},
|
||||
}"
|
||||
/>
|
||||
@ -268,8 +208,9 @@
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||
import ListIcon from '@/components/Icons/ListIcon.vue'
|
||||
import KanbanIcon from '@/components/Icons/KanbanIcon.vue'
|
||||
import GroupByIcon from '@/components/Icons/GroupByIcon.vue'
|
||||
import QuickFilterField from '@/components/QuickFilterField.vue'
|
||||
import RefreshIcon from '@/components/Icons/RefreshIcon.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
@ -340,15 +281,18 @@ function getViewType() {
|
||||
let viewType = route.params.viewType || 'list'
|
||||
let types = {
|
||||
list: {
|
||||
label: __('List View'),
|
||||
icon: 'list',
|
||||
name: 'list',
|
||||
label: __('List'),
|
||||
icon: markRaw(ListIcon),
|
||||
},
|
||||
group_by: {
|
||||
label: __('Group By View'),
|
||||
icon: markRaw(DetailsIcon),
|
||||
name: 'group_by',
|
||||
label: __('Group By'),
|
||||
icon: markRaw(GroupByIcon),
|
||||
},
|
||||
kanban: {
|
||||
label: __('Kanban View'),
|
||||
name: 'kanban',
|
||||
label: __('Kanban'),
|
||||
icon: markRaw(KanbanIcon),
|
||||
},
|
||||
}
|
||||
@ -359,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,
|
||||
@ -531,27 +476,19 @@ let allowedViews = props.options.allowedViews || ['list']
|
||||
|
||||
if (allowedViews.includes('list')) {
|
||||
defaultViews.push({
|
||||
label: __(props.options?.defaultViewName) || __('List View'),
|
||||
icon: 'list',
|
||||
name: 'list',
|
||||
label: __(props.options?.defaultViewName) || __('List'),
|
||||
icon: markRaw(ListIcon),
|
||||
onClick() {
|
||||
viewUpdated.value = false
|
||||
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')) {
|
||||
defaultViews.push({
|
||||
label: __(props.options?.defaultViewName) || __('Kanban View'),
|
||||
name: 'kanban',
|
||||
label: __(props.options?.defaultViewName) || __('Kanban'),
|
||||
icon: markRaw(KanbanIcon),
|
||||
onClick() {
|
||||
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) {
|
||||
if (isEmoji(icon)) {
|
||||
return h('div', icon)
|
||||
} 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(() => {
|
||||
@ -580,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)
|
||||
@ -602,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'),
|
||||
@ -620,6 +570,18 @@ const viewsDropdownOptions = computed(() => {
|
||||
})
|
||||
}
|
||||
|
||||
_views.push({
|
||||
group: __('Actions'),
|
||||
hideLabel: true,
|
||||
items: [
|
||||
{
|
||||
label: __('Create View'),
|
||||
icon: 'plus',
|
||||
onClick: () => createView(),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return _views
|
||||
})
|
||||
|
||||
@ -912,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'),
|
||||
@ -921,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),
|
||||
})
|
||||
}
|
||||
|
||||
@ -965,14 +929,16 @@ const viewActions = computed(() => {
|
||||
onClick: () =>
|
||||
$dialog({
|
||||
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',
|
||||
actions: [
|
||||
{
|
||||
label: __('Delete'),
|
||||
variant: 'solid',
|
||||
theme: 'red',
|
||||
onClick: (close) => deleteView(close),
|
||||
onClick: (close) => deleteView(_view, close),
|
||||
},
|
||||
],
|
||||
}),
|
||||
@ -981,56 +947,61 @@ const viewActions = computed(() => {
|
||||
})
|
||||
}
|
||||
return actions
|
||||
})
|
||||
}
|
||||
|
||||
const viewModalObj = ref({})
|
||||
|
||||
function duplicateView() {
|
||||
let label =
|
||||
__(
|
||||
getView(route.query.view, route.params.viewType, props.doctype)?.label,
|
||||
) || getViewType().label
|
||||
function createView() {
|
||||
view.value.name = ''
|
||||
view.value.label = label + __(' (New)')
|
||||
view.value.label = ''
|
||||
view.value.icon = ''
|
||||
viewModalObj.value = view.value
|
||||
viewModalObj.value.mode = 'create'
|
||||
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 duplicateView(v) {
|
||||
v.label = v.label + __(' (New)')
|
||||
viewModalObj.value = v
|
||||
viewModalObj.value.mode = 'duplicate'
|
||||
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', {
|
||||
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()
|
||||
}
|
||||
@ -1059,6 +1030,7 @@ function saveView() {
|
||||
load_default_columns: view.value.load_default_columns,
|
||||
}
|
||||
viewModalObj.value = view.value
|
||||
viewModalObj.value.mode = 'edit'
|
||||
showViewModal.value = true
|
||||
}
|
||||
|
||||
@ -1119,6 +1091,9 @@ defineExpose({
|
||||
likeDoc,
|
||||
updateKanbanSettings,
|
||||
loadMoreKanban,
|
||||
viewActions,
|
||||
viewsDropdownOptions,
|
||||
currentView,
|
||||
})
|
||||
|
||||
// Watchers
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Call Logs" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -54,6 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.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 CallLogModal from '@/components/Modals/CallLogModal.vue'
|
||||
import { getCallLogDetail } from '@/utils/callLog'
|
||||
import { Breadcrumbs } from 'frappe-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
|
||||
|
||||
const callLogsListView = ref(null)
|
||||
|
||||
// callLogs data is loaded in the ViewControls component
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<LayoutHeader v-if="contact.data">
|
||||
<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>
|
||||
</LayoutHeader>
|
||||
<div v-if="contact.data" class="flex h-full flex-col overflow-hidden">
|
||||
@ -216,16 +220,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Avatar,
|
||||
FileUploader,
|
||||
Tooltip,
|
||||
Tabs,
|
||||
call,
|
||||
createResource,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import Dropdown from '@/components/frappe-ui/Dropdown.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
@ -242,13 +237,24 @@ import {
|
||||
timeAgo,
|
||||
formatNumberIntoCurrency,
|
||||
} from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global.js'
|
||||
import { usersStore } from '@/stores/users.js'
|
||||
import { organizationsStore } from '@/stores/organizations.js'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { callEnabled } from '@/composables/settings'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Avatar,
|
||||
FileUploader,
|
||||
Tooltip,
|
||||
Tabs,
|
||||
call,
|
||||
createResource,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { ref, computed, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const { $dialog, makeCall } = globalStore()
|
||||
|
||||
@ -263,6 +269,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const showContactModal = ref(false)
|
||||
@ -287,6 +294,22 @@ const contact = createResource({
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
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({
|
||||
label: contact.data?.full_name,
|
||||
route: { name: 'Contact', params: { contactId: props.contactId } },
|
||||
@ -300,7 +323,6 @@ usePageMeta(() => {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function validateFile(file) {
|
||||
let extn = file.name.split('.').pop().toLowerCase()
|
||||
if (!['png', 'jpg', 'jpeg'].includes(extn)) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Contacts" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -72,6 +72,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import ContactsIcon from '@/components/Icons/ContactsIcon.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 ContactsListView from '@/components/ListViews/ContactsListView.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import { Breadcrumbs } from 'frappe-ui'
|
||||
import { organizationsStore } from '@/stores/organizations.js'
|
||||
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { getOrganization } = organizationsStore()
|
||||
const route = useRoute()
|
||||
|
||||
const showContactModal = 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)
|
||||
|
||||
// contacts data is loaded in the ViewControls component
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<LayoutHeader v-if="deal.data">
|
||||
<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 #right-header>
|
||||
<CustomActions
|
||||
@ -302,6 +306,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import Resizer from '@/components/Resizer.vue'
|
||||
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
@ -337,6 +342,7 @@ import {
|
||||
errorMessage,
|
||||
copyToClipboard,
|
||||
} from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
@ -353,12 +359,13 @@ import {
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { ref, computed, h, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const { $dialog, makeCall } = globalStore()
|
||||
const { organizations, getOrganization } = organizationsStore()
|
||||
const { statusOptions, getDealStatus } = statusesStore()
|
||||
const { isManager } = usersStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
@ -452,6 +459,22 @@ function validateRequired(fieldname, value) {
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
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({
|
||||
label: organization.value?.name || __('Untitled'),
|
||||
route: { name: 'Deal', params: { dealId: deal.data.name } },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Deals" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -32,7 +32,11 @@
|
||||
v-if="route.params.viewType == 'kanban'"
|
||||
v-model="deals"
|
||||
: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),
|
||||
}"
|
||||
@update="(data) => viewControls.updateKanbanSettings(data)"
|
||||
@ -259,6 +263,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue'
|
||||
@ -288,12 +293,10 @@ import {
|
||||
formatNumberIntoCurrency,
|
||||
formatTime,
|
||||
} from '@/utils'
|
||||
import { Breadcrumbs, Tooltip, Avatar, Dropdown } from 'frappe-ui'
|
||||
import { Tooltip, Avatar, Dropdown } from 'frappe-ui'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, reactive, computed, h } from 'vue'
|
||||
|
||||
const breadcrumbs = [{ label: __('Deals'), route: { name: 'Deals' } }]
|
||||
|
||||
const { makeCall } = globalStore()
|
||||
const { getUser } = usersStore()
|
||||
const { getOrganization } = organizationsStore()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Email Templates" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -68,6 +68,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.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 EmailTemplateModal from '@/components/Modals/EmailTemplateModal.vue'
|
||||
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
|
||||
import { Breadcrumbs } from 'frappe-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const breadcrumbs = [
|
||||
{ label: __('Email Templates'), route: { name: 'Email Templates' } },
|
||||
]
|
||||
|
||||
const emailTemplatesListView = ref(null)
|
||||
|
||||
// emailTemplates data is loaded in the ViewControls component
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<LayoutHeader v-if="lead.data">
|
||||
<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 #right-header>
|
||||
<CustomActions
|
||||
@ -273,6 +277,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import Resizer from '@/components/Resizer.vue'
|
||||
import EditIcon from '@/components/Icons/EditIcon.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
@ -306,6 +311,7 @@ import {
|
||||
errorMessage,
|
||||
copyToClipboard,
|
||||
} from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
@ -421,6 +427,22 @@ function validateRequired(fieldname, value) {
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
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({
|
||||
label: lead.data.lead_name || __('Untitled'),
|
||||
route: { name: 'Lead', params: { leadId: lead.data.name } },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Leads" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -33,7 +33,11 @@
|
||||
v-if="route.params.viewType == 'kanban'"
|
||||
v-model="leads"
|
||||
: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),
|
||||
}"
|
||||
@update="(data) => viewControls.updateKanbanSettings(data)"
|
||||
@ -281,6 +285,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue'
|
||||
@ -304,12 +309,10 @@ import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { callEnabled } from '@/composables/settings'
|
||||
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 { ref, computed, reactive, h } from 'vue'
|
||||
|
||||
const breadcrumbs = [{ label: __('Leads'), route: { name: 'Leads' } }]
|
||||
|
||||
const { makeCall } = globalStore()
|
||||
const { getUser } = usersStore()
|
||||
const { getOrganization } = organizationsStore()
|
||||
|
||||
@ -3,7 +3,11 @@
|
||||
<header
|
||||
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">
|
||||
<Dropdown :options="statusOptions('deal', updateField)">
|
||||
<template #default="{ open }">
|
||||
@ -245,6 +249,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||
import LoadingIndicator from '@/components/Icons/LoadingIndicator.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 SLASection from '@/components/SLASection.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import {
|
||||
createToast,
|
||||
setupAssignees,
|
||||
setupCustomActions,
|
||||
errorMessage,
|
||||
} from '@/utils'
|
||||
import { createToast, setupAssignees, setupCustomActions } from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { whatsappEnabled, callEnabled, isMobileView } from '@/composables/settings'
|
||||
import {
|
||||
whatsappEnabled,
|
||||
callEnabled,
|
||||
isMobileView,
|
||||
} from '@/composables/settings'
|
||||
import {
|
||||
createResource,
|
||||
Dropdown,
|
||||
@ -288,11 +293,12 @@ import {
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
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 { statusOptions, getDealStatus } = statusesStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
@ -385,6 +391,22 @@ function validateRequired(fieldname, value) {
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
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({
|
||||
label: organization.value?.name || __('Untitled'),
|
||||
route: { name: 'Deal', params: { dealId: deal.data.name } },
|
||||
|
||||
@ -3,7 +3,11 @@
|
||||
<header
|
||||
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">
|
||||
<Dropdown :options="statusOptions('lead', updateField)">
|
||||
<template #default="{ open }">
|
||||
@ -138,7 +142,7 @@
|
||||
<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>
|
||||
@ -170,6 +174,7 @@
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.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 CustomActions from '@/components/CustomActions.vue'
|
||||
import { createToast, setupAssignees, setupCustomActions } from '@/utils'
|
||||
import { getView } from '@/utils/view'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { contactsStore } from '@/stores/contacts'
|
||||
import { organizationsStore } from '@/stores/organizations'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { whatsappEnabled, callEnabled, isMobileView } from '@/composables/settings'
|
||||
import {
|
||||
whatsappEnabled,
|
||||
callEnabled,
|
||||
isMobileView,
|
||||
} from '@/composables/settings'
|
||||
import {
|
||||
createResource,
|
||||
Dropdown,
|
||||
@ -298,6 +308,22 @@ function validateRequired(fieldname, value) {
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
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({
|
||||
label: lead.data.lead_name || __('Untitled'),
|
||||
route: { name: 'Lead', params: { leadId: lead.data.name } },
|
||||
@ -359,7 +385,7 @@ const tabs = computed(() => {
|
||||
watch(tabs, (value) => {
|
||||
if (value && route.params.tabName) {
|
||||
let index = value.findIndex(
|
||||
(tab) => tab.name.toLowerCase() === route.params.tabName.toLowerCase()
|
||||
(tab) => tab.name.toLowerCase() === route.params.tabName.toLowerCase(),
|
||||
)
|
||||
if (index !== -1) {
|
||||
tabIndex.value = index
|
||||
@ -447,7 +473,7 @@ async function convertToDeal(updated) {
|
||||
organization: lead.data.organization,
|
||||
},
|
||||
'',
|
||||
() => convertToDeal(true)
|
||||
() => convertToDeal(true),
|
||||
)
|
||||
showConvertToDealModal.value = false
|
||||
} else {
|
||||
@ -455,7 +481,7 @@ async function convertToDeal(updated) {
|
||||
'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal',
|
||||
{
|
||||
lead: lead.data.name,
|
||||
}
|
||||
},
|
||||
)
|
||||
if (deal) {
|
||||
if (updated) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Notes" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<Button variant="solid" :label="__('Create')" @click="createNote">
|
||||
@ -10,6 +10,7 @@
|
||||
</template>
|
||||
</LayoutHeader>
|
||||
<ViewControls
|
||||
ref="viewControls"
|
||||
v-model="notes"
|
||||
v-model:loadMore="loadMore"
|
||||
v-model:updatedPageCount="updatedPageCount"
|
||||
@ -102,6 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.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 { usersStore } from '@/stores/users'
|
||||
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
||||
import {
|
||||
TextEditor,
|
||||
call,
|
||||
Dropdown,
|
||||
Tooltip,
|
||||
Breadcrumbs,
|
||||
ListFooter,
|
||||
} from 'frappe-ui'
|
||||
import { TextEditor, call, Dropdown, Tooltip, ListFooter } from 'frappe-ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const breadcrumbs = [{ label: __('Notes'), route: { name: 'Notes' } }]
|
||||
|
||||
const showNoteModal = ref(false)
|
||||
const currentNote = ref(null)
|
||||
|
||||
const notes = ref({})
|
||||
const loadMore = ref(1)
|
||||
const updatedPageCount = ref(20)
|
||||
const viewControls = ref(null)
|
||||
|
||||
watch(
|
||||
() => notes.value?.data?.page_length_count,
|
||||
(val, old_value) => {
|
||||
if (!val || val === old_value) return
|
||||
updatedPageCount.value = val
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function createNote() {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<LayoutHeader v-if="organization.doc">
|
||||
<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>
|
||||
</LayoutHeader>
|
||||
<div v-if="organization.doc" class="flex flex-1 flex-col overflow-hidden">
|
||||
@ -226,17 +230,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Avatar,
|
||||
FileUploader,
|
||||
Dropdown,
|
||||
Tabs,
|
||||
call,
|
||||
createListResource,
|
||||
createDocumentResource,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import OrganizationModal from '@/components/Modals/OrganizationModal.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 { usersStore } from '@/stores/users'
|
||||
import { statusesStore } from '@/stores/statuses'
|
||||
import { getView } from '@/utils/view'
|
||||
import {
|
||||
dateFormat,
|
||||
dateTooltipFormat,
|
||||
timeAgo,
|
||||
formatNumberIntoCurrency,
|
||||
} from '@/utils'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Avatar,
|
||||
FileUploader,
|
||||
Dropdown,
|
||||
Tabs,
|
||||
call,
|
||||
createListResource,
|
||||
createDocumentResource,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { h, computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
organizationId: {
|
||||
@ -274,6 +280,7 @@ const showOrganizationModal = ref(false)
|
||||
const showQuickEntryModal = ref(false)
|
||||
const detailMode = ref(false)
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const organization = createDocumentResource({
|
||||
@ -286,6 +293,26 @@ const organization = createDocumentResource({
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
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({
|
||||
label: props.organizationId,
|
||||
route: {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Organizations" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -70,6 +70,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.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 OrganizationsListView from '@/components/ListViews/OrganizationsListView.vue'
|
||||
import ViewControls from '@/components/ViewControls.vue'
|
||||
import { Breadcrumbs } from 'frappe-ui'
|
||||
import {
|
||||
dateFormat,
|
||||
dateTooltipFormat,
|
||||
@ -85,33 +85,11 @@ import {
|
||||
formatNumberIntoCurrency,
|
||||
} from '@/utils'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const organizationsListView = ref(null)
|
||||
const showOrganizationModal = 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
|
||||
const organizations = ref({})
|
||||
const loadMore = ref(1)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LayoutHeader>
|
||||
<template #left-header>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<ViewBreadcrumbs v-model="viewControls" routeName="Tasks" />
|
||||
</template>
|
||||
<template #right-header>
|
||||
<CustomActions
|
||||
@ -193,6 +193,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
|
||||
import CustomActions from '@/components/CustomActions.vue'
|
||||
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.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 { usersStore } from '@/stores/users'
|
||||
import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Tooltip,
|
||||
Avatar,
|
||||
TextEditor,
|
||||
Dropdown,
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
import { Tooltip, Avatar, TextEditor, Dropdown, call } from 'frappe-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const breadcrumbs = [{ label: __('Tasks'), route: { name: 'Tasks' } }]
|
||||
|
||||
const { getUser } = usersStore()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@ -40,7 +40,8 @@ const routes = [
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/notes',
|
||||
alias: '/notes',
|
||||
path: '/notes/view/:viewType?',
|
||||
name: 'Notes',
|
||||
component: () => import('@/pages/Notes.vue'),
|
||||
},
|
||||
@ -51,7 +52,8 @@ const routes = [
|
||||
component: () => import('@/pages/Tasks.vue'),
|
||||
},
|
||||
{
|
||||
path: '/contacts',
|
||||
alias: '/contacts',
|
||||
path: '/contacts/view/:viewType?',
|
||||
name: 'Contacts',
|
||||
component: () => import('@/pages/Contacts.vue'),
|
||||
meta: { scrollPos: { top: 0, left: 0 } },
|
||||
@ -63,7 +65,8 @@ const routes = [
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/organizations',
|
||||
alias: '/organizations',
|
||||
path: '/organizations/view/:viewType?',
|
||||
name: 'Organizations',
|
||||
component: () => import('@/pages/Organizations.vue'),
|
||||
meta: { scrollPos: { top: 0, left: 0 } },
|
||||
@ -75,13 +78,15 @@ const routes = [
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/call-logs',
|
||||
alias: '/call-logs',
|
||||
path: '/call-logs/view/:viewType?',
|
||||
name: 'Call Logs',
|
||||
component: () => import('@/pages/CallLogs.vue'),
|
||||
meta: { scrollPos: { top: 0, left: 0 } },
|
||||
},
|
||||
{
|
||||
path: '/email-templates',
|
||||
alias: '/email-templates',
|
||||
path: '/email-templates/view/:viewType?',
|
||||
name: 'Email Templates',
|
||||
component: () => import('@/pages/EmailTemplates.vue'),
|
||||
meta: { scrollPos: { top: 0, left: 0 } },
|
||||
@ -92,11 +97,6 @@ const routes = [
|
||||
component: () => import('@/pages/EmailTemplate.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/pages/Dashboard.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:invalidpath',
|
||||
name: 'Invalid Page',
|
||||
|
||||
35
frontend/src/utils/view.js
Normal file
35
frontend/src/utils/view.js
Normal 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)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user