'use client'; import React, { useMemo, useRef, useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Pin, Check, CheckCheck, Clock, ChevronDown, VolumeX, Star, StarOff, Archive, ArchiveRestore, Trash2, MailOpen, Volume2, PinOff, Mic, FileText, Video, Image as ImageIcon, AlertCircle, BotOff, } from 'lucide-react'; import type { NewWhatsChat, PresenceState } from '../types/inboxTypes'; import { normalizeJid, } from '../utils/inboxUtils'; import Avatar from './ui/Avatar'; interface ChatListItemProps { chat: NewWhatsChat; isActive: boolean; onSelect: (chat: NewWhatsChat) => void; onMenuAction: (action: any, chat: NewWhatsChat) => void; presenceByJid: Record; isFavorite: boolean; draft?: string; } const renderLastStatus = (chat: NewWhatsChat) => { if (chat.last_message_from_me !== 1) return null; switch (chat.last_message_status) { case 'pending': case 'sending': return ; case 'sent': return ; case 'delivered': return ; case 'read': return ; case 'failed': return ; default: return null; } }; const getLastMessageSnippet = (chat: NewWhatsChat, draft?: string): React.ReactNode => { if (draft) return Rascunho: {draft}; const truncate = (value: string, limit = 35) => value.length > limit ? `${value.slice(0, limit - 1)}...` : value; // Group Sender Prefix — WhatsApp exibe "Você:" ou primeiro nome do remetente const isGroup = chat.remote_jid.endsWith('@g.us'); const senderPrefix = isGroup ? (chat.last_message_from_me === 1 ? 'Você: ' : (chat.last_message_sender ? `${chat.last_message_sender.split(' ')[0]}: ` : '')) : ''; const IconLabel = ({ icon: Icon, label }: { icon: any, label: string }) => ( {senderPrefix}{label} ); // Use last_message_type (pipeline do chatStore) para ícones de mídia const msgType = (chat.last_message_type ?? '').toLowerCase(); if (msgType === 'audio' || msgType === 'ptt') return ; if (msgType === 'sticker') return ; if (msgType === 'image') { const caption = chat.last_message_text?.trim(); return caption ? : ; } if (msgType === 'video') { const caption = chat.last_message_text?.trim(); return caption ? : ; } if (msgType === 'document') return ; // Texto puro (text, extendedtext, etc.) const text = chat.last_message_text ?? ''; if (text.trim()) return `${senderPrefix}${truncate(text.trim())}`; return senderPrefix || ''; }; export default function ChatListItem({ chat, isActive, onSelect, onMenuAction, presenceByJid, isFavorite, draft }: ChatListItemProps) { const [menuOpen, setMenuOpen] = useState(false); const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(null); const menuBtnRef = useRef(null); const relativeTime = useMemo(() => { if (!chat.last_message_time) return ''; const date = new Date(chat.last_message_time); if (isNaN(date.getTime())) return ''; // Comparação por data de calendário (não por 24h corridas) const today = new Date(); today.setHours(0, 0, 0, 0); const msgDay = new Date(date); msgDay.setHours(0, 0, 0, 0); const diffDays = Math.round((today.getTime() - msgDay.getTime()) / (1000 * 60 * 60 * 24)); if (diffDays === 0) { return date.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); } if (diffDays === 1) return 'Ontem'; if (diffDays < 7) { // "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom" const short = date.toLocaleDateString('pt-BR', { weekday: 'short' }); return short.replace('.', '').replace(/^\w/, (c) => c.toUpperCase()); } return date.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', year: '2-digit' }); }, [chat.last_message_time]); const isMuted = useMemo(() => { return Boolean(chat.muted_until && new Date(chat.muted_until).getTime() > Date.now()); }, [chat.muted_until]); const menuItems = useMemo(() => { const isPinned = Boolean(chat.is_pinned); const isArchived = Boolean(chat.is_archived); const isGroup = chat.remote_jid.endsWith('@g.us'); return [ { key: 'favorite', label: isFavorite ? 'Remover favorito' : 'Favoritar', icon: isFavorite ? : }, { key: 'archive', label: isArchived ? 'Desarquivar' : 'Arquivar', icon: isArchived ? : }, { key: 'mute', label: isMuted ? 'Desativar silêncio' : 'Silenciar', icon: isMuted ? : }, { key: 'pin', label: isPinned ? 'Desafixar' : 'Fixar conversa', icon: isPinned ? : }, { key: 'mark_read', label: 'Marcar como lida', icon: }, { key: 'delete', label: 'Apagar conversa', icon: , danger: true }, ]; }, [chat, isFavorite, isMuted]); const MENU_H = 252; // 6 itens × ~40px + 12px padding container const MENU_W = 224; // w-56 const openMenu = (anchorX: number, anchorY: number, anchorBottom?: number) => { const triggerBottom = anchorBottom ?? anchorY; const spaceBelow = window.innerHeight - triggerBottom; // Abre abaixo se couber; senão ancora o BOTTOM do menu no TOP do trigger const top = spaceBelow >= MENU_H + 8 ? triggerBottom + 4 : anchorY - MENU_H; const left = Math.min(anchorX, window.innerWidth - MENU_W - 8); setMenuPos({ top: Math.max(8, top), left: Math.max(8, left) }); setMenuOpen(true); }; return ( <> {/* Usamos div+role="button" em vez de {/* Context Menu Portal (fora do item pra não ficar dentro do role=button) */} {menuOpen && menuPos && typeof window !== 'undefined' && createPortal( <>
{ e.stopPropagation(); setMenuOpen(false); }} />
e.stopPropagation()} > {menuItems.map((item: any) => ( ))}
, document.body )} ); }