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:
parent
2682b6e8ac
commit
f4880f32d2
7 changed files with 219 additions and 37 deletions
|
|
@ -38,6 +38,14 @@ import {
|
|||
const MAX_MESSAGE_LENGTH = 4000
|
||||
const MAX_ATTACHMENT_SIZE = 5 * 1024 * 1024 // 5MB
|
||||
const MAX_ATTACHMENTS = 5
|
||||
const CHAT_WIDGET_CHANNEL = "chat-widget-sync"
|
||||
const STORAGE_KEY = "chat-widget-state"
|
||||
|
||||
type ChatWidgetState = {
|
||||
isOpen: boolean
|
||||
isMinimized: boolean
|
||||
activeTicketId: string | null
|
||||
}
|
||||
|
||||
function formatTime(timestamp: number) {
|
||||
return new Date(timestamp).toLocaleTimeString("pt-BR", {
|
||||
|
|
@ -238,10 +246,42 @@ export function ChatWidget() {
|
|||
const { convexUserId } = useAuth()
|
||||
const viewerId = convexUserId ?? null
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isMinimized, setIsMinimized] = useState(false)
|
||||
const [activeTicketId, setActiveTicketId] = useState<string | null>(null)
|
||||
// Inicializar estado a partir do localStorage (para persistir entre reloads)
|
||||
const [isOpen, setIsOpen] = useState(() => {
|
||||
if (typeof window === "undefined") return false
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if (saved) {
|
||||
const state = JSON.parse(saved) as ChatWidgetState
|
||||
return state.isOpen
|
||||
}
|
||||
} catch {}
|
||||
return false
|
||||
})
|
||||
const [isMinimized, setIsMinimized] = useState(() => {
|
||||
if (typeof window === "undefined") return false
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if (saved) {
|
||||
const state = JSON.parse(saved) as ChatWidgetState
|
||||
return state.isMinimized
|
||||
}
|
||||
} catch {}
|
||||
return false
|
||||
})
|
||||
const [activeTicketId, setActiveTicketId] = useState<string | null>(() => {
|
||||
if (typeof window === "undefined") return null
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if (saved) {
|
||||
const state = JSON.parse(saved) as ChatWidgetState
|
||||
return state.activeTicketId
|
||||
}
|
||||
} catch {}
|
||||
return null
|
||||
})
|
||||
const [draft, setDraft] = useState("")
|
||||
const broadcastChannelRef = useRef<BroadcastChannel | null>(null)
|
||||
const [isSending, setIsSending] = useState(false)
|
||||
const [isEndingChat, setIsEndingChat] = useState(false)
|
||||
const [attachments, setAttachments] = useState<UploadedFile[]>([])
|
||||
|
|
@ -279,6 +319,51 @@ export function ChatWidget() {
|
|||
const machineOnline = liveChat?.machineOnline ?? false
|
||||
const machineHostname = liveChat?.machineHostname
|
||||
|
||||
// Sincronizar estado entre abas usando BroadcastChannel
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
// Criar canal de broadcast
|
||||
const channel = new BroadcastChannel(CHAT_WIDGET_CHANNEL)
|
||||
broadcastChannelRef.current = channel
|
||||
|
||||
// Ouvir mensagens de outras abas
|
||||
channel.onmessage = (event: MessageEvent<ChatWidgetState>) => {
|
||||
const state = event.data
|
||||
setIsOpen(state.isOpen)
|
||||
setIsMinimized(state.isMinimized)
|
||||
if (state.activeTicketId) {
|
||||
setActiveTicketId(state.activeTicketId)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
channel.close()
|
||||
broadcastChannelRef.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Salvar estado no localStorage e broadcast para outras abas quando muda
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return
|
||||
|
||||
const state: ChatWidgetState = {
|
||||
isOpen,
|
||||
isMinimized,
|
||||
activeTicketId,
|
||||
}
|
||||
|
||||
// Salvar no localStorage para persistir entre reloads
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
|
||||
} catch {}
|
||||
|
||||
// Broadcast para outras abas
|
||||
if (broadcastChannelRef.current) {
|
||||
broadcastChannelRef.current.postMessage(state)
|
||||
}
|
||||
}, [isOpen, isMinimized, activeTicketId])
|
||||
|
||||
// Auto-selecionar primeira sessão se nenhuma selecionada
|
||||
useEffect(() => {
|
||||
if (!activeTicketId && activeSessions && activeSessions.length > 0) {
|
||||
|
|
@ -316,8 +401,10 @@ export function ChatWidget() {
|
|||
// Marcar mensagens como lidas ao abrir/mostrar chat
|
||||
useEffect(() => {
|
||||
if (!viewerId || !chat || !activeTicketId) return
|
||||
// Só marca quando o widget está aberto e visível
|
||||
// Só marca quando o widget está aberto, expandido e a aba está ativa
|
||||
if (!isOpen || isMinimized) return
|
||||
if (typeof document !== "undefined" && document.visibilityState === "hidden") return
|
||||
|
||||
const unreadIds = chat.messages
|
||||
?.filter((msg) => !msg.readBy?.some((r) => r.userId === viewerId))
|
||||
.map((msg) => msg.id) ?? []
|
||||
|
|
@ -492,7 +579,15 @@ export function ChatWidget() {
|
|||
|
||||
// Nao mostrar se nao logado ou sem sessoes
|
||||
if (!viewerId) return null
|
||||
if (!activeSessions || activeSessions.length === 0) return null
|
||||
if (!activeSessions || activeSessions.length === 0) {
|
||||
// Limpar estado salvo quando nao ha sessoes
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
} catch {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const activeSession = activeSessions.find((s) => s.ticketId === activeTicketId)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue