Consolidate chat timeline events + auto-end inactive sessions
Timeline consolidation: - Replace multiple LIVE_CHAT_STARTED/ENDED events with single LIVE_CHAT_SUMMARY - Show total duration accumulated across all sessions - Display session count (e.g., "23min 15s total - 3 sessoes") - Show "Ativo" badge when session is active Auto-end inactive chat sessions: - Add cron job running every minute to check inactive sessions - Automatically end sessions after 5 minutes of client inactivity - Mark auto-ended sessions with "(encerrado por inatividade)" flag 🤖 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
409da8afda
commit
115c5128a6
5 changed files with 224 additions and 2 deletions
|
|
@ -54,6 +54,7 @@ const timelineIcons: Record<string, ComponentType<{ className?: string }>> = {
|
|||
TICKET_LINKED: IconLink,
|
||||
LIVE_CHAT_STARTED: IconMessage,
|
||||
LIVE_CHAT_ENDED: IconMessage,
|
||||
LIVE_CHAT_SUMMARY: IconMessage,
|
||||
}
|
||||
|
||||
const timelineLabels: Record<string, string> = TICKET_TIMELINE_LABELS
|
||||
|
|
@ -637,6 +638,7 @@ export function TicketTimeline({ ticket }: TicketTimelineProps) {
|
|||
if (entry.type === "LIVE_CHAT_ENDED") {
|
||||
const agentName = (payload as { agentName?: string }).agentName
|
||||
const durationMs = (payload as { durationMs?: number }).durationMs
|
||||
const autoEnded = (payload as { autoEnded?: boolean }).autoEnded
|
||||
const durationFormatted = typeof durationMs === "number" ? formatDuration(durationMs) : null
|
||||
message = (
|
||||
<div className="space-y-1">
|
||||
|
|
@ -647,15 +649,48 @@ export function TicketTimeline({ ticket }: TicketTimelineProps) {
|
|||
{" "}por <span className="font-semibold text-neutral-900">{agentName}</span>
|
||||
</>
|
||||
)}
|
||||
{autoEnded && (
|
||||
<span className="text-neutral-500"> (encerrado por inatividade)</span>
|
||||
)}
|
||||
</span>
|
||||
{durationFormatted && (
|
||||
<span className="block text-xs text-neutral-500">
|
||||
Duração: {durationFormatted}
|
||||
Duracao: {durationFormatted}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (entry.type === "LIVE_CHAT_SUMMARY") {
|
||||
const chatPayload = payload as {
|
||||
sessionCount?: number
|
||||
totalDurationMs?: number
|
||||
agentName?: string
|
||||
hasActiveSession?: boolean
|
||||
}
|
||||
const sessionCount = chatPayload.sessionCount ?? 1
|
||||
const totalDurationMs = chatPayload.totalDurationMs ?? 0
|
||||
const durationFormatted = totalDurationMs > 0 ? formatDuration(totalDurationMs) : null
|
||||
const sessionLabel = sessionCount === 1 ? "sessao" : "sessoes"
|
||||
message = (
|
||||
<div className="space-y-1">
|
||||
<span className="block text-sm text-neutral-600">
|
||||
<span className="font-semibold text-neutral-800">Chat ao vivo</span>
|
||||
{chatPayload.hasActiveSession && (
|
||||
<span className="ml-2 inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-medium text-emerald-700">
|
||||
<span className="size-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||
Ativo
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="block text-xs text-neutral-500">
|
||||
{durationFormatted ? `${durationFormatted} total` : ""}
|
||||
{durationFormatted && sessionCount > 0 ? " • " : ""}
|
||||
{sessionCount} {sessionLabel}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (!message) return null
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -24,4 +24,5 @@ export const TICKET_TIMELINE_LABELS: Record<string, string> = {
|
|||
CUSTOM_FIELDS_UPDATED: "Campos personalizados atualizados",
|
||||
LIVE_CHAT_STARTED: "Chat iniciado",
|
||||
LIVE_CHAT_ENDED: "Chat finalizado",
|
||||
LIVE_CHAT_SUMMARY: "Chat ao vivo",
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue