feat: remove CommunicationsQuery, load messages on-demand only
- Remove bulk CommunicationsQuery from useContacts (was loading ALL messages for ALL contacts on init) - Rebuild commThreads from contacts + contactInboxes using the new lastMessageText field from Phase 1 - Per-contact messages now load on-demand via getClientTimeline - Remove commItems from useWorkspaceRouting, use clientTimelineItems Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -95,7 +95,6 @@ const {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
const {
|
const {
|
||||||
contacts,
|
contacts,
|
||||||
commItems,
|
|
||||||
contactSearch,
|
contactSearch,
|
||||||
selectedChannel,
|
selectedChannel,
|
||||||
sortMode,
|
sortMode,
|
||||||
@@ -110,7 +109,6 @@ const {
|
|||||||
markAvatarBroken,
|
markAvatarBroken,
|
||||||
contactInitials,
|
contactInitials,
|
||||||
refetchContacts,
|
refetchContacts,
|
||||||
refetchCommunications,
|
|
||||||
} = useContacts({ apolloAuthReady });
|
} = useContacts({ apolloAuthReady });
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -301,7 +299,6 @@ const {
|
|||||||
async function refetchAllCrmQueries() {
|
async function refetchAllCrmQueries() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
refetchContacts(),
|
refetchContacts(),
|
||||||
refetchCommunications(),
|
|
||||||
refetchContactInboxes(),
|
refetchContactInboxes(),
|
||||||
refetchCalendar(),
|
refetchCalendar(),
|
||||||
refetchDeals(),
|
refetchDeals(),
|
||||||
@@ -461,59 +458,36 @@ const pilotHeaderPhrases = [
|
|||||||
const pilotHeaderText = ref("Every step moves you forward");
|
const pilotHeaderText = ref("Every step moves you forward");
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Comm Threads (glue: contacts + commItems + contactInboxes)
|
// Comm Threads (glue: contacts + contactInboxes — no bulk message loading)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
const commThreads = computed(() => {
|
const commThreads = computed(() => {
|
||||||
const sorted = [...commItems.value].sort((a, b) => a.at.localeCompare(b.at));
|
|
||||||
const map = new Map<string, CommItem[]>();
|
|
||||||
for (const item of sorted) {
|
|
||||||
if (!map.has(item.contact)) map.set(item.contact, []);
|
|
||||||
map.get(item.contact)?.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contactById = new Map(contacts.value.map((contact) => [contact.id, contact]));
|
|
||||||
const inboxesByContactId = new Map<string, ContactInbox[]>();
|
const inboxesByContactId = new Map<string, ContactInbox[]>();
|
||||||
for (const inbox of contactInboxes.value) {
|
for (const inbox of contactInboxes.value) {
|
||||||
if (!inboxesByContactId.has(inbox.contactId)) inboxesByContactId.set(inbox.contactId, []);
|
if (!inboxesByContactId.has(inbox.contactId)) inboxesByContactId.set(inbox.contactId, []);
|
||||||
inboxesByContactId.get(inbox.contactId)?.push(inbox);
|
inboxesByContactId.get(inbox.contactId)!.push(inbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contactIds = new Set<string>([
|
return contacts.value
|
||||||
...contacts.value.map((contact) => contact.id),
|
.map((c) => {
|
||||||
...contactInboxes.value.map((inbox) => inbox.contactId),
|
const inboxes = inboxesByContactId.get(c.id) ?? [];
|
||||||
]);
|
|
||||||
|
|
||||||
return [...contactIds]
|
|
||||||
.map((contactId) => {
|
|
||||||
const contact = contactById.get(contactId);
|
|
||||||
const inboxes = inboxesByContactId.get(contactId) ?? [];
|
|
||||||
const contactName = contact?.name ?? inboxes[0]?.contactName ?? "";
|
|
||||||
const items = map.get(contactName) ?? [];
|
|
||||||
const last = items[items.length - 1];
|
|
||||||
const channels = [
|
const channels = [
|
||||||
...new Set([
|
...new Set([
|
||||||
...(contact?.channels ?? []),
|
...c.channels,
|
||||||
...inboxes.map((inbox) => inbox.channel),
|
...inboxes.map((i) => i.channel),
|
||||||
...items.map((item) => item.channel),
|
|
||||||
]),
|
]),
|
||||||
] as CommItem["channel"][];
|
] as CommItem["channel"][];
|
||||||
const inboxFallbackLast = inboxes
|
|
||||||
.map((inbox) => inbox.lastMessageAt || inbox.updatedAt)
|
|
||||||
.filter(Boolean)
|
|
||||||
.sort()
|
|
||||||
.at(-1);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: contactId,
|
id: c.id,
|
||||||
contact: contactName,
|
contact: c.name,
|
||||||
avatar: contact?.avatar ?? "",
|
avatar: c.avatar,
|
||||||
channels,
|
channels,
|
||||||
lastAt: last?.at ?? contact?.lastContactAt ?? inboxFallbackLast ?? "",
|
lastAt: c.lastContactAt,
|
||||||
lastText: last?.text ?? "No messages yet",
|
lastText: c.lastMessageText || "No messages yet",
|
||||||
items,
|
items: [] as CommItem[],
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((thread) => thread.contact)
|
.filter((t) => t.contact)
|
||||||
.sort((a, b) => b.lastAt.localeCompare(a.lastAt));
|
.sort((a, b) => b.lastAt.localeCompare(a.lastAt));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -722,7 +696,7 @@ const routing = useWorkspaceRouting({
|
|||||||
commThreads,
|
commThreads,
|
||||||
contacts,
|
contacts,
|
||||||
deals,
|
deals,
|
||||||
commItems,
|
clientTimelineItems,
|
||||||
activeChangeMessage,
|
activeChangeMessage,
|
||||||
activeChangeItem,
|
activeChangeItem,
|
||||||
activeChangeItems,
|
activeChangeItems,
|
||||||
@@ -958,8 +932,9 @@ const selectedContactEvents = computed(() => {
|
|||||||
|
|
||||||
const selectedContactRecentMessages = computed(() => {
|
const selectedContactRecentMessages = computed(() => {
|
||||||
if (!selectedContact.value) return [];
|
if (!selectedContact.value) return [];
|
||||||
return commItems.value
|
return clientTimelineItems.value
|
||||||
.filter((item) => item.contact === selectedContact.value?.name && item.kind === "message")
|
.filter((entry) => entry.contentType === "message" && entry.message)
|
||||||
|
.map((entry) => entry.message!)
|
||||||
.sort((a, b) => b.at.localeCompare(a.at))
|
.sort((a, b) => b.at.localeCompare(a.at))
|
||||||
.slice(0, 8);
|
.slice(0, 8);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { ref, computed, watch, watchEffect, type ComputedRef } from "vue";
|
import { ref, computed, watch, watchEffect, type ComputedRef } from "vue";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
import {
|
import { ContactsQueryDocument } from "~~/graphql/generated";
|
||||||
ContactsQueryDocument,
|
|
||||||
CommunicationsQueryDocument,
|
|
||||||
} from "~~/graphql/generated";
|
|
||||||
|
|
||||||
export type Contact = {
|
export type Contact = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -11,6 +8,8 @@ export type Contact = {
|
|||||||
avatar: string;
|
avatar: string;
|
||||||
channels: string[];
|
channels: string[];
|
||||||
lastContactAt: string;
|
lastContactAt: string;
|
||||||
|
lastMessageText: string;
|
||||||
|
lastMessageChannel: string;
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,32 +40,13 @@ export function useContacts(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
{ enabled: opts.apolloAuthReady },
|
{ enabled: opts.apolloAuthReady },
|
||||||
);
|
);
|
||||||
|
|
||||||
const { result: communicationsResult, refetch: refetchCommunications } = useQuery(
|
|
||||||
CommunicationsQueryDocument,
|
|
||||||
null,
|
|
||||||
{ enabled: opts.apolloAuthReady },
|
|
||||||
);
|
|
||||||
|
|
||||||
const contacts = ref<Contact[]>([]);
|
const contacts = ref<Contact[]>([]);
|
||||||
const commItems = ref<CommItem[]>([]);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => contactsResult.value?.contacts, () => communicationsResult.value?.communications],
|
() => contactsResult.value?.contacts,
|
||||||
([rawContacts, rawComms]) => {
|
(rawContacts) => {
|
||||||
if (!rawContacts) return;
|
if (!rawContacts) return;
|
||||||
const contactsList = [...rawContacts] as Contact[];
|
contacts.value = [...rawContacts] as Contact[];
|
||||||
const commsList = (rawComms ?? []) as CommItem[];
|
|
||||||
|
|
||||||
const byName = new Map<string, Set<string>>();
|
|
||||||
for (const item of commsList) {
|
|
||||||
if (!byName.has(item.contact)) byName.set(item.contact, new Set());
|
|
||||||
byName.get(item.contact)?.add(item.channel);
|
|
||||||
}
|
|
||||||
contacts.value = contactsList.map((c) => ({
|
|
||||||
...c,
|
|
||||||
channels: Array.from(byName.get(c.name) ?? c.channels ?? []),
|
|
||||||
}));
|
|
||||||
commItems.value = commsList;
|
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
@@ -166,7 +146,6 @@ export function useContacts(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
contacts,
|
contacts,
|
||||||
commItems,
|
|
||||||
contactSearch,
|
contactSearch,
|
||||||
selectedChannel,
|
selectedChannel,
|
||||||
sortMode,
|
sortMode,
|
||||||
@@ -181,6 +160,5 @@ export function useContacts(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
|||||||
markAvatarBroken,
|
markAvatarBroken,
|
||||||
contactInitials,
|
contactInitials,
|
||||||
refetchContacts,
|
refetchContacts,
|
||||||
refetchCommunications,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function useWorkspaceRouting(opts: {
|
|||||||
commThreads: ComputedRef<{ id: string; [key: string]: any }[]>;
|
commThreads: ComputedRef<{ id: string; [key: string]: any }[]>;
|
||||||
contacts: Ref<{ id: string; name: string; [key: string]: any }[]>;
|
contacts: Ref<{ id: string; name: string; [key: string]: any }[]>;
|
||||||
deals: Ref<{ id: string; contact: string; [key: string]: any }[]>;
|
deals: Ref<{ id: string; contact: string; [key: string]: any }[]>;
|
||||||
commItems: Ref<{ id: string; contact: string; [key: string]: any }[]>;
|
clientTimelineItems: Ref<{ id: string; contactId: string; contentType: string; message?: { contact: string } | null; [key: string]: any }[]>;
|
||||||
activeChangeMessage: ComputedRef<{ changeSetId?: string | null; changeItems?: PilotChangeItem[] | null } | null>;
|
activeChangeMessage: ComputedRef<{ changeSetId?: string | null; changeItems?: PilotChangeItem[] | null } | null>;
|
||||||
activeChangeItem: ComputedRef<PilotChangeItem | null>;
|
activeChangeItem: ComputedRef<PilotChangeItem | null>;
|
||||||
activeChangeItems: ComputedRef<PilotChangeItem[]>;
|
activeChangeItems: ComputedRef<PilotChangeItem[]>;
|
||||||
@@ -340,9 +340,9 @@ export function useWorkspaceRouting(opts: {
|
|||||||
if (item.entity === "message" && item.entityId) {
|
if (item.entity === "message" && item.entityId) {
|
||||||
opts.peopleLeftMode.value = "contacts";
|
opts.peopleLeftMode.value = "contacts";
|
||||||
opts.peopleListMode.value = "contacts";
|
opts.peopleListMode.value = "contacts";
|
||||||
const message = opts.commItems.value.find((entry) => entry.id === item.entityId);
|
const timelineEntry = opts.clientTimelineItems.value.find((entry) => entry.contentType === "message" && entry.message && entry.id === item.entityId);
|
||||||
if (message?.contact) {
|
if (timelineEntry?.message?.contact) {
|
||||||
opts.openCommunicationThread(message.contact);
|
opts.openCommunicationThread(timelineEntry.message.contact);
|
||||||
}
|
}
|
||||||
opts.focusedCalendarEventId.value = "";
|
opts.focusedCalendarEventId.value = "";
|
||||||
syncPathFromUi(push);
|
syncPathFromUi(push);
|
||||||
|
|||||||
Reference in New Issue
Block a user