After hiding a contact inbox, the contacts list now refetches immediately so the hidden contact disappears reactively. When the current contact is removed from the list, the selection jumps to the most recently active contact (by lastContactAt) instead of the first item in the current sort. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.3 KiB
TypeScript
100 lines
3.3 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>; onHidden?: () => void }) {
|
|
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 });
|
|
if (hidden && opts.onHidden) opts.onHidden();
|
|
} 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,
|
|
};
|
|
}
|