Files
clientsflow/frontend/app/composables/useContactInboxes.ts
Ruslan Bakiev d892d0c604 refactor: distribute types from crm-types.ts to owning composables
Each composable now owns its types and exports them. Other composables
import types from the owning composable. Deleted centralized crm-types.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:21:30 +07:00

99 lines
3.2 KiB
TypeScript

import { ref, watch, type ComputedRef } from "vue";
import { useQuery, useMutation } from "@vue/apollo-composable";
import {
ContactInboxesQueryDocument,
SetContactInboxHiddenDocument,
} from "~~/graphql/generated";
import type { CommItem } from "~/composables/useContacts";
export type ContactInbox = {
id: string;
contactId: string;
contactName: string;
channel: CommItem["channel"];
sourceExternalId: string;
title: string;
isHidden: boolean;
lastMessageAt: string;
updatedAt: string;
};
export function useContactInboxes(opts: { apolloAuthReady: ComputedRef<boolean> }) {
const { result: contactInboxesResult, refetch: refetchContactInboxes } = useQuery(
ContactInboxesQueryDocument,
null,
{ enabled: opts.apolloAuthReady },
);
const { mutate: doSetContactInboxHidden } = useMutation(SetContactInboxHiddenDocument, {
refetchQueries: [{ query: ContactInboxesQueryDocument }],
update: (cache, _result, { variables }) => {
if (!variables) return;
const existing = cache.readQuery({ query: ContactInboxesQueryDocument }) as { contactInboxes?: ContactInbox[] } | null;
if (!existing?.contactInboxes) return;
cache.writeQuery({
query: ContactInboxesQueryDocument,
data: {
contactInboxes: existing.contactInboxes.map((inbox) =>
inbox.id === variables.inboxId ? { ...inbox, isHidden: variables.hidden } : inbox,
),
},
});
},
});
const contactInboxes = ref<ContactInbox[]>([]);
const inboxToggleLoadingById = ref<Record<string, boolean>>({});
watch(() => contactInboxesResult.value?.contactInboxes, (v) => {
if (v) contactInboxes.value = v as ContactInbox[];
}, { immediate: true });
function isInboxToggleLoading(inboxId: string) {
return Boolean(inboxToggleLoadingById.value[inboxId]);
}
async function setInboxHidden(inboxId: string, hidden: boolean) {
const id = String(inboxId ?? "").trim();
if (!id || isInboxToggleLoading(id)) return;
inboxToggleLoadingById.value = { ...inboxToggleLoadingById.value, [id]: true };
try {
await doSetContactInboxHidden({ inboxId: id, hidden });
} catch (e: unknown) {
console.error("[setInboxHidden] mutation failed:", e);
} finally {
inboxToggleLoadingById.value = { ...inboxToggleLoadingById.value, [id]: false };
}
}
function threadInboxes(thread: { id: string }) {
return contactInboxes.value
.filter((inbox) => inbox.contactId === thread.id)
.sort((a, b) => {
const aTime = a.lastMessageAt || a.updatedAt;
const bTime = b.lastMessageAt || b.updatedAt;
return bTime.localeCompare(aTime);
});
}
function formatInboxLabel(inbox: ContactInbox) {
const title = String(inbox.title ?? "").trim();
if (title) return `${inbox.channel} · ${title}`;
const source = String(inbox.sourceExternalId ?? "").trim();
if (!source) return inbox.channel;
const tail = source.length > 18 ? source.slice(-18) : source;
return `${inbox.channel} · ${tail}`;
}
return {
contactInboxes,
inboxToggleLoadingById,
setInboxHidden,
isInboxToggleLoading,
threadInboxes,
formatInboxLabel,
refetchContactInboxes,
};
}