feat: group timeline changes
This commit is contained in:
parent
463ead3671
commit
4eb8a5aa77
@ -93,5 +93,39 @@ def get_activities(name):
|
|||||||
activities.append(activity)
|
activities.append(activity)
|
||||||
|
|
||||||
activities.sort(key=lambda x: x["creation"], reverse=True)
|
activities.sort(key=lambda x: x["creation"], reverse=True)
|
||||||
|
activities = handle_multiple_versions(activities)
|
||||||
|
|
||||||
return activities
|
return activities
|
||||||
|
|
||||||
|
def handle_multiple_versions(versions):
|
||||||
|
activities = []
|
||||||
|
grouped_versions = []
|
||||||
|
old_version = None
|
||||||
|
for version in versions:
|
||||||
|
is_version = version["activity_type"] in ["changed", "added", "removed"]
|
||||||
|
if not is_version:
|
||||||
|
activities.append(version)
|
||||||
|
if not old_version:
|
||||||
|
old_version = version
|
||||||
|
if is_version: grouped_versions.append(version)
|
||||||
|
continue
|
||||||
|
if is_version and old_version.get("owner") and version["owner"] == old_version["owner"]:
|
||||||
|
grouped_versions.append(version)
|
||||||
|
else:
|
||||||
|
if grouped_versions:
|
||||||
|
activities.append(parse_grouped_versions(grouped_versions))
|
||||||
|
grouped_versions = []
|
||||||
|
if is_version: grouped_versions.append(version)
|
||||||
|
old_version = version
|
||||||
|
if version == versions[-1] and grouped_versions:
|
||||||
|
activities.append(parse_grouped_versions(grouped_versions))
|
||||||
|
|
||||||
|
return activities
|
||||||
|
|
||||||
|
def parse_grouped_versions(versions):
|
||||||
|
version = versions[0]
|
||||||
|
if len(versions) == 1:
|
||||||
|
return version
|
||||||
|
other_versions = versions[1:]
|
||||||
|
version["other_versions"] = other_versions
|
||||||
|
return version
|
||||||
@ -170,8 +170,13 @@
|
|||||||
<div v-else v-for="(activity, i) in activities">
|
<div v-else v-for="(activity, i) in activities">
|
||||||
<div class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-10">
|
<div class="grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-10">
|
||||||
<div
|
<div
|
||||||
class="relative flex justify-center after:absolute after:left-[50%] after:top-0 after:-z-10 after:border-l after:border-gray-200"
|
class="relative flex justify-center before:absolute before:left-[50%] before:top-0 before:-z-10 before:border-l before:border-gray-200"
|
||||||
:class="i != activities.length - 1 ? 'after:h-full' : 'after:h-4'"
|
:class="[
|
||||||
|
i != activities.length - 1 ? 'before:h-full' : 'before:h-4',
|
||||||
|
activity.other_versions
|
||||||
|
? 'after:translate-y-[calc(-50% - 4px)] after:absolute after:bottom-9 after:left-[50%] after:top-0 after:-z-10 after:w-8 after:rounded-bl-xl after:border-b after:border-l after:border-gray-200'
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="z-10 flex h-7 w-7 items-center justify-center rounded-full bg-gray-100"
|
class="z-10 flex h-7 w-7 items-center justify-center rounded-full bg-gray-100"
|
||||||
@ -308,7 +313,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mb-4 flex flex-col gap-3 py-1.5">
|
<div v-else class="mb-4 flex flex-col gap-5 py-1.5">
|
||||||
<div class="flex items-start justify-stretch gap-2 text-base">
|
<div class="flex items-start justify-stretch gap-2 text-base">
|
||||||
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
||||||
<span class="font-medium text-gray-800">{{
|
<span class="font-medium text-gray-800">{{
|
||||||
@ -346,6 +351,60 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="activity.other_versions && activity.show_others"
|
||||||
|
v-for="activity in activity.other_versions"
|
||||||
|
class="flex items-start justify-stretch gap-2 text-base"
|
||||||
|
>
|
||||||
|
<div class="inline-flex flex-wrap gap-1 text-gray-600">
|
||||||
|
<span v-if="activity.type">{{ activity.type }}</span>
|
||||||
|
<span
|
||||||
|
v-if="activity.data.field_label"
|
||||||
|
class="max-w-xs truncate font-medium text-gray-800"
|
||||||
|
>
|
||||||
|
{{ activity.data.field_label }}
|
||||||
|
</span>
|
||||||
|
<span v-if="activity.value">{{ activity.value }}</span>
|
||||||
|
<span
|
||||||
|
v-if="activity.data.old_value"
|
||||||
|
class="max-w-xs truncate font-medium text-gray-800"
|
||||||
|
>
|
||||||
|
{{ activity.data.old_value }}
|
||||||
|
</span>
|
||||||
|
<span v-if="activity.to">to</span>
|
||||||
|
<span
|
||||||
|
v-if="activity.data.value"
|
||||||
|
class="max-w-xs truncate font-medium text-gray-800"
|
||||||
|
>
|
||||||
|
{{ activity.data.value }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-auto whitespace-nowrap">
|
||||||
|
<Tooltip
|
||||||
|
:text="dateFormat(activity.creation, dateTooltipFormat)"
|
||||||
|
class="text-gray-600"
|
||||||
|
>
|
||||||
|
{{ timeAgo(activity.creation) }}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="activity.other_versions">
|
||||||
|
<Button
|
||||||
|
:label="
|
||||||
|
activity.show_others ? 'Hide all changes' : 'Show all changes'
|
||||||
|
"
|
||||||
|
variant="outline"
|
||||||
|
@click="activity.show_others = !activity.show_others"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<FeatherIcon
|
||||||
|
:name="activity.show_others ? 'chevron-up' : 'chevron-down'"
|
||||||
|
class="h-4 text-gray-600"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -415,7 +474,7 @@ import {
|
|||||||
createListResource,
|
createListResource,
|
||||||
call,
|
call,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, computed, h, defineModel, markRaw } from 'vue'
|
import { ref, computed, h, defineModel, markRaw, watch } from 'vue'
|
||||||
|
|
||||||
const { getUser } = usersStore()
|
const { getUser } = usersStore()
|
||||||
const { getContact } = contactsStore()
|
const { getContact } = contactsStore()
|
||||||
@ -425,13 +484,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'Activity',
|
default: 'Activity',
|
||||||
},
|
},
|
||||||
activities: {
|
|
||||||
type: Array,
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const lead = defineModel()
|
const lead = defineModel()
|
||||||
|
const reload = defineModel('reload')
|
||||||
|
|
||||||
const versions = createResource({
|
const versions = createResource({
|
||||||
url: 'crm.fcrm.doctype.crm_lead.api.get_activities',
|
url: 'crm.fcrm.doctype.crm_lead.api.get_activities',
|
||||||
@ -535,28 +591,39 @@ const activities = computed(() => {
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
activity.owner_name = getUser(activity.owner).full_name
|
update_activities_details(activity)
|
||||||
activity.type = ''
|
|
||||||
activity.value = ''
|
|
||||||
activity.to = ''
|
|
||||||
|
|
||||||
if (activity.activity_type == 'creation') {
|
if (activity.other_versions) {
|
||||||
activity.type = activity.data
|
activity.show_others = false
|
||||||
} else if (activity.activity_type == 'added') {
|
activity.other_versions.forEach((other_version) => {
|
||||||
activity.type = 'added'
|
update_activities_details(other_version)
|
||||||
activity.value = 'value as'
|
})
|
||||||
} else if (activity.activity_type == 'removed') {
|
|
||||||
activity.type = 'removed'
|
|
||||||
activity.value = 'value'
|
|
||||||
} else if (activity.activity_type == 'changed') {
|
|
||||||
activity.type = 'changed'
|
|
||||||
activity.value = 'value from'
|
|
||||||
activity.to = 'to'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return activities
|
return activities
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function update_activities_details(activity) {
|
||||||
|
activity.owner_name = getUser(activity.owner).full_name
|
||||||
|
activity.type = ''
|
||||||
|
activity.value = ''
|
||||||
|
activity.to = ''
|
||||||
|
|
||||||
|
if (activity.activity_type == 'creation') {
|
||||||
|
activity.type = activity.data
|
||||||
|
} else if (activity.activity_type == 'added') {
|
||||||
|
activity.type = 'added'
|
||||||
|
activity.value = 'as'
|
||||||
|
} else if (activity.activity_type == 'removed') {
|
||||||
|
activity.type = 'removed'
|
||||||
|
activity.value = 'value'
|
||||||
|
} else if (activity.activity_type == 'changed') {
|
||||||
|
activity.type = 'changed'
|
||||||
|
activity.value = 'from'
|
||||||
|
activity.to = 'to'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const emptyText = computed(() => {
|
const emptyText = computed(() => {
|
||||||
let text = 'No emails communications'
|
let text = 'No emails communications'
|
||||||
if (props.title == 'Calls') {
|
if (props.title == 'Calls') {
|
||||||
@ -645,6 +712,13 @@ async function updateNote(note) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(reload, (value) => {
|
||||||
|
if (value) {
|
||||||
|
versions.reload()
|
||||||
|
reload.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -65,7 +65,11 @@
|
|||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.label"
|
:key="tab.label"
|
||||||
>
|
>
|
||||||
<Activities :title="tab.label" v-model="deal" />
|
<Activities
|
||||||
|
:title="tab.label"
|
||||||
|
v-model:reload="reload"
|
||||||
|
v-model="deal"
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</TabGroup>
|
</TabGroup>
|
||||||
@ -387,6 +391,8 @@ const deal = createResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reload = ref(false)
|
||||||
|
|
||||||
function updateDeal(fieldname, value) {
|
function updateDeal(fieldname, value) {
|
||||||
createResource({
|
createResource({
|
||||||
url: 'frappe.client.set_value',
|
url: 'frappe.client.set_value',
|
||||||
@ -400,6 +406,7 @@ function updateDeal(fieldname, value) {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
deal.reload()
|
deal.reload()
|
||||||
contacts.reload()
|
contacts.reload()
|
||||||
|
reload.value = true
|
||||||
createToast({
|
createToast({
|
||||||
title: 'Deal updated',
|
title: 'Deal updated',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
|
|||||||
@ -68,7 +68,11 @@
|
|||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.label"
|
:key="tab.label"
|
||||||
>
|
>
|
||||||
<Activities :title="tab.label" v-model="lead" />
|
<Activities
|
||||||
|
:title="tab.label"
|
||||||
|
v-model:reload="reload"
|
||||||
|
v-model="lead"
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</TabGroup>
|
</TabGroup>
|
||||||
@ -361,6 +365,8 @@ const lead = createResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reload = ref(false)
|
||||||
|
|
||||||
function updateLead(fieldname, value) {
|
function updateLead(fieldname, value) {
|
||||||
createResource({
|
createResource({
|
||||||
url: 'frappe.client.set_value',
|
url: 'frappe.client.set_value',
|
||||||
@ -374,6 +380,7 @@ function updateLead(fieldname, value) {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
lead.reload()
|
lead.reload()
|
||||||
contacts.reload()
|
contacts.reload()
|
||||||
|
reload.value = true
|
||||||
createToast({
|
createToast({
|
||||||
title: 'Lead updated',
|
title: 'Lead updated',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user