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 {
|
||||
contacts,
|
||||
commItems,
|
||||
contactSearch,
|
||||
selectedChannel,
|
||||
sortMode,
|
||||
@@ -110,7 +109,6 @@ const {
|
||||
markAvatarBroken,
|
||||
contactInitials,
|
||||
refetchContacts,
|
||||
refetchCommunications,
|
||||
} = useContacts({ apolloAuthReady });
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -301,7 +299,6 @@ const {
|
||||
async function refetchAllCrmQueries() {
|
||||
await Promise.all([
|
||||
refetchContacts(),
|
||||
refetchCommunications(),
|
||||
refetchContactInboxes(),
|
||||
refetchCalendar(),
|
||||
refetchDeals(),
|
||||
@@ -461,59 +458,36 @@ const pilotHeaderPhrases = [
|
||||
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 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[]>();
|
||||
for (const inbox of contactInboxes.value) {
|
||||
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>([
|
||||
...contacts.value.map((contact) => contact.id),
|
||||
...contactInboxes.value.map((inbox) => inbox.contactId),
|
||||
]);
|
||||
|
||||
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];
|
||||
return contacts.value
|
||||
.map((c) => {
|
||||
const inboxes = inboxesByContactId.get(c.id) ?? [];
|
||||
const channels = [
|
||||
...new Set([
|
||||
...(contact?.channels ?? []),
|
||||
...inboxes.map((inbox) => inbox.channel),
|
||||
...items.map((item) => item.channel),
|
||||
...c.channels,
|
||||
...inboxes.map((i) => i.channel),
|
||||
]),
|
||||
] as CommItem["channel"][];
|
||||
const inboxFallbackLast = inboxes
|
||||
.map((inbox) => inbox.lastMessageAt || inbox.updatedAt)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.at(-1);
|
||||
|
||||
return {
|
||||
id: contactId,
|
||||
contact: contactName,
|
||||
avatar: contact?.avatar ?? "",
|
||||
id: c.id,
|
||||
contact: c.name,
|
||||
avatar: c.avatar,
|
||||
channels,
|
||||
lastAt: last?.at ?? contact?.lastContactAt ?? inboxFallbackLast ?? "",
|
||||
lastText: last?.text ?? "No messages yet",
|
||||
items,
|
||||
lastAt: c.lastContactAt,
|
||||
lastText: c.lastMessageText || "No messages yet",
|
||||
items: [] as CommItem[],
|
||||
};
|
||||
})
|
||||
.filter((thread) => thread.contact)
|
||||
.filter((t) => t.contact)
|
||||
.sort((a, b) => b.lastAt.localeCompare(a.lastAt));
|
||||
});
|
||||
|
||||
@@ -722,7 +696,7 @@ const routing = useWorkspaceRouting({
|
||||
commThreads,
|
||||
contacts,
|
||||
deals,
|
||||
commItems,
|
||||
clientTimelineItems,
|
||||
activeChangeMessage,
|
||||
activeChangeItem,
|
||||
activeChangeItems,
|
||||
@@ -958,8 +932,9 @@ const selectedContactEvents = computed(() => {
|
||||
|
||||
const selectedContactRecentMessages = computed(() => {
|
||||
if (!selectedContact.value) return [];
|
||||
return commItems.value
|
||||
.filter((item) => item.contact === selectedContact.value?.name && item.kind === "message")
|
||||
return clientTimelineItems.value
|
||||
.filter((entry) => entry.contentType === "message" && entry.message)
|
||||
.map((entry) => entry.message!)
|
||||
.sort((a, b) => b.at.localeCompare(a.at))
|
||||
.slice(0, 8);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { ref, computed, watch, watchEffect, type ComputedRef } from "vue";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import {
|
||||
ContactsQueryDocument,
|
||||
CommunicationsQueryDocument,
|
||||
} from "~~/graphql/generated";
|
||||
import { ContactsQueryDocument } from "~~/graphql/generated";
|
||||
|
||||
export type Contact = {
|
||||
id: string;
|
||||
@@ -11,6 +8,8 @@ export type Contact = {
|
||||
avatar: string;
|
||||
channels: string[];
|
||||
lastContactAt: string;
|
||||
lastMessageText: string;
|
||||
lastMessageChannel: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
@@ -41,32 +40,13 @@ export function useContacts(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
||||
{ enabled: opts.apolloAuthReady },
|
||||
);
|
||||
|
||||
const { result: communicationsResult, refetch: refetchCommunications } = useQuery(
|
||||
CommunicationsQueryDocument,
|
||||
null,
|
||||
{ enabled: opts.apolloAuthReady },
|
||||
);
|
||||
|
||||
const contacts = ref<Contact[]>([]);
|
||||
const commItems = ref<CommItem[]>([]);
|
||||
|
||||
watch(
|
||||
[() => contactsResult.value?.contacts, () => communicationsResult.value?.communications],
|
||||
([rawContacts, rawComms]) => {
|
||||
() => contactsResult.value?.contacts,
|
||||
(rawContacts) => {
|
||||
if (!rawContacts) return;
|
||||
const contactsList = [...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;
|
||||
contacts.value = [...rawContacts] as Contact[];
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -166,7 +146,6 @@ export function useContacts(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
||||
|
||||
return {
|
||||
contacts,
|
||||
commItems,
|
||||
contactSearch,
|
||||
selectedChannel,
|
||||
sortMode,
|
||||
@@ -181,6 +160,5 @@ export function useContacts(opts: { apolloAuthReady: ComputedRef<boolean> }) {
|
||||
markAvatarBroken,
|
||||
contactInitials,
|
||||
refetchContacts,
|
||||
refetchCommunications,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function useWorkspaceRouting(opts: {
|
||||
commThreads: ComputedRef<{ id: string; [key: string]: any }[]>;
|
||||
contacts: Ref<{ id: string; name: 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>;
|
||||
activeChangeItem: ComputedRef<PilotChangeItem | null>;
|
||||
activeChangeItems: ComputedRef<PilotChangeItem[]>;
|
||||
@@ -340,9 +340,9 @@ export function useWorkspaceRouting(opts: {
|
||||
if (item.entity === "message" && item.entityId) {
|
||||
opts.peopleLeftMode.value = "contacts";
|
||||
opts.peopleListMode.value = "contacts";
|
||||
const message = opts.commItems.value.find((entry) => entry.id === item.entityId);
|
||||
if (message?.contact) {
|
||||
opts.openCommunicationThread(message.contact);
|
||||
const timelineEntry = opts.clientTimelineItems.value.find((entry) => entry.contentType === "message" && entry.message && entry.id === item.entityId);
|
||||
if (timelineEntry?.message?.contact) {
|
||||
opts.openCommunicationThread(timelineEntry.message.contact);
|
||||
}
|
||||
opts.focusedCalendarEventId.value = "";
|
||||
syncPathFromUi(push);
|
||||
|
||||
Reference in New Issue
Block a user