feat(desktop): adiciona hub de chats para multiplas sessoes
- Cria ChatSessionList, ChatSessionItem e ChatHubWidget no desktop - Adiciona comandos Rust para gerenciar hub window - Quando ha multiplas sessoes, abre hub ao inves de janela individual - Hub lista todas as sessoes ativas com badge de nao lidos - Clicar em sessao abre/foca janela de chat especifica - Menu do tray abre hub quando ha multiplas sessoes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
95ab1b5f0c
commit
29fbbfaa26
6 changed files with 560 additions and 38 deletions
82
apps/desktop/src/chat/ChatSessionItem.tsx
Normal file
82
apps/desktop/src/chat/ChatSessionItem.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { MessageCircle } from "lucide-react"
|
||||
import type { ChatSession } from "./types"
|
||||
|
||||
type ChatSessionItemProps = {
|
||||
session: ChatSession
|
||||
isActive?: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
function formatTime(timestamp: number) {
|
||||
const now = Date.now()
|
||||
const diff = now - timestamp
|
||||
const minutes = Math.floor(diff / 60000)
|
||||
const hours = Math.floor(diff / 3600000)
|
||||
const days = Math.floor(diff / 86400000)
|
||||
|
||||
if (minutes < 1) return "Agora"
|
||||
if (minutes < 60) return `${minutes}min`
|
||||
if (hours < 24) return `${hours}h`
|
||||
if (days === 1) return "Ontem"
|
||||
|
||||
return new Date(timestamp).toLocaleDateString("pt-BR", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
})
|
||||
}
|
||||
|
||||
export function ChatSessionItem({ session, isActive, onClick }: ChatSessionItemProps) {
|
||||
const hasUnread = session.unreadCount > 0
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`flex w-full items-start gap-3 border-b border-slate-100 px-4 py-3 text-left transition-colors hover:bg-slate-50 ${
|
||||
isActive ? "bg-slate-100" : ""
|
||||
} ${hasUnread ? "bg-red-50/50 hover:bg-red-50" : ""}`}
|
||||
>
|
||||
{/* Avatar/Icone */}
|
||||
<div
|
||||
className={`flex size-10 shrink-0 items-center justify-center rounded-full ${
|
||||
hasUnread ? "bg-red-100 text-red-600" : "bg-slate-100 text-slate-600"
|
||||
}`}
|
||||
>
|
||||
<MessageCircle className="size-5" />
|
||||
</div>
|
||||
|
||||
{/* Conteudo */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-sm font-semibold ${hasUnread ? "text-red-700" : "text-slate-900"}`}>
|
||||
#{session.ticketRef}
|
||||
</span>
|
||||
{/* Indicador online - sessao ativa significa online */}
|
||||
<span className="size-2 rounded-full bg-emerald-500" title="Online" />
|
||||
</div>
|
||||
<span className="text-xs text-slate-400">
|
||||
{formatTime(session.lastActivityAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="mt-0.5 truncate text-sm text-slate-600">
|
||||
{session.ticketSubject}
|
||||
</p>
|
||||
|
||||
<p className="mt-0.5 truncate text-xs text-slate-400">
|
||||
{session.agentName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Badge de nao lidos */}
|
||||
{hasUnread && (
|
||||
<div className="flex shrink-0 items-center justify-center">
|
||||
<span className="flex size-5 items-center justify-center rounded-full bg-red-500 text-xs font-bold text-white">
|
||||
{session.unreadCount > 9 ? "9+" : session.unreadCount}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue