diff --git a/apps/desktop/src/chat/ChatHubWidget.tsx b/apps/desktop/src/chat/ChatHubWidget.tsx index 8d3da45..cabe10c 100644 --- a/apps/desktop/src/chat/ChatHubWidget.tsx +++ b/apps/desktop/src/chat/ChatHubWidget.tsx @@ -118,11 +118,13 @@ export function ChatHubWidget() { } const handleExpand = async () => { - setIsMinimized(false) try { await invoke("set_hub_minimized", { minimized: false }) + // Aguarda a janela redimensionar antes de atualizar o estado + setTimeout(() => setIsMinimized(false), 100) } catch (err) { console.error("Erro ao expandir hub:", err) + setIsMinimized(false) // Fallback } } @@ -201,7 +203,7 @@ export function ChatHubWidget() { // Expandido - mostrar lista return ( -
+
{ + const now = Date.now() + + // Buscar sessoes ativas que podem ter campos faltando + const activeSessions = await ctx.db + .query("liveChatSessions") + .withIndex("by_status_lastActivity", (q) => q.eq("status", "ACTIVE")) + .take(100) + + let fixed = 0 + let ended = 0 + + for (const session of activeSessions) { + // Se sessao nao tem lastAgentMessageAt, adiciona o valor de startedAt + if (session.lastAgentMessageAt === undefined) { + // Sessao muito antiga (mais de 24h) - encerrar + if (now - session.startedAt > 24 * 60 * 60 * 1000) { + await ctx.db.patch(session._id, { + status: "ENDED", + endedAt: now, + lastAgentMessageAt: session.lastActivityAt ?? session.startedAt, + }) + ended++ + } else { + // Sessao recente - apenas corrigir o campo + await ctx.db.patch(session._id, { + lastAgentMessageAt: session.lastActivityAt ?? session.startedAt, + }) + fixed++ + } + } + } + + console.log(`fixLegacySessions: fixed=${fixed}, ended=${ended}`) + return { fixed, ended, total: activeSessions.length } + }, +}) + // ============================================ // UPLOAD DE ARQUIVOS (Maquina/Cliente) // ============================================ diff --git a/src/components/tickets/ticket-summary-header.tsx b/src/components/tickets/ticket-summary-header.tsx index 8242bf3..95b8418 100644 --- a/src/components/tickets/ticket-summary-header.tsx +++ b/src/components/tickets/ticket-summary-header.tsx @@ -343,13 +343,38 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { const machineOnline = liveChatSession?.machineOnline ?? false const hasActiveSession = Boolean(liveChatSession?.sessionId) const ticketHasAssignee = Boolean(ticket.assignee) - const canStartChat = hasMachine && !hasActiveSession && isStaff && ticketHasAssignee + // Permitir iniciar chat mesmo sem responsavel - vai atribuir automaticamente + const canStartChat = hasMachine && !hasActiveSession && isStaff const handleStartLiveChat = async () => { if (!convexUserId || !ticket.id || isStartingChat) return setIsStartingChat(true) toast.dismiss("live-chat") + try { + // Se nao ha responsavel, atribuir o usuario logado automaticamente + if (!ticketHasAssignee) { + toast.loading("Atribuindo responsavel...", { id: "live-chat" }) + await changeAssignee({ + ticketId: ticket.id as Id<"tickets">, + assigneeId: convexUserId as Id<"users">, + actorId: convexUserId as Id<"users">, + reason: "Atribuido automaticamente ao iniciar chat ao vivo", + }) + // Atualizar estado local + const agent = agents.find((a) => String(a._id) === convexUserId) + if (agent) { + setAssigneeState({ + id: String(agent._id), + name: agent.name, + email: agent.email, + avatarUrl: agent.avatarUrl ?? undefined, + teams: Array.isArray(agent.teams) ? agent.teams.filter((t): t is string => typeof t === "string") : [], + }) + } + } + + toast.loading("Iniciando chat ao vivo...", { id: "live-chat" }) const result = await startLiveChat({ ticketId: ticket.id as Id<"tickets">, actorId: convexUserId as Id<"users">, @@ -357,18 +382,18 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { if (result.isNew) { toast.success("Chat ao vivo iniciado", { id: "live-chat" }) } else { - toast.info("Já existe uma sessão de chat ativa", { id: "live-chat" }) + toast.info("Ja existe uma sessao de chat ativa", { id: "live-chat" }) } } catch (error: unknown) { console.error("[LiveChat] Erro ao iniciar chat:", error) // Extrair mensagem amigável do erro do Convex - let message = "Não foi possível iniciar o chat" + let message = "Nao foi possivel iniciar o chat" if (error instanceof Error) { const errorMsg = error.message.toLowerCase() if (errorMsg.includes("offline")) { - message = "Máquina offline. Aguarde a máquina ficar online para iniciar o chat." - } else if (errorMsg.includes("não encontrad") || errorMsg.includes("not found")) { - message = "Máquina não encontrada" + message = "Maquina offline. Aguarde a maquina ficar online para iniciar o chat." + } else if (errorMsg.includes("nao encontrad") || errorMsg.includes("not found")) { + message = "Maquina nao encontrada" } } toast.error(message, { id: "live-chat" }) @@ -597,7 +622,8 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { const hasAssignee = Boolean(currentAssigneeId) const isCurrentResponsible = hasAssignee && convexUserId ? currentAssigneeId === convexUserId : false const isResolved = status === "RESOLVED" - const canControlWork = !isResolved && isStaff && hasAssignee + // Permitir Play mesmo sem responsavel - vai atribuir automaticamente + const canControlWork = !isResolved && isStaff const canPauseWork = !isResolved && (isAdmin || isCurrentResponsible) const pauseDisabled = !canPauseWork const startDisabled = !canControlWork @@ -605,14 +631,11 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { if (isResolved) { return "Este chamado está encerrado. Reabra o ticket para iniciar um novo atendimento." } - if (!hasAssignee) { - return "Defina um responsável antes de iniciar o atendimento." - } if (!isStaff) { return "Apenas a equipe interna pode iniciar este atendimento." } return "Não é possível iniciar o atendimento neste momento." - }, [isResolved, hasAssignee, isStaff]) + }, [isResolved, isStaff]) useEffect(() => { if (!customersInitialized) { @@ -1097,10 +1120,34 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { const handleStartWork = async (workType: "INTERNAL" | "EXTERNAL") => { if (!convexUserId) return + + // Se nao ha responsavel, atribuir o usuario logado automaticamente if (!assigneeState?.id) { - toast.error("Defina um responsável antes de iniciar o atendimento.") - return + toast.loading("Atribuindo responsavel e iniciando...", { id: "work" }) + try { + await changeAssignee({ + ticketId: ticket.id as Id<"tickets">, + assigneeId: convexUserId as Id<"users">, + actorId: convexUserId as Id<"users">, + reason: "Atribuido automaticamente ao iniciar atendimento", + }) + // Atualizar estado local + const agent = agents.find((a) => String(a._id) === convexUserId) + if (agent) { + setAssigneeState({ + id: String(agent._id), + name: agent.name, + email: agent.email, + avatarUrl: agent.avatarUrl ?? undefined, + teams: Array.isArray(agent.teams) ? agent.teams.filter((t): t is string => typeof t === "string") : [], + }) + } + } catch (err) { + toast.error("Nao foi possivel atribuir responsavel.", { id: "work" }) + return + } } + toast.dismiss("work") toast.loading("Iniciando atendimento...", { id: "work" }) try {