diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index a1996af..f9b93d5 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react"; import { getDashboardStats } from "@/lib/dashboard-queries"; import { FollowUpWidget } from "@/components/admin/dashboard/FollowUpWidget"; -import { MessagesWidget } from "@/components/admin/dashboard/MessagesWidget"; +import { InboxBand } from "@/components/admin/dashboard/InboxBand"; import { getAnalyticsByYear, getTotalTrackedHours, @@ -39,6 +39,12 @@ export default async function AdminDashboard({ return (
+ {/* Messaggi in attesa — in cima perché è la cosa più urgente della + giornata. Non renderizza nulla quando non c'è niente da leggere. */} + + + + {/* KPI strip */}
- } - > - -
diff --git a/src/components/admin/dashboard/InboxBand.tsx b/src/components/admin/dashboard/InboxBand.tsx new file mode 100644 index 0000000..ef80ec2 --- /dev/null +++ b/src/components/admin/dashboard/InboxBand.tsx @@ -0,0 +1,93 @@ +import { getConversations } from "@/lib/conversations-queries"; +import { relativeTime } from "@/lib/dates"; +import Link from "next/link"; + +const MAX_ROWS = 6; + +/** + * I messaggi che aspettano una risposta, in cima alla dashboard. + * + * Prima era un riquadro stretto in fondo alla terza colonna: c'era, ma sotto la + * piega. Un messaggio non letto è la cosa più urgente della giornata e va vista + * per prima — per questo occupa tutta la larghezza, e per questo sparisce del + * tutto quando non c'è niente da leggere invece di lasciare a video una fascia + * vuota che si impara a saltare. + * + * Si legge qui, si risponde in /admin/conversazioni. + */ +export async function InboxBand() { + const conversations = await getConversations(); + const unread = conversations.filter((c) => c.unread); + + if (unread.length === 0) return null; + + const visible = unread.slice(0, MAX_ROWS); + const totalMessages = unread.reduce((sum, c) => sum + c.unreadCount, 0); + + return ( +
+
+
+ +

+ Da rispondere +

+ + {totalMessages} {totalMessages === 1 ? "messaggio" : "messaggi"} ·{" "} + {unread.length} {unread.length === 1 ? "cliente" : "clienti"} + +
+ + Apri conversazioni → + +
+ +
+ {visible.map((conv) => ( + +
+
+

+ {conv.brand_name} +

+ + {relativeTime(conv.lastMessageAt)} + + {conv.unreadCount > 1 && ( + + {conv.unreadCount} nuovi + + )} +
+

+ {conv.lastMessage} +

+

+ {conv.lastEntityLabel} +

+
+ + Rispondi → + + + ))} +
+ + {unread.length > MAX_ROWS && ( + + Altri {unread.length - MAX_ROWS} clienti in attesa + + )} +
+ ); +} diff --git a/src/components/admin/dashboard/MessagesWidget.tsx b/src/components/admin/dashboard/MessagesWidget.tsx deleted file mode 100644 index a74d2a6..0000000 --- a/src/components/admin/dashboard/MessagesWidget.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { getConversations } from "@/lib/conversations-queries"; -import Link from "next/link"; - -const MAX_ROWS = 4; - -export async function MessagesWidget() { - const conversations = await getConversations(); - const unread = conversations.filter((c) => c.unread); - - if (unread.length === 0) { - return ( -
-

- Messaggi Clienti -

-

Nessun messaggio in attesa ✓

-
- ); - } - - const visible = unread.slice(0, MAX_ROWS); - - return ( -
-
-
-

- Messaggi Clienti -

- -
- - {unread.length} {unread.length === 1 ? "Nuovo" : "Nuovi"} - -
- -
- {visible.map((conv) => ( - -
-

{conv.brand_name}

-

{conv.lastMessage}

-
- - Rispondi → - - - ))} -
- - {unread.length > MAX_ROWS && ( - - Vedi tutti i messaggi - - )} -
- ); -} diff --git a/src/lib/dates.ts b/src/lib/dates.ts new file mode 100644 index 0000000..d14b88b --- /dev/null +++ b/src/lib/dates.ts @@ -0,0 +1,29 @@ +/** + * Distanza da adesso, in italiano e senza prefissi: "adesso", "12 min fa", + * "3 ore fa", "ieri", "5 giorni fa", "12 mar". + * + * Sui messaggi la scala che conta è quella delle ore: sapere che un cliente + * scrive da due ore o da due giorni cambia con che fretta gli si risponde, e + * una data assoluta lo direbbe soltanto dopo un calcolo mentale. + * Oltre la settimana la distanza smette di essere utile e si passa alla data. + */ +export function relativeTime(date: Date | string | null | undefined): string { + if (!date) return "—"; + const d = date instanceof Date ? date : new Date(date); + if (isNaN(d.getTime())) return "—"; + + const seconds = Math.floor((Date.now() - d.getTime()) / 1000); + if (seconds < 60) return "adesso"; + + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes} min fa`; + + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours} ${hours === 1 ? "ora" : "ore"} fa`; + + const days = Math.floor(hours / 24); + if (days === 1) return "ieri"; + if (days < 7) return `${days} giorni fa`; + + return d.toLocaleDateString("it-IT", { day: "numeric", month: "short" }); +}