fix: refactor EventArea to improve event rendering and participant handling

This commit is contained in:
Shariq Ansari 2025-09-02 17:16:06 +05:30
parent ae5a1ceae5
commit da7ee0926f
2 changed files with 151 additions and 133 deletions

View File

@ -21,10 +21,12 @@
<LoadingIndicator class="h-6 w-6" />
<span>{{ __('Loading...') }}</span>
</div>
<div v-else-if="title == 'Events'" class="activity">
<EventArea :doctype="doctype" :docname="docname" />
</div>
<div
v-else-if="
activities?.length ||
(events.data?.length && title == 'Events') ||
(whatsappMessages.data?.length && title == 'WhatsApp')
"
class="activities"
@ -37,33 +39,6 @@
:messages="whatsappMessages.data"
/>
</div>
<div v-else-if="title == 'Events'" class="activity">
<div v-for="(event, i) in events.data" :key="event.name">
<div
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-3 sm:px-10"
>
<div
class="z-0 relative flex justify-center before:absolute before:left-[50%] before:-z-[1] before:top-0 before:border-l before:border-outline-gray-modals"
:class="
i != events.data.length - 1 ? 'before:h-full' : 'before:h-4'
"
>
<div
class="flex h-8 w-7 items-center justify-center bg-surface-white text-ink-gray-8"
>
<CalendarIcon class="h-4 w-4" />
</div>
</div>
<EventArea
class="mb-4"
v-model="events"
:event="event"
:doctype="doctype"
:docname="doc?.name"
/>
</div>
</div>
</div>
<div
v-else-if="title == 'Notes'"
class="grid grid-cols-1 gap-4 px-3 pb-3 sm:px-10 sm:pb-5 lg:grid-cols-2 xl:grid-cols-3"
@ -529,7 +504,7 @@ import { usersStore } from '@/stores/users'
import { whatsappEnabled, callEnabled } from '@/composables/settings'
import { useDocument } from '@/data/document'
import { capture } from '@/telemetry'
import { Button, Tooltip, createResource, createListResource } from 'frappe-ui'
import { Button, Tooltip, createResource } from 'frappe-ui'
import { useElementVisibility } from '@vueuse/core'
import {
ref,
@ -610,47 +585,6 @@ const whatsappMessages = createResource({
onSuccess: () => nextTick(() => scroll()),
})
const events = createListResource({
doctype: 'Event',
cache: ['calendar', props.docname],
fields: [
'name',
'status',
'subject',
'description',
'starts_on',
'ends_on',
'all_day',
'event_type',
'color',
'owner',
'reference_doctype',
'reference_docname',
'creation',
],
filters: {
status: 'Open',
reference_doctype: props.doctype,
reference_docname: props.docname,
},
orderBy: 'creation desc',
auto: title.value == 'Events',
transform: (data) => {
return data.map((event) => {
if (typeof event.owner !== 'object') {
event.owner = {
label: getUser(event.owner).full_name,
image: getUser(event.owner).image,
}
}
return event
})
},
onSuccess: (d) => {
console.log(d)
},
})
onBeforeUnmount(() => {
$socket.off('whatsapp_message')
})

View File

@ -1,57 +1,67 @@
<template>
<div class="mb-5">
<div class="mb-1 flex items-center justify-stretch gap-2 py-1 text-base">
<div class="inline-flex items-center flex-wrap gap-1 text-ink-gray-5">
<Avatar
:image="event.owner.image"
:label="event.owner.label"
size="md"
/>
<span class="font-medium text-ink-gray-8 ml-1">
{{ event.owner.label }}
</span>
<span>{{ 'has created an event' }}</span>
</div>
<div class="ml-auto whitespace-nowrap">
<Tooltip :text="formatDate(event.creation)">
<div class="text-sm text-ink-gray-5">
{{ __(timeAgo(event.creation)) }}
</div>
</Tooltip>
</div>
</div>
<div v-for="(event, i) in events" :key="event.name">
<div
class="flex gap-2 border cursor-pointer border-outline-gray-modals rounded-lg bg-surface-cards px-2.5 py-2.5 text-ink-gray-9"
@click="showEvent(event)"
class="activity grid grid-cols-[30px_minmax(auto,_1fr)] gap-4 px-3 sm:px-10"
>
<div
class="flex w-[2px] rounded-lg"
:style="{ backgroundColor: event.color || '#30A66D' }"
/>
<div class="flex-1 flex flex-col gap-1 text-base">
class="z-0 relative flex justify-center before:absolute before:left-[50%] before:-z-[1] before:top-0 before:border-l before:border-outline-gray-modals"
:class="i != events.length - 1 ? 'before:h-full' : 'before:h-4'"
>
<div
class="flex items-center justify-between gap-2 font-medium text-ink-gray-7"
class="flex h-8 w-7 items-center justify-center bg-surface-white text-ink-gray-8"
>
<div>{{ event.subject }}</div>
<MultipleAvatar
:avatars="[
{
image: event.owner.image,
label: event.owner.label,
name: event.owner.label,
},
{
image: event.owner.image,
label: event.owner.label,
name: event.owner.label,
},
]"
size="sm"
/>
<CalendarIcon class="h-4 w-4" />
</div>
<div class="flex justify-between gap-2 items-center text-ink-gray-6">
<div>{{ formattedDateTime }}</div>
<div>{{ formattedDate }}</div>
</div>
<div class="mb-5">
<div
class="mb-1 flex items-center justify-stretch gap-2 py-1 text-base"
>
<div class="inline-flex items-center flex-wrap gap-1 text-ink-gray-5">
<Avatar
:image="event.owner.image"
:label="event.owner.label"
size="md"
/>
<span class="font-medium text-ink-gray-8 ml-1">
{{ event.owner.label }}
</span>
<span>{{ 'has created an event' }}</span>
</div>
<div class="ml-auto whitespace-nowrap">
<Tooltip :text="formatDate(event.creation)">
<div class="text-sm text-ink-gray-5">
{{ __(timeAgo(event.creation)) }}
</div>
</Tooltip>
</div>
</div>
<div
class="flex gap-2 border cursor-pointer border-outline-gray-modals rounded-lg bg-surface-cards px-2.5 py-2.5 text-ink-gray-9"
@click="showEvent(event)"
>
<div
class="flex w-[2px] rounded-lg"
:style="{ backgroundColor: event.color || '#30A66D' }"
/>
<div class="flex-1 flex flex-col gap-1 text-base">
<div
class="flex items-center justify-between gap-2 font-medium text-ink-gray-7"
>
<div>{{ event.subject }}</div>
<MultipleAvatar
v-if="event.participants?.length > 1"
:avatars="event.participants"
size="sm"
/>
</div>
<div
class="flex justify-between gap-2 items-center text-ink-gray-6"
>
<div>{{ formattedDateTime(event) }}</div>
<div>{{ formattedDate(event) }}</div>
</div>
</div>
</div>
</div>
</div>
@ -59,24 +69,22 @@
<EventModal
v-if="showEventModal"
v-model="showEventModal"
v-model:events="events"
v-model:events="eventsResource"
:event="event"
:doctype="doctype"
:docname="docname"
/>
</template>
<script setup>
import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
import EventModal from '@/components/Modals/EventModal.vue'
import MultipleAvatar from '@/components/MultipleAvatar.vue'
import { Tooltip, Avatar, dayjs } from 'frappe-ui'
import { usersStore } from '@/stores/users'
import { formatDate, timeAgo } from '@/utils'
import { Tooltip, Avatar, dayjs, createListResource } from 'frappe-ui'
import { computed, ref } from 'vue'
const props = defineProps({
event: {
type: Object,
required: true,
},
doctype: {
type: String,
default: '',
@ -87,27 +95,103 @@ const props = defineProps({
},
})
const events = defineModel()
const { getUser } = usersStore()
const formattedDateTime = computed(() => {
const start = dayjs(props.event.starts_on)
const end = dayjs(props.event.ends_on)
const eventsResource = createListResource({
doctype: 'Event',
cache: ['calendar', props.docname],
fields: [
'name',
'status',
'subject',
'description',
'starts_on',
'ends_on',
'all_day',
'event_type',
'color',
'owner',
'reference_doctype',
'reference_docname',
'creation',
],
filters: {
reference_doctype: props.doctype,
reference_docname: props.docname,
},
auto: true,
orderBy: 'creation desc',
onSuccess: (d) => {
console.log(d)
},
})
if (props.event.all_day) {
const eventParticipants = createListResource({
doctype: 'Event Participants',
cache: ['Event Participants', props.docname],
fields: ['*'],
parent: 'Event',
})
const events = computed(() => {
if (!eventsResource.data) return []
if (!eventParticipants.data?.length) {
eventParticipants.update({
filters: {
parenttype: 'Event',
parentfield: 'event_participants',
parent: ['in', eventsResource.data.map((e) => e.name)],
},
})
!eventParticipants.list.loading && eventParticipants.reload()
} else {
eventsResource.data.forEach((event) => {
if (typeof event.owner !== 'object') {
event.owner = {
label: getUser(event.owner).full_name,
image: getUser(event.owner).user_image,
name: event.owner,
}
}
event.participants = [
event.owner,
...eventParticipants.data
.filter((participant) => participant.parent === event.name)
.map((participant) => ({
label: getUser(participant.email).full_name || participant.email,
image: getUser(participant.email).user_image || '',
name: participant.email,
})),
]
})
}
return eventsResource.data
})
const formattedDateTime = (e) => {
const start = dayjs(e.starts_on)
const end = dayjs(e.ends_on)
if (e.all_day) {
return __('All day')
}
return `${start.format('h:mm a')} - ${end.format('h:mm a')}`
})
}
const formattedDate = computed(() => {
const start = dayjs(props.event.starts_on)
const formattedDate = (e) => {
const start = dayjs(e.starts_on)
return start.format('ddd, D MMM YYYY')
})
}
const showEventModal = ref(false)
const event = ref(null)
function showEvent() {
function showEvent(e) {
showEventModal.value = true
event.value = e
}
</script>