Corrige comportamentos do chat e melhora UX

- Corrige contador de mensagens resetando sozinho (web)
  - Adiciona verificacao de visibilidade antes de marcar como lido
  - Verifica se aba esta ativa antes de marcar como lido

- Adiciona sincronizacao de estado do chat entre abas (web)
  - Usa BroadcastChannel para sincronizar aberto/fechado/minimizado
  - Persiste estado no localStorage

- Corrige chat minimizando sozinho no desktop (Rust)
  - Verifica se chat esta expandido antes de minimizar
  - Mantem chat aberto quando usuario esta usando

- Melhora encerramento automatico de sessoes de chat
  - Muda criterio de inatividade de chat para maquina offline
  - Sessao permanece ativa enquanto Raven estiver aberto
  - Encerra apenas quando maquina fica offline por 5+ min

- Corrige tabela de tickets em /devices
  - Adiciona acentuacao correta (Ultima atualizacao, Responsavel)
  - Torna linha inteira clicavel para abrir ticket

- Ajusta sidebar
  - Menu Tickets agora expande ao clicar (igual Cadastros)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-11 15:21:24 -03:00
parent 2682b6e8ac
commit f4880f32d2
7 changed files with 219 additions and 37 deletions

View file

@ -753,43 +753,78 @@ export const getTicketChatHistory = query({
// ENCERRAMENTO AUTOMATICO POR INATIVIDADE
// ============================================
// Timeout de inatividade: 5 minutos
const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000
// Timeout de maquina offline: 5 minutos sem heartbeat
const MACHINE_OFFLINE_TIMEOUT_MS = 5 * 60 * 1000
// Mutation interna para encerrar sessões inativas (chamada pelo cron)
// Otimizada com paginação para evitar timeout
// Mutation interna para encerrar sessões de máquinas offline (chamada pelo cron)
// Nova lógica: só encerra se a MÁQUINA estiver offline, não por inatividade de chat
// Isso permite que usuários mantenham o chat aberto sem precisar enviar mensagens
export const autoEndInactiveSessions = mutation({
args: {},
handler: async (ctx) => {
// Log obrigatorio para evitar shape_inference errors com logLines vazios
console.log("cron: autoEndInactiveSessions iniciado")
console.log("cron: autoEndInactiveSessions iniciado (verificando maquinas offline)")
const now = Date.now()
const cutoffTime = now - INACTIVITY_TIMEOUT_MS
const offlineCutoff = now - MACHINE_OFFLINE_TIMEOUT_MS
// Limitar a 50 sessões por execução para evitar timeout do cron (30s)
const maxSessionsPerRun = 50
// Buscar sessões ativas com inatividade > 5 minutos (usando índice otimizado)
const inactiveSessions = await ctx.db
// Buscar todas as sessões ativas
const activeSessions = await ctx.db
.query("liveChatSessions")
.withIndex("by_status_lastActivity", (q) =>
q.eq("status", "ACTIVE").lt("lastActivityAt", cutoffTime)
)
.withIndex("by_status_lastActivity", (q) => q.eq("status", "ACTIVE"))
.take(maxSessionsPerRun)
let endedCount = 0
let checkedCount = 0
for (const session of inactiveSessions) {
// Encerrar a sessão
for (const session of activeSessions) {
checkedCount++
// Buscar o ticket para obter a máquina
const ticket = await ctx.db.get(session.ticketId)
if (!ticket || !ticket.machineId) {
// Ticket sem máquina - encerrar sessão órfã
await ctx.db.patch(session._id, {
status: "ENDED",
endedAt: now,
})
await ctx.db.insert("ticketEvents", {
ticketId: session.ticketId,
type: "LIVE_CHAT_ENDED",
payload: {
sessionId: session._id,
agentId: session.agentId,
agentName: session.agentSnapshot?.name ?? "Sistema",
durationMs: now - session.startedAt,
startedAt: session.startedAt,
endedAt: now,
autoEnded: true,
reason: "ticket_sem_maquina",
},
createdAt: now,
})
endedCount++
continue
}
// Verificar heartbeat da máquina
const lastHeartbeatAt = await getLastHeartbeatAt(ctx, ticket.machineId)
const machineIsOnline = lastHeartbeatAt !== null && lastHeartbeatAt > offlineCutoff
// Se máquina está online, manter sessão ativa
if (machineIsOnline) {
continue
}
// Máquina está offline - encerrar sessão
await ctx.db.patch(session._id, {
status: "ENDED",
endedAt: now,
})
// Calcular duração da sessão
const durationMs = now - session.startedAt
// Registrar evento na timeline
await ctx.db.insert("ticketEvents", {
ticketId: session.ticketId,
type: "LIVE_CHAT_ENDED",
@ -800,8 +835,8 @@ export const autoEndInactiveSessions = mutation({
durationMs,
startedAt: session.startedAt,
endedAt: now,
autoEnded: true, // Flag para indicar encerramento automático
reason: "inatividade",
autoEnded: true,
reason: "maquina_offline",
},
createdAt: now,
})
@ -809,7 +844,8 @@ export const autoEndInactiveSessions = mutation({
endedCount++
}
return { endedCount, hasMore: inactiveSessions.length === maxSessionsPerRun }
console.log(`cron: verificadas ${checkedCount} sessoes, encerradas ${endedCount} (maquinas offline)`)
return { endedCount, checkedCount, hasMore: activeSessions.length === maxSessionsPerRun }
},
})