feat: pagina Conversazioni — inbox unificata messaggi clienti

Nuova pagina admin /admin/conversazioni: vista WhatsApp-style (lista
conversazioni a sinistra, thread + risposta a destra) che aggrega i
messaggi di tutti i clienti dalla tabella comments, cross-cliente.

- Migration additiva 0014: clients.admin_last_read_at per tracciare
  letto/non-letto (pallini + badge in sidebar). Applicata a prod.
- conversations-queries.ts: aggregazione entity_id → cliente
  (general/phase/task/deliverable), getConversations /
  getConversationThread / getUnreadConversationsCount.
- Risposte admin salvate come commento "general" (visibili anche nella
  chat cliente e nel CommentsTab della scheda).
- Voce di menu + badge non-letti (AdminSidebar/AdminShell/layout).
- Token semantici per dual light/dark; refresh manuale (no polling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 16:10:14 +02:00
parent a20a9de2d7
commit c1cc13a99a
10 changed files with 994 additions and 10 deletions
+41
View File
@@ -0,0 +1,41 @@
import { PageHeader } from "@/components/admin/PageHeader";
import { ConversationsView } from "@/components/admin/conversazioni/ConversationsView";
import {
getConversations,
getConversationThread,
} from "@/lib/conversations-queries";
export const revalidate = 0;
export default async function ConversazioniPage({
searchParams,
}: {
searchParams: Promise<{ c?: string }>;
}) {
const { c } = await searchParams;
const conversations = await getConversations();
// Default selection: the requested client, else the most recent conversation.
const activeClientId =
(c && conversations.some((conv) => conv.clientId === c) ? c : null) ??
conversations[0]?.clientId ??
null;
const activeThread = activeClientId
? await getConversationThread(activeClientId)
: null;
return (
<div className="space-y-6">
<PageHeader
title="Conversazioni"
subtitle="Centro messaggi clienti — tutte le richieste in un unico posto"
/>
<ConversationsView
conversations={conversations}
activeThread={activeThread}
activeClientId={activeClientId}
/>
</div>
);
}