Files
clientsflow/frontend/app/composables/useContactInboxes.ts
Ruslan Bakiev 6e3763a5fd fix: refetch contacts after hiding inbox, redirect to most recent chat
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>
2026-02-25 07:56:10 +07:00

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