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>
99 lines
3.2 KiB
TypeScript
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,
|
|
};
|
|
}
|