// Copyright: © 2026 TWWIM UG. All rights reserved. (www.twwim.com) /** * ConversationsPage — Communication Center, live. * * Data source: customer-api conversations module via TanStack Query. * - Inbox list: 5 s poll, paused while tab hidden, immediate refetch on visible. * - Selected conversation detail: 3 s poll under same visibility rules. * - Queue counts: 5 s poll, drives QueueMetricsHeader. * * No mock data, no demo banner — every count, badge, status, avatar, and * page-trail item is wired to the DB row served by /conversations. */ import { useEffect, useMemo, useState } from 'react'; import { MessagesSquare, Search } from 'lucide-react'; import { PageLayout } from '@/components/shared'; import { useTranslation } from '@/i18n/TranslationProvider'; import { useTenants } from '@/features/tenants/hooks'; import { ConversationListItem } from './components/ConversationListItem'; import { ConversationDetail } from './components/ConversationDetail'; import { VisitorContextPanel } from './components/VisitorContextPanel'; import { QueueMetricsHeader } from './components/QueueMetricsHeader'; import { OperatorAvailabilityToggle } from './components/OperatorAvailabilityToggle'; import { useConversationsList, useConversationDetail, useQueueCounts, useInterceptMutation, useReleaseMutation, useSendMessageMutation, useMarkSeenMutation, } from './hooks/useConversations'; import type { ConversationStatus, ListFilter } from './lib/types'; const ALL_TENANTS = '__all__'; const STATUS_FILTERS: (ConversationStatus | 'all')[] = ['all', 'active', 'intercepted', 'idle', 'closed']; export function ConversationsPage() { const { t } = useTranslation(); const tenantsQuery = useTenants(); const tenants = tenantsQuery.data?.tenants ?? []; const [tenantFilter, setTenantFilter] = useState(ALL_TENANTS); const [statusFilter, setStatusFilter] = useState('all'); const [search, setSearch] = useState(''); const [selectedId, setSelectedId] = useState(null); const listFilter: ListFilter = { tenantId: tenantFilter === ALL_TENANTS ? undefined : tenantFilter, status: statusFilter, }; const listQuery = useConversationsList(listFilter); const countsQuery = useQueueCounts(tenantFilter === ALL_TENANTS ? undefined : tenantFilter); const detailQuery = useConversationDetail(selectedId); const intercept = useInterceptMutation(); const release = useReleaseMutation(); const sendMessage = useSendMessageMutation(); const markSeen = useMarkSeenMutation(); const items = listQuery.data?.conversations ?? []; const counts = countsQuery.data ?? { total: 0, active: 0, idle: 0, intercepted: 0, closed: 0, awaitingReply: 0, breaching: 0, }; const filtered = useMemo(() => { const q = search.trim().toLowerCase(); return [...items] .filter((c) => { if (!q) return true; if (c.visitorRef.toLowerCase().includes(q)) return true; if (c.tenantName.toLowerCase().includes(q)) return true; if (c.lastMessage?.content.toLowerCase().includes(q)) return true; return false; }) .sort((a, b) => new Date(b.lastActivityAt).getTime() - new Date(a.lastActivityAt).getTime()); }, [items, search]); useEffect(() => { if (!selectedId && filtered.length > 0) { setSelectedId(filtered[0].id); } }, [filtered, selectedId]); useEffect(() => { if (selectedId) markSeen.mutate(selectedId); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedId]); const selected = detailQuery.data ?? null; return ( } gradient maxWidth="full" >
{STATUS_FILTERS.map((s) => ( ))}
{t('conversations.filterSummary', { shown: filtered.length, total: items.length })}
{selected ? ( intercept.mutate(selected.id)} onRelease={() => release.mutate(selected.id)} onSendOperatorMessage={(content) => sendMessage.mutate({ id: selected.id, content })} /> ) : (
{detailQuery.isLoading ? t('conversations.detailLoading') : t('conversations.detailEmpty')}
)} {selected && (
)}
); }