diff --git a/frontend/components.d.ts b/frontend/components.d.ts index afd1538b..7946766c 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -40,6 +40,7 @@ declare module 'vue' { AttachmentArea: typeof import('./src/components/Activities/AttachmentArea.vue')['default'] AttachmentIcon: typeof import('./src/components/Icons/AttachmentIcon.vue')['default'] AttachmentItem: typeof import('./src/components/AttachmentItem.vue')['default'] + Attendee: typeof import('./src/components/Calendar/Attendee.vue')['default'] AudioPlayer: typeof import('./src/components/Activities/AudioPlayer.vue')['default'] Autocomplete: typeof import('./src/components/frappe-ui/Autocomplete.vue')['default'] AvatarIcon: typeof import('./src/components/Icons/AvatarIcon.vue')['default'] @@ -195,7 +196,6 @@ declare module 'vue' { LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default'] LucideCalendar: typeof import('~icons/lucide/calendar')['default'] LucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default'] - LucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] LucideCopy: typeof import('~icons/lucide/copy')['default'] LucideTrash2: typeof import('~icons/lucide/trash2')['default'] LucideX: typeof import('~icons/lucide/x')['default'] @@ -226,6 +226,7 @@ declare module 'vue' { OutboundCallIcon: typeof import('./src/components/Icons/OutboundCallIcon.vue')['default'] Password: typeof import('./src/components/Controls/Password.vue')['default'] PauseIcon: typeof import('./src/components/Icons/PauseIcon.vue')['default'] + PeopleIcon: typeof import('./src/components/Icons/PeopleIcon.vue')['default'] PhoneIcon: typeof import('./src/components/Icons/PhoneIcon.vue')['default'] PinIcon: typeof import('./src/components/Icons/PinIcon.vue')['default'] PlaybackSpeedIcon: typeof import('./src/components/Icons/PlaybackSpeedIcon.vue')['default'] diff --git a/frontend/src/components/Calendar/Attendee.vue b/frontend/src/components/Calendar/Attendee.vue new file mode 100644 index 00000000..b7b85d56 --- /dev/null +++ b/frontend/src/components/Calendar/Attendee.vue @@ -0,0 +1,318 @@ + + + diff --git a/frontend/src/components/Calendar/CalendarEventPanel.vue b/frontend/src/components/Calendar/CalendarEventPanel.vue index 5cbdc642..a308de98 100644 --- a/frontend/src/components/Calendar/CalendarEventPanel.vue +++ b/frontend/src/components/Calendar/CalendarEventPanel.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/components/Icons/PeopleIcon.vue b/frontend/src/components/Icons/PeopleIcon.vue new file mode 100644 index 00000000..300e28ab --- /dev/null +++ b/frontend/src/components/Icons/PeopleIcon.vue @@ -0,0 +1,14 @@ + diff --git a/frontend/src/pages/Calendar.vue b/frontend/src/pages/Calendar.vue index 99f0aa79..afa2590e 100644 --- a/frontend/src/pages/Calendar.vue +++ b/frontend/src/pages/Calendar.vue @@ -4,7 +4,14 @@ @@ -93,6 +100,7 @@ @duplicate="duplicateEvent" @details="showDetails" @close="close" + @sync="syncEvent" /> @@ -108,6 +116,7 @@ import { TabButtons, dayjs, CalendarActiveEvent as activeEvent, + call, } from 'frappe-ui' import { onMounted, ref } from 'vue' @@ -186,6 +195,7 @@ function createEvent(_event) { color: _event.color, reference_doctype: _event.referenceDoctype, reference_docname: _event.referenceDocname, + event_participants: _event.event_participants, }, { onSuccess: async (e) => { @@ -196,10 +206,19 @@ function createEvent(_event) { ) } -function updateEvent(_event) { +async function updateEvent(_event) { if (!_event.id) return - if (!mode.value || mode.value === 'edit' || mode.value === 'details') { + // Ensure Contacts exist for participants referencing a new/unknown Contact, if not create them + if ( + Array.isArray(_event.event_participants) && + _event.event_participants.length + ) { + _event.event_participants = await ensureParticipantContacts( + _event.event_participants, + ) + } + events.setValue.submit( { name: _event.id, @@ -212,6 +231,7 @@ function updateEvent(_event) { color: _event.color, reference_doctype: _event.referenceDoctype, reference_docname: _event.referenceDocname, + event_participants: _event.event_participants, }, { onSuccess: async (e) => { @@ -221,8 +241,6 @@ function updateEvent(_event) { }, ) } - - event.value = _event } function deleteEvent(eventID) { @@ -251,6 +269,11 @@ function deleteEvent(eventID) { }) } +function syncEvent(eventID, _event) { + if (!eventID) return + Object.assign(events.data.filter((event) => event.id === eventID)[0], _event) +} + onMounted(() => { activeEvent.value = '' mode.value = '' @@ -270,7 +293,7 @@ function showDetails(e) { ) showEventPanel.value = true - event.value = events.data.find((ev) => ev.id === _e.id) || _e + event.value = { id: _e.id } activeEvent.value = _e.id mode.value = 'details' } @@ -284,7 +307,7 @@ function editDetails(e) { ) showEventPanel.value = true - event.value = events.data.find((ev) => ev.id === _e.id) || _e + event.value = { id: _e.id } activeEvent.value = _e.id mode.value = 'edit' } @@ -320,6 +343,9 @@ function newEvent(e, duplicate = false) { isFullDay: e.isFullDay || false, eventType: e.eventType || 'Public', color: e.color || 'green', + referenceDoctype: e.referenceDoctype, + referenceDocname: e.referenceDocname, + event_participants: e.event_participants || [], } events.data.push(event.value) @@ -384,4 +410,34 @@ function getFromToTime(time) { return [fromTime, toTime] } + +// Helper: create Contact docs for participants missing reference_docname +async function ensureParticipantContacts(participants) { + if (!Array.isArray(participants) || !participants.length) return participants + const updated = [] + for (const part of participants) { + const p = { ...part } + try { + if ( + p.reference_doctype === 'Contact' && + (!p.reference_docname || p.reference_docname === 'new') && + p.email + ) { + const firstName = p.email.split('@')[0] || p.email + const contactDoc = await call('frappe.client.insert', { + doc: { + doctype: 'Contact', + first_name: firstName, + email_ids: [{ email_id: p.email, is_primary: 1 }], + }, + }) + if (contactDoc?.name) p.reference_docname = contactDoc.name + } + } catch (e) { + console.error('Failed creating contact for participant', p.email, e) + } + updated.push(p) + } + return updated +}