Files
DeskcommCRM/hooks/useAdminConversation.ts
Rafael MelgaçoandClaude Opus 4.7 a3d6bcc6bb feat(EPIC-11): admin inbox cross-tenant + realtime fanout [wave 3]
GET /api/v1/admin/inbox/conversations: requirePlatformAdmin, service-role
cross-tenant query joining organizations + contacts + last_message_preview
+ unread_count. Cursor pagination by last_inbound_at desc.

GET /api/v1/admin/inbox/conversations/[id]: detail with last 50 messages.

useAdminInboxRealtime subscribes to postgres_changes on messages INSERT
(no org filter — admin sees all tenants). TanStack invalidation on event.

UI: 3-col layout reusing MessageBubble from EPIC-03. TenantBadge in
every list item + thread header. Composer disabled (read-only) — agent
response only via Impersonate (S-11.07).

Audit: platform_admin.inbox_listed + platform_admin.conversation_viewed
(lightweight, tenant_id only — no PII).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:22:44 -03:00

69 lines
1.8 KiB
TypeScript

"use client";
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "@/lib/api/client";
import type { Message } from "@/lib/types/messaging";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface AdminConversationDetail {
id: string;
organization_id: string;
contact_id: string | null;
channel: string;
status: string;
assigned_to_user_id: string | null;
last_inbound_at: string | null;
last_message_at: string | null;
last_message_preview: string | null;
unread_count_for_assignee: number;
created_at: string;
updated_at: string;
}
export interface AdminOrganizationInfo {
id: string;
display_name: string;
slug: string;
status: string;
}
export interface AdminContactInfo {
id: string;
name: string | null;
phone_number: string | null;
email: string | null;
is_anonymized: boolean;
is_blocked: boolean;
}
export interface AdminConversationDetailResponse {
conversation: AdminConversationDetail;
organization: AdminOrganizationInfo | null;
contact: AdminContactInfo | null;
messages: Message[];
}
interface ApiResponse {
data: AdminConversationDetailResponse;
}
// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------
export function useAdminConversation(conversationId: string | null) {
return useQuery({
queryKey: ["admin", "inbox", conversationId] as const,
enabled: !!conversationId,
queryFn: async () => {
const res = await apiClient.get<ApiResponse>(
`/api/v1/admin/inbox/conversations/${conversationId}`,
);
return res.data;
},
staleTime: 10_000,
});
}