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:
Ruslan Bakiev
2026-02-24 20:02:58 +07:00
parent 601de37ab0
commit ac9c50b47d
3 changed files with 28 additions and 75 deletions

View File

@@ -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,
};
}

View File

@@ -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);