feat(chat): vincula timer automaticamente com inicio/fim do chat ao vivo
- Ao iniciar chat: inicia timer EXTERNAL automaticamente se nao houver sessao ativa - Ao encerrar chat: pausa timer automaticamente se houver sessao ativa - Adiciona razao de pausa END_LIVE_CHAT para identificar pausas automaticas 🤖 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
47ccdc51a7
commit
14480df9f3
2 changed files with 89 additions and 2 deletions
|
|
@ -168,7 +168,40 @@ export const startSession = mutation({
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
})
|
})
|
||||||
|
|
||||||
return { sessionId, isNew: true }
|
// Iniciar timer automaticamente se nao houver sessao de trabalho ativa
|
||||||
|
// O chat ao vivo eh considerado trabalho EXTERNAL (interacao com cliente)
|
||||||
|
let workSessionId: Id<"ticketWorkSessions"> | null = null
|
||||||
|
if (!ticket.activeSessionId && ticket.assigneeId) {
|
||||||
|
workSessionId = await ctx.db.insert("ticketWorkSessions", {
|
||||||
|
ticketId,
|
||||||
|
agentId: ticket.assigneeId,
|
||||||
|
workType: "EXTERNAL",
|
||||||
|
startedAt: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
await ctx.db.patch(ticketId, {
|
||||||
|
working: true,
|
||||||
|
activeSessionId: workSessionId,
|
||||||
|
status: "AWAITING_ATTENDANCE",
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
await ctx.db.insert("ticketEvents", {
|
||||||
|
ticketId,
|
||||||
|
type: "WORK_STARTED",
|
||||||
|
payload: {
|
||||||
|
actorId,
|
||||||
|
actorName: agent.name,
|
||||||
|
actorAvatar: agent.avatarUrl,
|
||||||
|
sessionId: workSessionId,
|
||||||
|
workType: "EXTERNAL",
|
||||||
|
source: "live_chat_auto",
|
||||||
|
},
|
||||||
|
createdAt: now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sessionId, isNew: true, workSessionStarted: workSessionId !== null }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -225,7 +258,60 @@ export const endSession = mutation({
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
})
|
})
|
||||||
|
|
||||||
return { ok: true }
|
// Pausar timer automaticamente se houver sessao de trabalho ativa
|
||||||
|
let workSessionPaused = false
|
||||||
|
const ticket = await ctx.db.get(session.ticketId)
|
||||||
|
if (ticket?.activeSessionId) {
|
||||||
|
const workSession = await ctx.db.get(ticket.activeSessionId)
|
||||||
|
if (workSession && !workSession.stoppedAt) {
|
||||||
|
const workDurationMs = now - workSession.startedAt
|
||||||
|
const sessionType = (workSession.workType ?? "INTERNAL").toUpperCase()
|
||||||
|
const deltaInternal = sessionType === "INTERNAL" ? workDurationMs : 0
|
||||||
|
const deltaExternal = sessionType === "EXTERNAL" ? workDurationMs : 0
|
||||||
|
|
||||||
|
// Encerrar sessao de trabalho
|
||||||
|
await ctx.db.patch(ticket.activeSessionId, {
|
||||||
|
stoppedAt: now,
|
||||||
|
durationMs: workDurationMs,
|
||||||
|
pauseReason: "END_LIVE_CHAT",
|
||||||
|
pauseNote: "Pausa automática ao encerrar chat ao vivo",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Atualizar ticket
|
||||||
|
await ctx.db.patch(session.ticketId, {
|
||||||
|
working: false,
|
||||||
|
activeSessionId: undefined,
|
||||||
|
status: "PAUSED",
|
||||||
|
totalWorkedMs: (ticket.totalWorkedMs ?? 0) + workDurationMs,
|
||||||
|
internalWorkedMs: (ticket.internalWorkedMs ?? 0) + deltaInternal,
|
||||||
|
externalWorkedMs: (ticket.externalWorkedMs ?? 0) + deltaExternal,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Registrar evento de pausa
|
||||||
|
await ctx.db.insert("ticketEvents", {
|
||||||
|
ticketId: session.ticketId,
|
||||||
|
type: "WORK_PAUSED",
|
||||||
|
payload: {
|
||||||
|
actorId,
|
||||||
|
actorName: actor.name,
|
||||||
|
actorAvatar: actor.avatarUrl,
|
||||||
|
sessionId: workSession._id,
|
||||||
|
sessionDurationMs: workDurationMs,
|
||||||
|
workType: sessionType,
|
||||||
|
pauseReason: "END_LIVE_CHAT",
|
||||||
|
pauseReasonLabel: "Chat ao vivo encerrado",
|
||||||
|
pauseNote: "Pausa automática ao encerrar chat ao vivo",
|
||||||
|
source: "live_chat_auto",
|
||||||
|
},
|
||||||
|
createdAt: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
workSessionPaused = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, workSessionPaused }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const PAUSE_REASON_LABELS: Record<string, string> = {
|
||||||
NO_CONTACT: "Falta de contato",
|
NO_CONTACT: "Falta de contato",
|
||||||
WAITING_THIRD_PARTY: "Aguardando terceiro",
|
WAITING_THIRD_PARTY: "Aguardando terceiro",
|
||||||
IN_PROCEDURE: "Em procedimento",
|
IN_PROCEDURE: "Em procedimento",
|
||||||
|
END_LIVE_CHAT: "Chat ao vivo encerrado",
|
||||||
[LUNCH_BREAK_REASON]: LUNCH_BREAK_PAUSE_LABEL,
|
[LUNCH_BREAK_REASON]: LUNCH_BREAK_PAUSE_LABEL,
|
||||||
};
|
};
|
||||||
const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue