'use client'; import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Search, MoreVertical, User, MessageSquare, ChevronDown, RefreshCw, MessageSquarePlus, RotateCw, ArrowLeft, } from 'lucide-react'; import { useRouter } from 'next/router'; import { motion, AnimatePresence } from 'framer-motion'; import { format } from 'date-fns'; import { ptBR } from 'date-fns/locale'; import type { NewWhatsChat } from '../../types/inboxTypes'; import { getAvatarProxyUrl, formatPhone } from '../../utils/inboxUtils'; import { useNewInboxBridge } from '../../hooks/inbox/useNewInboxBridge'; import { useInstanceSelector } from '../../hooks/inbox/useInstanceSelector'; import { useChatFilter } from '../../hooks/inbox/useChatFilter'; import { useChatWindow } from '../../hooks/inbox/useChatWindow'; import { useMessageInput } from '../../hooks/inbox/useMessageInput'; import { messageApi } from '../../services/chatApiService'; import { useProtocolPanel } from '../../hooks/useProtocolPanel'; import ChatSidebar from '../../components/ChatSidebar'; import ThinSidebar, { TabType } from '../../components/ThinSidebar'; import InboxHeader from '../../components/InboxHeader'; import MessageArea from '../../components/MessageArea'; import InputBar from '../../components/InputBar'; import NewConversationModal from '../../components/NewConversationModal'; import ContactProfile from '../../components/ContactProfile'; import ProtocolPanel from '../../components/ProtocolPanel'; type Chat = NewWhatsChat; export default function WhatsAppInboxPage() { const router = useRouter() // ── Seleção de instância (hook dedicado) ───────────────────────────────── const { instances, activeInstance, mappedActiveInstance, connectedInstances, isConnected, dropdownRef, selectInstance, refreshInstances, handleConnect: handleConnectInstance, } = useInstanceSelector() // ── Inbox bridge (chats + mensagens) ────────────────────────────────────── const { chats: chatsStore, selectedChat, messages, loadingChats: loading, loadingMessages, presenceByJid, handleSelectChat: handleSelectChatBridge, handleSendText, handleLoadMoreHistory, handleMenuAction, } = useNewInboxBridge() // ── Filtros de chat (hook dedicado) ─────────────────────────────────────── const { searchTerm, activeFilter, chatScope, chatsByScope, isFavoriteChat, setSearchTerm, setActiveFilter, setChatScope, toggleFavorite, } = useChatFilter(chatsStore) // ── Chat window (hook dedicado) ─────────────────────────────────────────── const { scrollRef, isTyping, replyingTo, reactionPickerFor, hasMoreHistory, loadingMore, isProfileOpen, isSyncingAvatar, isChatMenuOpen, setReplyingTo, setIsProfileOpen, setIsChatMenuOpen, handleTyping, handleScroll, handleSelectChat: onChatWindowSelect, handleReactToMessage, handleSendMedia, handleSendAudio, handleSyncAvatar, toggleReactionPicker, } = useChatWindow({ selectedChat, activeInstanceId: activeInstance?.instance ?? null, onLoadMoreHistory: handleLoadMoreHistory, }) // ── Mensagem de entrada (hook dedicado) ─────────────────────────────────── const { newMessage, setNewMessage, mentions, setMentions, handleSendMessage } = useMessageInput({ selectedChat, replyingTo, onClearReply: () => setReplyingTo(null), onSendText: handleSendText, }) // ── Painel de Protocolo ─────────────────────────────────────────────────── const [isProtocolOpen, setIsProtocolOpen] = useState(false) const protocolPanel = useProtocolPanel(selectedChat ? String(selectedChat.id) : null) // ── Estado local de UI ──────────────────────────────────────────────────── const [activeTab, setActiveTab] = useState('chats') const [isInstanceDropdownOpen, setIsInstanceDropdownOpen] = useState(false) const [isOptionsMenuOpen, setIsOptionsMenuOpen] = useState(false) const [drafts] = useState>({}) const [isMobile, setIsMobile] = useState(false) const [isNewConvoModalOpen, setIsNewConvoModalOpen] = useState(false) // Preloader: começa visível e some após o primeiro ciclo loading true→false // O delay de 400ms dá tempo ao browser iniciar o carregamento dos avatares // antes do overlay sumir, evitando o flash de skeletons pulsando. const [pageReady, setPageReady] = useState(false) const prevLoadingRef = useRef(false) const readyTimerRef = useRef | null>(null) useEffect(() => { if (prevLoadingRef.current && !loading) { readyTimerRef.current = setTimeout(() => setPageReady(true), 400) } prevLoadingRef.current = loading return () => { if (readyTimerRef.current) clearTimeout(readyTimerRef.current) } }, [loading]) const optionsRef = useRef(null) const initialized = useRef(false) // ── Setup mobile + click-outside ───────────────────────────────────────── useEffect(() => { if (initialized.current) return initialized.current = true const resize = () => setIsMobile(window.innerWidth < 768) window.addEventListener('resize', resize) resize() const click = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) setIsInstanceDropdownOpen(false) if (optionsRef.current && !optionsRef.current.contains(e.target as Node)) setIsOptionsMenuOpen(false) } document.addEventListener('mousedown', click) return () => { window.removeEventListener('resize', resize); document.removeEventListener('mousedown', click) } }, []) // ── Handlers ───────────────────────────────────────────────────────────── const handleSelectChat = useCallback((chat: Chat | null) => { handleSelectChatBridge(chat) onChatWindowSelect() setIsProtocolOpen(false) }, [handleSelectChatBridge, onChatWindowSelect]) const handleChatMenuAction = useCallback((action: string, chat: Chat) => { handleMenuAction(action, chat, toggleFavorite) }, [handleMenuAction, toggleFavorite]) const handleFetchChats = useCallback(async () => { await refreshInstances() setIsOptionsMenuOpen(false) }, [refreshInstances]) // Voltar: se há chat aberto, fecha-o; senão, página anterior const handleBack = useCallback(() => { if (selectedChat) { handleSelectChat(null) } else { router.back() } }, [selectedChat, handleSelectChat, router]) // ESC fecha chat ou vai para página anterior useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') handleBack() } document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) }, [handleBack]) // ── Render ──────────────────────────────────────────────────────────────── return (
{/* Preloader global – visível até o primeiro carregamento de chats completar */} {!pageReady && (
Carregando conversas…
)}
{activeTab === 'chats' && (
{/* Header da sidebar */}
setIsInstanceDropdownOpen(!isInstanceDropdownOpen)} className="flex items-center gap-3 cursor-pointer p-1">
{activeInstance?.instance ? { (e.currentTarget as HTMLImageElement).style.display = 'none' }} /> : }
{activeInstance?.nome || 'Instância'}
{isInstanceDropdownOpen && ( {instances.map(i => ( ))} )}
{/* end left flex */}
{isOptionsMenuOpen && ( )}
{/* Busca */}
setSearchTerm(e.target.value)} />
{/* Lista de chats */}
)} {/* Área principal */}
{/* Overlay branqueador sobre o padrão */}
{selectedChat ? ( <>
{ setIsProfileOpen(!isProfileOpen); setIsProtocolOpen(false) }} onBack={() => handleSelectChat(null)} onSyncAvatar={handleSyncAvatar} isChatMenuOpen={isChatMenuOpen} isSyncingAvatar={isSyncingAvatar} isProtocolOpen={isProtocolOpen} hasActiveProtocol={protocolPanel.hasActive} onSearchClick={() => {}} onToggleMenu={() => setIsChatMenuOpen(!isChatMenuOpen)} onProtocolClick={() => { setIsProtocolOpen(!isProtocolOpen); setIsProfileOpen(false) }} /> {}} onDelete={() => {}} onEdit={() => {}} quickReactions={['👍', '❤️', '😂', '😮', '😢', '🙏']} getGroupSenderLabel={(msg: any) => { if (msg.direction === 'out') return 'Você' if (msg.participant) return msg.participant if (msg.senderJid) { if (msg.senderJid.endsWith('@lid')) return msg.participant || '' const phone = msg.senderJid.split('@')[0].split(':')[0] return formatPhone(phone, msg.senderJid) } return '' }} /> setReplyingTo(null)} replyingTo={replyingTo} selectedChat={selectedChat} isMobile={isMobile} onSendMedia={handleSendMedia} onSendAudio={handleSendAudio} instanceId={activeInstance?.instance ?? null} mentions={mentions} onMentionsChange={setMentions} />
setIsProfileOpen(false)} /> setIsProtocolOpen(false)} {...protocolPanel} /> ) : (

NewWhats

Selecione uma conversa para começar.

)}
setIsNewConvoModalOpen(false)} activeInstance={mappedActiveInstance as any} connectedInstances={connectedInstances as any} sendMessage={async (instanceId: string, phone: string, text: string) => { try { const res = await messageApi.send(instanceId, `${phone}@s.whatsapp.net`, text) return { ok: true, messageId: res?.messageId } } catch { return { ok: false } } }} />
) }