FEAT: Adição de horas as mensgens

This commit is contained in:
Rafael Alves Lopes 2026-05-19 16:39:01 -03:00
parent 217d566057
commit 2e97ac6e5a
6 changed files with 53 additions and 43 deletions

View File

@ -23,13 +23,8 @@ function ChannelBadge({ channel }) {
); );
} }
function PresenceDot({ status }) { function ActivityDot({ status }) {
const color = const color = status === 'away' ? '#e5a22a' : '#dc2626';
status === 'online'
? '#16a34a'
: status === 'away'
? '#e5a22a'
: '#dc2626';
return ( return (
<span <span
@ -134,7 +129,7 @@ export function ChatConversationList({
> >
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem' }}> <div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem' }}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', minWidth: 0 }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', minWidth: 0 }}>
<PresenceDot status={contact.status} /> <ActivityDot status={contact.status} />
<strong style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <strong style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{contact.name} {contact.name}
</strong> </strong>

View File

@ -22,6 +22,13 @@ function parseMessageText(text) {
}; };
} }
function formatMessageTime(timestamp) {
if (!timestamp) return '';
const numericTimestamp = Number(timestamp);
const date = new Date(numericTimestamp > 1000000000000 ? numericTimestamp : numericTimestamp * 1000);
return date.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
}
function MediaRenderer({ message, contactId, onLoadMedia, isAgent }) { function MediaRenderer({ message, contactId, onLoadMedia, isAgent }) {
const mediaUrl = useMemo(() => getMediaUrl(message.media), [message.media]); const mediaUrl = useMemo(() => getMediaUrl(message.media), [message.media]);
const mimetype = message.media?.mimetype || ''; const mimetype = message.media?.mimetype || '';
@ -196,19 +203,14 @@ function AttachmentPreview({ file, onRemove }) {
); );
} }
function ContactPresence({ contact }) { function ContactActivity({ contact }) {
if (!contact) { if (!contact) {
return null; return null;
} }
const status = contact.status || 'offline'; const status = contact.status || 'offline';
const color = const color = status === 'away' ? '#e5a22a' : '#dc2626';
status === 'online' const label = contact.lastSeen || 'Sem atividade recente';
? '#16a34a'
: status === 'away'
? '#e5a22a'
: '#dc2626';
const label = status === 'online' ? 'Online agora' : contact.lastSeen || 'Offline';
return ( return (
<span <span
@ -301,7 +303,7 @@ export function ChatWindow({
> >
<div> <div>
<strong style={{ display: 'block', fontSize: '1.15rem' }}>{safeContact.name}</strong> <strong style={{ display: 'block', fontSize: '1.15rem' }}>{safeContact.name}</strong>
<ContactPresence contact={safeContact} /> <ContactActivity contact={safeContact} />
</div> </div>
<div <div
@ -397,6 +399,7 @@ export function ChatWindow({
const isAgent = message.sender === 'agent'; const isAgent = message.sender === 'agent';
const isSystem = message.sender === 'system'; const isSystem = message.sender === 'system';
const parsedText = parseMessageText(message.text); const parsedText = parseMessageText(message.text);
const messageTime = formatMessageTime(message.timestamp);
if (isSystem) { if (isSystem) {
return ( return (
@ -464,6 +467,18 @@ export function ChatWindow({
{parsedText.body} {parsedText.body}
</span> </span>
) : null} ) : null}
{messageTime ? (
<span
style={{
justifySelf: 'end',
fontSize: '0.72rem',
lineHeight: 1,
color: isAgent ? 'rgba(255,255,255,0.7)' : 'var(--color-text-soft)',
}}
>
{messageTime}
</span>
) : null}
</div> </div>
); );
})} })}

View File

@ -38,22 +38,17 @@ function getPreviewFromMessage(message) {
function normalizeChat(chat) { function normalizeChat(chat) {
const id = getSerializedId(chat.id); const id = getSerializedId(chat.id);
const lastActivitySeconds = chat.timestamp ? Math.floor(Date.now() / 1000) - chat.timestamp : null;
const isRecentlyActive = lastActivitySeconds !== null && lastActivitySeconds < 300;
const assignment = chat.assignment || null; const assignment = chat.assignment || null;
const lastSeenTimestamp = chat.timestamp || null;
return { return {
id, id,
name: getContactName(chat), name: getContactName(chat),
channel: 'WhatsApp', channel: 'WhatsApp',
status: isRecentlyActive ? 'online' : 'away', status: lastSeenTimestamp ? 'away' : 'offline',
area: assignment?.area_nome || (assignment?.area_id ? String(assignment.area_id) : 'Sem fila'), area: assignment?.area_nome || (assignment?.area_id ? String(assignment.area_id) : 'Sem fila'),
areaId: assignment?.area_id || null, areaId: assignment?.area_id || null,
lastSeen: isRecentlyActive lastSeen: lastSeenTimestamp ? `Ultima atividade as ${formatTime(lastSeenTimestamp)}` : 'Sem atividade recente',
? 'Online agora'
: chat.timestamp
? `Visto as ${formatTime(chat.timestamp)}`
: 'Sem atividade recente',
preview: chat.preview || chat.lastMessage?.body || '', preview: chat.preview || chat.lastMessage?.body || '',
time: formatTime(chat.timestamp) || 'Agora', time: formatTime(chat.timestamp) || 'Agora',
unread: chat.unreadCount || 0, unread: chat.unreadCount || 0,
@ -77,6 +72,11 @@ function normalizeMessage(message) {
}; };
} }
function isDisplayableMessage(message) {
const text = String(message?.text ?? message?.body ?? '').trim();
return Boolean(text || message?.hasMedia || message?.media);
}
function getComparableMessageTime(message) { function getComparableMessageTime(message) {
if (message.timestamp) return Number(message.timestamp); if (message.timestamp) return Number(message.timestamp);
if (typeof message.id === 'string' && message.id.startsWith('temp-')) { if (typeof message.id === 'string' && message.id.startsWith('temp-')) {
@ -316,10 +316,12 @@ export function useChat() {
setMessagesByContact((current) => ({ setMessagesByContact((current) => ({
...current, ...current,
[activeContactId]: dedupeMessages( [activeContactId]: dedupeMessages(
data.map((message) => ({ data
...normalizeMessage(message), .map((message) => ({
chatId: activeContactId, ...normalizeMessage(message),
})), chatId: activeContactId,
}))
.filter(isDisplayableMessage),
), ),
})); }));
setApiError(null); setApiError(null);
@ -345,6 +347,10 @@ export function useChat() {
...normalizeMessage(incomingMessage), ...normalizeMessage(incomingMessage),
chatId: contactId, chatId: contactId,
}; };
if (!isDisplayableMessage(message)) {
clearIncomingMessage();
return;
}
const preview = getPreviewFromMessage(message); const preview = getPreviewFromMessage(message);
setMessagesByContact((current) => { setMessagesByContact((current) => {
@ -367,14 +373,16 @@ export function useChat() {
id: contactId, id: contactId,
name: incomingMessage.notifyName || contactId.split('@')[0], name: incomingMessage.notifyName || contactId.split('@')[0],
channel: 'WhatsApp', channel: 'WhatsApp',
status: 'online', status: 'away',
area: 'Sem fila', area: 'Sem fila',
lastSeen: 'Online agora', lastSeen: 'Visto agora',
unread: 0, unread: 0,
assignment: null, assignment: null,
}), }),
preview, preview,
time: 'Agora', time: 'Agora',
status: 'away',
lastSeen: 'Ultima atividade agora',
unread: unread:
incomingMessage.fromMe || contactId === activeContactRef.current incomingMessage.fromMe || contactId === activeContactRef.current
? 0 ? 0

View File

@ -3,9 +3,9 @@ export const chatContacts = [
id: 'maria-souza', id: 'maria-souza',
name: 'Maria Souza', name: 'Maria Souza',
channel: 'WhatsApp', channel: 'WhatsApp',
status: 'online', status: 'away',
area: 'Suporte', area: 'Suporte',
lastSeen: 'Online agora', lastSeen: 'Ultima atividade as 09:42',
preview: 'Preciso atualizar o cadastro do meu pedido.', preview: 'Preciso atualizar o cadastro do meu pedido.',
time: '09:42', time: '09:42',
unread: 2, unread: 2,
@ -22,7 +22,7 @@ export const chatContacts = [
channel: 'SMS', channel: 'SMS',
status: 'offline', status: 'offline',
area: 'Financeiro', area: 'Financeiro',
lastSeen: 'Visto ha 12 min', lastSeen: 'Ultima atividade as 08:15',
preview: 'Pode me ligar em 10 minutos?', preview: 'Pode me ligar em 10 minutos?',
time: '08:15', time: '08:15',
unread: 1, unread: 1,

View File

@ -301,7 +301,7 @@ export function MessagesWorkspace({
{safeActiveConversation.name} {safeActiveConversation.name}
</strong> </strong>
<span style={{ color: 'var(--color-text-soft)' }}> <span style={{ color: 'var(--color-text-soft)' }}>
{safeActiveConversation.status === 'online' ? 'Online agora' : 'Offline'} {safeActiveConversation.lastSeen || 'Sem atividade recente'}
</span> </span>
</div> </div>
<div style={{ display: 'flex', gap: '0.6rem', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: '0.6rem', flexWrap: 'wrap' }}>

View File

@ -7,7 +7,6 @@ export function useWhatsappSocket() {
const [qrCode, setQrCode] = useState(null); const [qrCode, setQrCode] = useState(null);
const [status, setStatus] = useState('DISCONNECTED'); const [status, setStatus] = useState('DISCONNECTED');
const [incomingMessage, setIncomingMessage] = useState(null); const [incomingMessage, setIncomingMessage] = useState(null);
const [presenceUpdate, setPresenceUpdate] = useState(null);
const socketRef = useRef(null); const socketRef = useRef(null);
useEffect(() => { useEffect(() => {
@ -44,11 +43,6 @@ export function useWhatsappSocket() {
setIncomingMessage(message); setIncomingMessage(message);
}); });
newSocket.on('presence', (presence) => {
console.log('Atualização de presença:', presence);
setPresenceUpdate(presence);
});
return () => { return () => {
newSocket.disconnect(); newSocket.disconnect();
socketRef.current = null; socketRef.current = null;
@ -60,8 +54,6 @@ export function useWhatsappSocket() {
qrCode, qrCode,
status, status,
incomingMessage, incomingMessage,
presenceUpdate,
clearIncomingMessage: () => setIncomingMessage(null), clearIncomingMessage: () => setIncomingMessage(null),
clearPresenceUpdate: () => setPresenceUpdate(null)
}; };
} }