feat(dashboard): l'inbox sale in cima e dice da quanto aspettano
Il widget dei messaggi esisteva gia', ma era il terzo riquadro della colonna stretta: sotto la piega, cioe' invisibile. Un messaggio non letto e' la cosa piu' urgente della giornata e ora sta in testa alla dashboard, a piena larghezza, due colonne. Due dati erano gia' in ConversationSummary e non venivano mostrati: da quanto aspetta il messaggio e su cosa e' stato scritto (fase, task, deliverable o generale). Sono esattamente i due che dicono con che fretta rispondere. Il tempo relativo si ferma alla settimana e poi passa alla data: oltre, "23 giorni fa" non aiuta piu' nessuno. La fascia sparisce del tutto quando non c'e' niente da leggere, invece di lasciare a video un riquadro vuoto che si impara a saltare — e con lui si imparerebbe a saltare anche quello pieno. relativeTime va in src/lib/dates.ts perche' serve anche altrove. FollowUpWidget ha ancora la sua copia locale con il prefisso "Contattato": la si unifica quando la si tocca, non oggi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
<section className="bg-card rounded-xl border border-border-light shadow-card p-5">
|
||||
<div className="flex items-center justify-between gap-3 mb-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<h2 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
||||
Da rispondere
|
||||
</h2>
|
||||
<span className="text-[10px] font-bold bg-emerald-50 text-emerald-700 border border-emerald-100 px-2 py-0.5 rounded-full dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20">
|
||||
{totalMessages} {totalMessages === 1 ? "messaggio" : "messaggi"} ·{" "}
|
||||
{unread.length} {unread.length === 1 ? "cliente" : "clienti"}
|
||||
</span>
|
||||
</div>
|
||||
<Link
|
||||
href="/admin/conversazioni"
|
||||
className="text-xs font-medium text-muted-foreground hover:text-primary transition-colors"
|
||||
>
|
||||
Apri conversazioni →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||
{visible.map((conv) => (
|
||||
<Link
|
||||
key={conv.clientId}
|
||||
href={`/admin/conversazioni?c=${conv.clientId}`}
|
||||
className="flex items-start justify-between gap-3 p-3 border border-border-light rounded-lg hover:border-border transition-colors group"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-baseline gap-2 flex-wrap">
|
||||
<h3 className="text-xs font-bold text-foreground truncate">
|
||||
{conv.brand_name}
|
||||
</h3>
|
||||
<span className="text-[10px] text-muted-foreground font-mono tabular-nums shrink-0">
|
||||
{relativeTime(conv.lastMessageAt)}
|
||||
</span>
|
||||
{conv.unreadCount > 1 && (
|
||||
<span className="text-[10px] font-bold text-emerald-700 dark:text-emerald-400 shrink-0">
|
||||
{conv.unreadCount} nuovi
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mt-1 line-clamp-2">
|
||||
{conv.lastMessage}
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground/70 mt-1 truncate">
|
||||
{conv.lastEntityLabel}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-[11px] font-bold text-primary group-hover:opacity-70 transition-opacity shrink-0 mt-0.5">
|
||||
Rispondi →
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{unread.length > MAX_ROWS && (
|
||||
<Link
|
||||
href="/admin/conversazioni"
|
||||
className="block mt-3 pt-3 text-center text-xs font-medium text-muted-foreground hover:text-primary border-t border-border-light transition-colors"
|
||||
>
|
||||
Altri {unread.length - MAX_ROWS} clienti in attesa
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user