Merge pull request #382 from frappe/develop
chore: Merge develop to main
This commit is contained in:
commit
67695580e8
@ -125,6 +125,7 @@ def get_deal_activities(name):
|
|||||||
"bcc": communication.bcc,
|
"bcc": communication.bcc,
|
||||||
"attachments": get_attachments('Communication', communication.name),
|
"attachments": get_attachments('Communication', communication.name),
|
||||||
"read_by_recipient": communication.read_by_recipient,
|
"read_by_recipient": communication.read_by_recipient,
|
||||||
|
"delivery_status": communication.delivery_status,
|
||||||
},
|
},
|
||||||
"is_lead": False,
|
"is_lead": False,
|
||||||
}
|
}
|
||||||
@ -238,6 +239,7 @@ def get_lead_activities(name):
|
|||||||
"bcc": communication.bcc,
|
"bcc": communication.bcc,
|
||||||
"attachments": get_attachments('Communication', communication.name),
|
"attachments": get_attachments('Communication', communication.name),
|
||||||
"read_by_recipient": communication.read_by_recipient,
|
"read_by_recipient": communication.read_by_recipient,
|
||||||
|
"delivery_status": communication.delivery_status,
|
||||||
},
|
},
|
||||||
"is_lead": True,
|
"is_lead": True,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -685,6 +685,7 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
|
|||||||
"depends_on": field.depends_on,
|
"depends_on": field.depends_on,
|
||||||
"mandatory_depends_on": field.mandatory_depends_on,
|
"mandatory_depends_on": field.mandatory_depends_on,
|
||||||
"read_only_depends_on": field.read_only_depends_on,
|
"read_only_depends_on": field.read_only_depends_on,
|
||||||
|
"link_filters": field.get("link_filters"),
|
||||||
})
|
})
|
||||||
|
|
||||||
return _fields
|
return _fields
|
||||||
|
|||||||
@ -16,6 +16,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 shrink-0">
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
|
<Badge
|
||||||
|
v-if="status.label"
|
||||||
|
:label="__(status.label)"
|
||||||
|
variant="subtle"
|
||||||
|
:theme="status.color"
|
||||||
|
/>
|
||||||
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
|
||||||
<div class="text-sm text-gray-600">
|
<div class="text-sm text-gray-600">
|
||||||
{{ __(timeAgo(activity.creation)) }}
|
{{ __(timeAgo(activity.creation)) }}
|
||||||
@ -87,6 +93,7 @@ import AttachmentItem from '@/components/AttachmentItem.vue'
|
|||||||
import EmailContent from '@/components/Activities/EmailContent.vue'
|
import EmailContent from '@/components/Activities/EmailContent.vue'
|
||||||
import { Badge, Tooltip } from 'frappe-ui'
|
import { Badge, Tooltip } from 'frappe-ui'
|
||||||
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
import { timeAgo, dateFormat, dateTooltipFormat } from '@/utils'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
activity: Object,
|
activity: Object,
|
||||||
@ -140,4 +147,19 @@ function reply(email, reply_all = false) {
|
|||||||
.focus('start')
|
.focus('start')
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const status = computed(() => {
|
||||||
|
let _status = props.activity?.data?.delivery_status
|
||||||
|
let indicator_color = 'red'
|
||||||
|
if (['Sent', 'Clicked'].includes(_status)) {
|
||||||
|
indicator_color = 'green'
|
||||||
|
} else if (['Sending', 'Scheduled'].includes(_status)) {
|
||||||
|
indicator_color = 'orange'
|
||||||
|
} else if (['Opened', 'Read'].includes(_status)) {
|
||||||
|
indicator_color = 'blue'
|
||||||
|
} else if (_status == 'Error') {
|
||||||
|
indicator_color = 'red'
|
||||||
|
}
|
||||||
|
return { label: _status, color: indicator_color }
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -69,6 +69,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
filters: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
@ -122,6 +126,7 @@ const options = createResource({
|
|||||||
params: {
|
params: {
|
||||||
txt: text.value,
|
txt: text.value,
|
||||||
doctype: props.doctype,
|
doctype: props.doctype,
|
||||||
|
filters: props.filters,
|
||||||
},
|
},
|
||||||
transform: (data) => {
|
transform: (data) => {
|
||||||
let allData = data.map((option) => {
|
let allData = data.map((option) => {
|
||||||
@ -152,6 +157,7 @@ function reload(val) {
|
|||||||
params: {
|
params: {
|
||||||
txt: val,
|
txt: val,
|
||||||
doctype: props.doctype,
|
doctype: props.doctype,
|
||||||
|
filters: props.filters,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
options.reload()
|
options.reload()
|
||||||
|
|||||||
@ -89,6 +89,7 @@
|
|||||||
class="form-control flex-1"
|
class="form-control flex-1"
|
||||||
:value="data[field.name]"
|
:value="data[field.name]"
|
||||||
:doctype="field.options"
|
:doctype="field.options"
|
||||||
|
:filters="field.filters"
|
||||||
@change="(v) => (data[field.name] = v)"
|
@change="(v) => (data[field.name] = v)"
|
||||||
:placeholder="__(field.placeholder || field.label)"
|
:placeholder="__(field.placeholder || field.label)"
|
||||||
:onCreate="field.create"
|
:onCreate="field.create"
|
||||||
@ -110,6 +111,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
:value="getUser(data[field.name]).full_name"
|
:value="getUser(data[field.name]).full_name"
|
||||||
:doctype="field.options"
|
:doctype="field.options"
|
||||||
|
:filters="field.filters"
|
||||||
@change="(v) => (data[field.name] = v)"
|
@change="(v) => (data[field.name] = v)"
|
||||||
:placeholder="__(field.placeholder || field.label)"
|
:placeholder="__(field.placeholder || field.label)"
|
||||||
:hideMe="true"
|
:hideMe="true"
|
||||||
|
|||||||
@ -65,6 +65,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
:value="data[field.name] && getUser(data[field.name]).full_name"
|
:value="data[field.name] && getUser(data[field.name]).full_name"
|
||||||
doctype="User"
|
doctype="User"
|
||||||
|
:filters="field.filters"
|
||||||
@change="(data) => emit('update', field.name, data)"
|
@change="(data) => emit('update', field.name, data)"
|
||||||
:placeholder="'Select' + ' ' + field.label + '...'"
|
:placeholder="'Select' + ' ' + field.label + '...'"
|
||||||
:hideMe="true"
|
:hideMe="true"
|
||||||
@ -88,6 +89,7 @@
|
|||||||
class="form-control select-text"
|
class="form-control select-text"
|
||||||
:value="data[field.name]"
|
:value="data[field.name]"
|
||||||
:doctype="field.doctype"
|
:doctype="field.doctype"
|
||||||
|
:filters="field.filters"
|
||||||
:placeholder="field.placeholder"
|
:placeholder="field.placeholder"
|
||||||
@change="(data) => emit('update', field.name, data)"
|
@change="(data) => emit('update', field.name, data)"
|
||||||
:onCreate="field.create"
|
:onCreate="field.create"
|
||||||
@ -144,6 +146,7 @@ const _fields = computed(() => {
|
|||||||
if (df?.depends_on) evaluate_depends_on(df.depends_on, field)
|
if (df?.depends_on) evaluate_depends_on(df.depends_on, field)
|
||||||
all_fields.push({
|
all_fields.push({
|
||||||
...field,
|
...field,
|
||||||
|
filters: df.link_filters && JSON.parse(df.link_filters),
|
||||||
placeholder: field.placeholder || field.label,
|
placeholder: field.placeholder || field.label,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -122,6 +122,7 @@ const sections = computed(() => {
|
|||||||
} else {
|
} else {
|
||||||
_sections[_sections.length - 1].fields.push({
|
_sections[_sections.length - 1].fields.push({
|
||||||
...field,
|
...field,
|
||||||
|
filters: field.link_filters && JSON.parse(field.link_filters),
|
||||||
display_via_depends_on: evaluate_depends_on_value(
|
display_via_depends_on: evaluate_depends_on_value(
|
||||||
field.depends_on,
|
field.depends_on,
|
||||||
data.doc,
|
data.doc,
|
||||||
|
|||||||
80
frontend/src/composables/useActiveTabManager.js
Normal file
80
frontend/src/composables/useActiveTabManager.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useDebounceFn, useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
export function useActiveTabManager(tabs, storageKey) {
|
||||||
|
const activieTab = useStorage(storageKey, 'activity')
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const preserveLastVisitedTab = useDebounceFn((tabName) => {
|
||||||
|
activieTab.value = tabName.toLowerCase()
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
function setActiveTabInUrl(tabName) {
|
||||||
|
window.location.hash = '#' + tabName.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveTabFromUrl() {
|
||||||
|
return route.hash.replace('#', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTabIndex(tabName) {
|
||||||
|
return tabs.value.findIndex(
|
||||||
|
(tabOptions) => tabOptions.name.toLowerCase() === tabName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTabIndex(tabName) {
|
||||||
|
let index = findTabIndex(tabName)
|
||||||
|
return index !== -1 ? index : 0 // Default to the first tab if not found
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveTabFromLocalStorage() {
|
||||||
|
return activieTab.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveTab() {
|
||||||
|
let activeTab = getActiveTabFromUrl()
|
||||||
|
if (activeTab) {
|
||||||
|
let index = findTabIndex(activeTab)
|
||||||
|
if (index !== -1) {
|
||||||
|
preserveLastVisitedTab(activeTab)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastVisitedTab = getActiveTabFromLocalStorage()
|
||||||
|
if (lastVisitedTab) {
|
||||||
|
setActiveTabInUrl(lastVisitedTab)
|
||||||
|
return getTabIndex(lastVisitedTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0 // Default to the first tab if nothing is found
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabIndex = ref(getActiveTab())
|
||||||
|
|
||||||
|
watch(tabIndex, (tabIndexValue) => {
|
||||||
|
let currentTab = tabs.value[tabIndexValue].name
|
||||||
|
setActiveTabInUrl(currentTab)
|
||||||
|
preserveLastVisitedTab(currentTab)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.hash,
|
||||||
|
(tabValue) => {
|
||||||
|
if (!tabValue) return
|
||||||
|
|
||||||
|
let tabName = tabValue.replace('#', '')
|
||||||
|
let index = findTabIndex(tabName)
|
||||||
|
if (index === -1) index = 0
|
||||||
|
|
||||||
|
let currentTab = tabs.value[index].name
|
||||||
|
preserveLastVisitedTab(currentTab)
|
||||||
|
tabIndex.value = index
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return { tabIndex }
|
||||||
|
}
|
||||||
@ -354,6 +354,8 @@ import {
|
|||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, h, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, computed, h, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useActiveTabManager } from '@/composables/useActiveTabManager'
|
||||||
|
|
||||||
|
|
||||||
const { $dialog, $socket, makeCall } = globalStore()
|
const { $dialog, $socket, makeCall } = globalStore()
|
||||||
const { statusOptions, getDealStatus } = statusesStore()
|
const { statusOptions, getDealStatus } = statusesStore()
|
||||||
@ -376,10 +378,13 @@ const deal = createResource({
|
|||||||
params: { name: props.dealId },
|
params: { name: props.dealId },
|
||||||
cache: ['deal', props.dealId],
|
cache: ['deal', props.dealId],
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
organization.update({
|
if (data.organization) {
|
||||||
params: { doctype: 'CRM Organization', name: data.organization },
|
organization.update({
|
||||||
})
|
params: { doctype: 'CRM Organization', name: data.organization },
|
||||||
organization.fetch()
|
})
|
||||||
|
organization.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
let obj = {
|
let obj = {
|
||||||
doc: data,
|
doc: data,
|
||||||
$dialog,
|
$dialog,
|
||||||
@ -513,7 +518,6 @@ usePageMeta(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabIndex = ref(0)
|
|
||||||
const tabs = computed(() => {
|
const tabs = computed(() => {
|
||||||
let tabOptions = [
|
let tabOptions = [
|
||||||
{
|
{
|
||||||
@ -556,6 +560,7 @@ const tabs = computed(() => {
|
|||||||
]
|
]
|
||||||
return tabOptions.filter((tab) => (tab.condition ? tab.condition() : true))
|
return tabOptions.filter((tab) => (tab.condition ? tab.condition() : true))
|
||||||
})
|
})
|
||||||
|
const { tabIndex } = useActiveTabManager(tabs, 'lastDealTab')
|
||||||
|
|
||||||
const fieldsLayout = createResource({
|
const fieldsLayout = createResource({
|
||||||
url: 'crm.api.doc.get_sidebar_fields',
|
url: 'crm.api.doc.get_sidebar_fields',
|
||||||
|
|||||||
@ -330,6 +330,7 @@ import {
|
|||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { useActiveTabManager } from '@/composables/useActiveTabManager'
|
||||||
|
|
||||||
const { $dialog, $socket, makeCall } = globalStore()
|
const { $dialog, $socket, makeCall } = globalStore()
|
||||||
const { getContactByName, contacts } = contactsStore()
|
const { getContactByName, contacts } = contactsStore()
|
||||||
@ -463,8 +464,6 @@ usePageMeta(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabIndex = ref(0)
|
|
||||||
|
|
||||||
const tabs = computed(() => {
|
const tabs = computed(() => {
|
||||||
let tabOptions = [
|
let tabOptions = [
|
||||||
{
|
{
|
||||||
@ -508,6 +507,8 @@ const tabs = computed(() => {
|
|||||||
return tabOptions.filter((tab) => (tab.condition ? tab.condition() : true))
|
return tabOptions.filter((tab) => (tab.condition ? tab.condition() : true))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { tabIndex } = useActiveTabManager(tabs, 'lastLeadTab')
|
||||||
|
|
||||||
watch(tabs, (value) => {
|
watch(tabs, (value) => {
|
||||||
if (value && route.params.tabName) {
|
if (value && route.params.tabName) {
|
||||||
let index = value.findIndex(
|
let index = value.findIndex(
|
||||||
|
|||||||
@ -309,10 +309,13 @@ const deal = createResource({
|
|||||||
params: { name: props.dealId },
|
params: { name: props.dealId },
|
||||||
cache: ['deal', props.dealId],
|
cache: ['deal', props.dealId],
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
organization.update({
|
if (data.organization) {
|
||||||
params: { doctype: 'CRM Organization', name: data.organization },
|
organization.update({
|
||||||
})
|
params: { doctype: 'CRM Organization', name: data.organization },
|
||||||
organization.fetch()
|
})
|
||||||
|
organization.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
let obj = {
|
let obj = {
|
||||||
doc: data,
|
doc: data,
|
||||||
$dialog,
|
$dialog,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user