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:
@@ -13,12 +13,14 @@ import {
|
||||
LogOut,
|
||||
Zap,
|
||||
FileText,
|
||||
MessageSquare,
|
||||
} from "lucide-react";
|
||||
import ThemeToggle from "@/components/ThemeToggle";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ href: "/admin", label: "Dashboard", icon: LayoutDashboard, exact: true },
|
||||
{ href: "/admin/conversazioni", label: "Conversazioni", icon: MessageSquare },
|
||||
{ href: "/admin/pipeline", label: "Pipeline", icon: Zap },
|
||||
{ href: "/admin/clients", label: "Clienti", icon: Users },
|
||||
{ href: "/admin/projects", label: "Progetti", icon: FolderOpen },
|
||||
@@ -28,7 +30,13 @@ const NAV_ITEMS = [
|
||||
{ href: "/admin/impostazioni", label: "Impostazioni", icon: Settings },
|
||||
];
|
||||
|
||||
export function AdminSidebar({ collapsed = false }: { collapsed?: boolean }) {
|
||||
export function AdminSidebar({
|
||||
collapsed = false,
|
||||
unreadConversations = 0,
|
||||
}: {
|
||||
collapsed?: boolean;
|
||||
unreadConversations?: number;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
|
||||
const isActive = (href: string, exact?: boolean) =>
|
||||
@@ -64,6 +72,10 @@ export function AdminSidebar({ collapsed = false }: { collapsed?: boolean }) {
|
||||
<nav className="flex-1 px-2 py-4 flex flex-col gap-0.5">
|
||||
{NAV_ITEMS.map(({ href, label, icon: Icon, exact }) => {
|
||||
const active = isActive(href, exact);
|
||||
const badge =
|
||||
href === "/admin/conversazioni" && unreadConversations > 0
|
||||
? unreadConversations
|
||||
: 0;
|
||||
return (
|
||||
<Link
|
||||
key={href}
|
||||
@@ -77,12 +89,22 @@ export function AdminSidebar({ collapsed = false }: { collapsed?: boolean }) {
|
||||
: "text-white/65 hover:text-white hover:bg-white/10"
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
size={16}
|
||||
strokeWidth={1.8}
|
||||
className="shrink-0"
|
||||
/>
|
||||
{!collapsed && <span className="whitespace-nowrap">{label}</span>}
|
||||
<span className="relative shrink-0">
|
||||
<Icon size={16} strokeWidth={1.8} className="shrink-0" />
|
||||
{badge > 0 && collapsed && (
|
||||
<span className="absolute -top-1.5 -right-1.5 w-2 h-2 rounded-full bg-emerald-500 ring-2 ring-[#1A463C]" />
|
||||
)}
|
||||
</span>
|
||||
{!collapsed && (
|
||||
<span className="flex-1 flex items-center justify-between gap-2 min-w-0">
|
||||
<span className="whitespace-nowrap">{label}</span>
|
||||
{badge > 0 && (
|
||||
<span className="shrink-0 min-w-[18px] h-[18px] px-1.5 inline-flex items-center justify-center rounded-full bg-emerald-500 text-white text-[10px] font-bold">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user