FEAT: Editado Home page para se adequar ao serviço de pausa

This commit is contained in:
Rafael Alves Lopes 2026-05-25 14:32:41 -03:00
parent fe40e8bd76
commit 4b0a4bb3e3
7 changed files with 345 additions and 52 deletions

View File

@ -318,14 +318,16 @@ export function ChatWindow({
assignmentLabel, assignmentLabel,
transferNote, transferNote,
isReplying, isReplying,
isPaused = false,
pauseDurationLabel = '00:00',
isMobile = false, isMobile = false,
}) { }) {
const messagesRef = useRef(null); const messagesRef = useRef(null);
const safeContact = contact || { const safeContact = contact || {
id: '', id: '',
name: 'Nenhuma conversa ativa', name: isPaused ? 'Atendimento pausado' : 'Nenhuma conversa ativa',
status: 'offline', status: 'offline',
lastSeen: 'Aguardando fila do Omnino', lastSeen: isPaused ? `Pausa em andamento: ${pauseDurationLabel}` : 'Aguardando fila do Omnino',
}; };
useEffect(() => { useEffect(() => {
@ -340,6 +342,62 @@ export function ChatWindow({
}); });
}, [messages, isReplying]); }, [messages, isReplying]);
if (isPaused) {
return (
<section
style={{
background: '#fff',
border: '1px solid var(--color-border)',
borderRadius: '28px',
overflow: 'hidden',
display: 'grid',
gridTemplateRows: 'auto minmax(0, 1fr)',
height: isMobile ? 'auto' : 'min(760px, calc(100vh - 190px))',
minHeight: isMobile ? 420 : 0,
minWidth: 0,
}}
>
<header
style={{
padding: '1.25rem 1.5rem',
borderBottom: '1px solid var(--color-border)',
}}
>
<strong style={{ display: 'block', fontSize: '1.15rem' }}>Atendimento pausado</strong>
<span style={{ color: 'var(--color-text-soft)' }}>Pausa em andamento: {pauseDurationLabel}</span>
</header>
<div
style={{
padding: '1.5rem',
display: 'grid',
placeItems: 'center',
minHeight: 0,
background:
'radial-gradient(circle at top left, rgba(0, 164, 183, 0.06), transparent 22%), linear-gradient(180deg, rgba(245, 248, 251, 0.8), rgba(255, 255, 255, 0.95))',
}}
>
<div
style={{
maxWidth: 460,
border: '1px solid var(--color-border)',
borderRadius: 20,
padding: '1.2rem',
background: '#fff',
color: 'var(--color-text-soft)',
fontWeight: 700,
lineHeight: 1.5,
textAlign: 'center',
}}
>
Voce esta em pausa ha {pauseDurationLabel}. Retome o atendimento pela Home para visualizar a fila,
assumir chamados e responder clientes.
</div>
</div>
</section>
);
}
return ( return (
<section <section
style={{ style={{
@ -583,7 +641,9 @@ export function ChatWindow({
fontWeight: 700, fontWeight: 700,
}} }}
> >
Nenhuma mensagem carregada. {isPaused
? `Voce esta em pausa ha ${pauseDurationLabel}. Volte da pausa para visualizar a fila e seus atendimentos.`
: 'Nenhuma mensagem carregada.'}
</div> </div>
) : null} ) : null}
@ -625,7 +685,9 @@ export function ChatWindow({
}} }}
> >
<span style={{ display: 'block' }}> <span style={{ display: 'block' }}>
{canAssumeChat {isPaused
? `Voce esta em pausa ha ${pauseDurationLabel}. Nenhum atendimento sera exibido ate voce voltar.`
: canAssumeChat
? 'Este atendimento está na fila. Assuma para responder ou transferir.' ? 'Este atendimento está na fila. Assuma para responder ou transferir.'
: assignmentLabel || 'Este atendimento está atribuído a outro usuário.'} : assignmentLabel || 'Este atendimento está atribuído a outro usuário.'}
</span> </span>
@ -681,7 +743,9 @@ export function ChatWindow({
}} }}
disabled={!safeContact.id || !canReply} disabled={!safeContact.id || !canReply}
placeholder={ placeholder={
!safeContact.id isPaused
? 'Voce esta em pausa'
: !safeContact.id
? 'Aguardando conversa entrar em uma fila' ? 'Aguardando conversa entrar em uma fila'
: canReply : canReply
? 'Escreva sua mensagem...' ? 'Escreva sua mensagem...'

View File

@ -4,9 +4,33 @@ import { API_BASE_URL } from '../../../shared/services/apiConfig';
import { getAccessOptions, getAccessUsers } from '../../management/services/adminAccessService'; import { getAccessOptions, getAccessUsers } from '../../management/services/adminAccessService';
import { getCurrentUser, getCurrentUserProfile } from '../../auth/services/sessionService'; import { getCurrentUser, getCurrentUserProfile } from '../../auth/services/sessionService';
import { transferAreas as fallbackTransferAreas } from '../services/chatMocks'; import { transferAreas as fallbackTransferAreas } from '../services/chatMocks';
import {
getAgentPresence,
listAgentPresence,
pauseAgent,
resumeAgent,
} from '../services/agentPresenceService';
const MAX_ATTACHMENT_SIZE_BYTES = 15 * 1024 * 1024; const MAX_ATTACHMENT_SIZE_BYTES = 15 * 1024 * 1024;
function getPresenceByUserId(presenceList, userId) {
return presenceList.find((presence) => Number(presence.user_id) === Number(userId)) || null;
}
function formatPauseDuration(totalSeconds) {
const seconds = Math.max(0, Number(totalSeconds || 0));
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (hours > 0) {
return `${hours}h ${String(remainingMinutes).padStart(2, '0')}m`;
}
return `${String(remainingMinutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
}
function getLastMessageFromMe(messages = []) { function getLastMessageFromMe(messages = []) {
const lastMessage = [...messages].reverse().find(isDisplayableMessage); const lastMessage = [...messages].reverse().find(isDisplayableMessage);
if (!lastMessage) return false; if (!lastMessage) return false;
@ -210,6 +234,10 @@ export function useChat() {
const [attachedFile, setAttachedFile] = useState(null); const [attachedFile, setAttachedFile] = useState(null);
const [areaOptions, setAreaOptions] = useState([]); const [areaOptions, setAreaOptions] = useState([]);
const [accessUsers, setAccessUsers] = useState([]); const [accessUsers, setAccessUsers] = useState([]);
const [presenceList, setPresenceList] = useState([]);
const [agentPresence, setAgentPresence] = useState(null);
const [pauseSeconds, setPauseSeconds] = useState(0);
const [isPresenceLoading, setIsPresenceLoading] = useState(false);
const [selectedArea, setSelectedArea] = useState('Sem fila'); const [selectedArea, setSelectedArea] = useState('Sem fila');
const [isTransferOpen, setIsTransferOpen] = useState(false); const [isTransferOpen, setIsTransferOpen] = useState(false);
const [transferArea, setTransferArea] = useState(currentUserAreas[0] || 'Suporte'); const [transferArea, setTransferArea] = useState(currentUserAreas[0] || 'Suporte');
@ -221,6 +249,7 @@ export function useChat() {
const [apiError, setApiError] = useState(null); const [apiError, setApiError] = useState(null);
const activeContactRef = useRef(activeContactId); const activeContactRef = useRef(activeContactId);
const contactsRef = useRef(contacts); const contactsRef = useRef(contacts);
const isPaused = agentPresence?.status === 'paused';
const activeContact = useMemo( const activeContact = useMemo(
() => { () => {
@ -240,8 +269,12 @@ export function useChat() {
const usersInTransferArea = accessUsers.filter((user) => const usersInTransferArea = accessUsers.filter((user) =>
user.areas?.some((area) => area.nome === transferArea) || user.areaPrincipal?.nome === transferArea, user.areas?.some((area) => area.nome === transferArea) || user.areaPrincipal?.nome === transferArea,
); );
const availableUsersInTransferArea = usersInTransferArea.filter((user) => {
const presence = getPresenceByUserId(presenceList, user.id);
return !presence || presence.status === 'available';
});
const isSameUserArea = currentUserAreas.includes(transferArea); const isSameUserArea = currentUserAreas.includes(transferArea);
const attendants = isSameUserArea ? usersInTransferArea : []; const attendants = isSameUserArea ? availableUsersInTransferArea : [];
const activeAssignment = activeContact?.assignment || null; const activeAssignment = activeContact?.assignment || null;
const isAssignedToCurrentUser = Boolean( const isAssignedToCurrentUser = Boolean(
activeAssignment?.user_id && currentUserId && Number(activeAssignment.user_id) === currentUserId, activeAssignment?.user_id && currentUserId && Number(activeAssignment.user_id) === currentUserId,
@ -251,8 +284,8 @@ export function useChat() {
activeAssignment?.status === 'queued' && activeAssignment?.status === 'queued' &&
(isAdminUser || !activeAssignment.area_nome || currentUserAreas.includes(activeAssignment.area_nome)), (isAdminUser || !activeAssignment.area_nome || currentUserAreas.includes(activeAssignment.area_nome)),
); );
const canAssumeChat = Boolean(activeContact?.id?.includes('@') && currentUserId && isQueuedForUserArea); const canAssumeChat = Boolean(!isPaused && activeContact?.id?.includes('@') && currentUserId && isQueuedForUserArea);
const canReply = Boolean(isAssignedToCurrentUser && !isWaitingCustomerReply); const canReply = Boolean(!isPaused && isAssignedToCurrentUser && !isWaitingCustomerReply);
const assignmentLabel = activeAssignment?.user_id const assignmentLabel = activeAssignment?.user_id
? isWaitingCustomerReply ? isWaitingCustomerReply
? 'Aguardando resposta do cliente para liberar novas mensagens' ? 'Aguardando resposta do cliente para liberar novas mensagens'
@ -268,7 +301,7 @@ export function useChat() {
useEffect(() => { useEffect(() => {
setTransferAttendant(attendants[0]?.id ? String(attendants[0].id) : ''); setTransferAttendant(attendants[0]?.id ? String(attendants[0].id) : '');
}, [transferArea, accessUsers]); }, [transferArea, accessUsers, presenceList]);
useEffect(() => { useEffect(() => {
activeContactRef.current = activeContactId; activeContactRef.current = activeContactId;
@ -283,14 +316,23 @@ export function useChat() {
async function loadAccessData() { async function loadAccessData() {
try { try {
const [options, users] = await Promise.all([getAccessOptions(), getAccessUsers()]); const [options, users, presences, currentPresence] = await Promise.all([
getAccessOptions(),
getAccessUsers(),
listAgentPresence(),
currentUserId ? getAgentPresence(currentUserId) : Promise.resolve(null),
]);
if (!isMounted) return; if (!isMounted) return;
setAreaOptions(options.areas || []); setAreaOptions(options.areas || []);
setAccessUsers(users || []); setAccessUsers(users || []);
setPresenceList(Array.isArray(presences) ? presences : []);
setAgentPresence(currentPresence);
setPauseSeconds(Number(currentPresence?.paused_seconds || 0));
} catch { } catch {
if (isMounted) { if (isMounted) {
setAreaOptions([]); setAreaOptions([]);
setAccessUsers([]); setAccessUsers([]);
setPresenceList([]);
} }
} }
} }
@ -299,7 +341,43 @@ export function useChat() {
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, []); }, [currentUserId]);
useEffect(() => {
if (!currentUserId) return undefined;
let isMounted = true;
async function refreshPresence() {
try {
const [presences, currentPresence] = await Promise.all([
listAgentPresence(),
getAgentPresence(currentUserId),
]);
if (!isMounted) return;
setPresenceList(Array.isArray(presences) ? presences : []);
setAgentPresence(currentPresence);
setPauseSeconds(Number(currentPresence?.paused_seconds || 0));
} catch {
if (isMounted) setPresenceList([]);
}
}
const intervalId = window.setInterval(refreshPresence, 30000);
return () => {
isMounted = false;
window.clearInterval(intervalId);
};
}, [currentUserId]);
useEffect(() => {
if (!isPaused) return undefined;
const intervalId = window.setInterval(() => {
setPauseSeconds((current) => current + 1);
}, 1000);
return () => window.clearInterval(intervalId);
}, [isPaused]);
function canSeeContact(contact) { function canSeeContact(contact) {
if (isAdminUser) { if (isAdminUser) {
@ -313,7 +391,15 @@ export function useChat() {
return currentUserAreas.includes(contact.assignment.area_nome); return currentUserAreas.includes(contact.assignment.area_nome);
} }
async function loadChats({ showLoading = false } = {}) { async function loadChats({ showLoading = false, ignorePause = false } = {}) {
if (isPaused && !ignorePause) {
setContacts((current) => (current.length ? [] : current));
setActiveContactId('');
setMessagesByContact({});
setIsLoadingChats(false);
return;
}
if (whatsappStatus !== 'CONNECTED') { if (whatsappStatus !== 'CONNECTED') {
setContacts((current) => (current.length ? [] : current)); setContacts((current) => (current.length ? [] : current));
setActiveContactId(''); setActiveContactId('');
@ -358,7 +444,7 @@ export function useChat() {
isMounted = false; isMounted = false;
window.clearInterval(intervalId); window.clearInterval(intervalId);
}; };
}, [currentUserId, currentUserAreas.join('|'), isAdminUser, whatsappStatus]); }, [currentUserId, currentUserAreas.join('|'), isAdminUser, isPaused, whatsappStatus]);
useEffect(() => { useEffect(() => {
if (!activeContactId) return; if (!activeContactId) return;
@ -586,6 +672,43 @@ export function useChat() {
})); }));
} }
async function pauseAttendance() {
if (!currentUserId) return;
setIsPresenceLoading(true);
try {
const result = await pauseAgent(currentUserId);
setAgentPresence(result.presence);
setPauseSeconds(0);
setContacts([]);
setActiveContactId('');
setMessagesByContact({});
setIsTransferOpen(false);
setApiError(null);
} catch (error) {
setApiError(error.message);
} finally {
setIsPresenceLoading(false);
}
}
async function resumeAttendance() {
if (!currentUserId) return;
setIsPresenceLoading(true);
try {
const result = await resumeAgent(currentUserId);
setAgentPresence(result.presence);
setPauseSeconds(0);
setApiError(null);
await loadChats({ showLoading: true, ignorePause: true });
const presences = await listAgentPresence();
setPresenceList(Array.isArray(presences) ? presences : []);
} catch (error) {
setApiError(error.message);
} finally {
setIsPresenceLoading(false);
}
}
async function sendMessage(messageText = draft, contactId = activeContactId) { async function sendMessage(messageText = draft, contactId = activeContactId) {
const rawMessage = typeof messageText === 'string' ? messageText : draft; const rawMessage = typeof messageText === 'string' ? messageText : draft;
const trimmed = rawMessage.trim(); const trimmed = rawMessage.trim();
@ -769,5 +892,12 @@ export function useChat() {
transferNote, transferNote,
setTransferNote, setTransferNote,
submitTransfer, submitTransfer,
agentPresence,
isPaused,
pauseSeconds,
pauseDurationLabel: formatPauseDuration(pauseSeconds),
isPresenceLoading,
pauseAttendance,
resumeAttendance,
}; };
} }

View File

@ -48,6 +48,8 @@ export function ChatPage() {
transferNote, transferNote,
setTransferNote, setTransferNote,
submitTransfer, submitTransfer,
isPaused,
pauseDurationLabel,
} = useChat(); } = useChat();
const requestedChatId = searchParams.get('chatId'); const requestedChatId = searchParams.get('chatId');
const handledRequestedChatIdRef = useRef(''); const handledRequestedChatIdRef = useRef('');
@ -111,7 +113,7 @@ export function ChatPage() {
textAlign: 'center', textAlign: 'center',
}} }}
> >
Atendimento em tempo real {isPaused ? `Pausado ha ${pauseDurationLabel}` : 'Atendimento em tempo real'}
</div> </div>
<Link <Link
to="/home" to="/home"
@ -173,6 +175,8 @@ export function ChatPage() {
assignmentLabel={assignmentLabel} assignmentLabel={assignmentLabel}
transferNote={transferNoteLabel} transferNote={transferNoteLabel}
isReplying={isReplying} isReplying={isReplying}
isPaused={isPaused}
pauseDurationLabel={pauseDurationLabel}
isMobile={isMobile} isMobile={isMobile}
/> />
@ -188,6 +192,7 @@ export function ChatPage() {
key={reply} key={reply}
type="button" type="button"
onClick={() => setDraft(reply)} onClick={() => setDraft(reply)}
disabled={isPaused}
style={{ style={{
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
borderRadius: '18px', borderRadius: '18px',
@ -196,6 +201,7 @@ export function ChatPage() {
color: 'var(--color-primary)', color: 'var(--color-primary)',
fontWeight: 600, fontWeight: 600,
textAlign: 'left', textAlign: 'left',
opacity: isPaused ? 0.55 : 1,
}} }}
> >
{reply} {reply}

View File

@ -0,0 +1,46 @@
import { API_BASE_URL } from '../../../shared/services/apiConfig';
async function request(path, options = {}) {
const response = await fetch(`${API_BASE_URL}${path}`, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
throw new Error('Falha ao atualizar presenca do agente');
}
return response.json();
}
export async function getAgentPresence(userId) {
return request(`/agent/presence/me?userId=${encodeURIComponent(userId)}`);
}
export async function listAgentPresence() {
return request('/agent/presence');
}
export async function pauseAgent(userId) {
return request('/agent/presence/pause', {
method: 'POST',
body: JSON.stringify({ userId }),
});
}
export async function resumeAgent(userId) {
return request('/agent/presence/resume', {
method: 'POST',
body: JSON.stringify({ userId }),
});
}
export async function markAgentOffline(userId) {
return request('/agent/presence/offline', {
method: 'POST',
body: JSON.stringify({ userId }),
});
}

View File

@ -1,29 +1,39 @@
import { useState, useEffect } from 'react'; import { useEffect, useState } from 'react';
export function AttendantOpsPanel({ activeChatsCount }) { export function AttendantOpsPanel({
const [isPaused, setIsPaused] = useState(false); activeChatsCount,
isPaused = false,
pauseDurationLabel = '00:00',
isPresenceLoading = false,
onTogglePause,
}) {
const [secondsOnline, setSecondsOnline] = useState(0); const [secondsOnline, setSecondsOnline] = useState(0);
useEffect(() => { useEffect(() => {
let interval; if (isPaused) return undefined;
if (!isPaused) {
interval = setInterval(() => { const intervalId = window.setInterval(() => {
setSecondsOnline((s) => s + 1); setSecondsOnline((current) => current + 1);
}, 1000); }, 1000);
}
return () => clearInterval(interval); return () => window.clearInterval(intervalId);
}, [isPaused]); }, [isPaused]);
const formatTime = (totalSeconds) => { const formatTime = (totalSeconds) => {
const h = Math.floor(totalSeconds / 3600); const hours = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60); const minutes = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60; const seconds = totalSeconds % 60;
return [h, m, s]
.map(v => v.toString().padStart(2, '0')) return [hours, minutes, seconds]
.filter((v, i) => v !== '00' || i > 0) .map((value) => value.toString().padStart(2, '0'))
.filter((value, index) => value !== '00' || index > 0)
.join(':'); .join(':');
}; };
const presenceLabel = isPaused ? 'Tempo em pausa' : 'Tempo online';
const presenceTime = isPaused ? pauseDurationLabel : formatTime(secondsOnline);
const statusColor = isPaused ? '#ef4444' : '#10b981';
return ( return (
<div <div
style={{ style={{
@ -46,20 +56,22 @@ export function AttendantOpsPanel({ activeChatsCount }) {
> >
<div> <div>
<span style={{ color: 'var(--color-text-soft)', fontSize: '0.9rem', fontWeight: 600 }}> <span style={{ color: 'var(--color-text-soft)', fontSize: '0.9rem', fontWeight: 600 }}>
Tempo Online {presenceLabel}
</span> </span>
<strong style={{ display: 'block', fontSize: '1.6rem', marginTop: '0.2rem', color: 'var(--color-text)' }}> <strong style={{ display: 'block', fontSize: '1.6rem', marginTop: '0.2rem', color: 'var(--color-text)' }}>
{formatTime(secondsOnline)} {presenceTime}
</strong> </strong>
</div> </div>
<div style={{ <div
width: '12px', title={isPaused ? 'Agente pausado' : 'Agente disponivel'}
height: '12px', style={{
width: 12,
height: 12,
borderRadius: '50%', borderRadius: '50%',
background: isPaused ? '#ef4444' : '#10b981', background: statusColor,
boxShadow: `0 0 10px ${isPaused ? '#ef4444' : '#10b981'}`, boxShadow: `0 0 10px ${statusColor}`,
animation: !isPaused ? 'pulse 2s infinite' : 'none' }}
}} /> />
</article> </article>
<article <article
@ -76,10 +88,10 @@ export function AttendantOpsPanel({ activeChatsCount }) {
> >
<div> <div>
<span style={{ color: 'var(--color-text-soft)', fontSize: '0.9rem', fontWeight: 600 }}> <span style={{ color: 'var(--color-text-soft)', fontSize: '0.9rem', fontWeight: 600 }}>
Atendimentos Abertos Atendimentos abertos
</span> </span>
<strong style={{ display: 'block', fontSize: '1.6rem', marginTop: '0.2rem', color: 'var(--color-text)' }}> <strong style={{ display: 'block', fontSize: '1.6rem', marginTop: '0.2rem', color: 'var(--color-text)' }}>
{activeChatsCount} {isPaused ? 0 : activeChatsCount}
</strong> </strong>
</div> </div>
</article> </article>
@ -93,11 +105,13 @@ export function AttendantOpsPanel({ activeChatsCount }) {
boxShadow: 'var(--shadow-sm)', boxShadow: 'var(--shadow-sm)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
}} }}
> >
<button <button
onClick={() => setIsPaused(!isPaused)} type="button"
onClick={onTogglePause}
disabled={isPresenceLoading}
style={{ style={{
width: '100%', width: '100%',
height: '100%', height: '100%',
@ -107,12 +121,13 @@ export function AttendantOpsPanel({ activeChatsCount }) {
background: isPaused ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)', background: isPaused ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
color: isPaused ? '#10b981' : '#ef4444', color: isPaused ? '#10b981' : '#ef4444',
fontSize: '1rem', fontSize: '1rem',
fontWeight: 700, fontWeight: 800,
cursor: 'pointer', cursor: isPresenceLoading ? 'wait' : 'pointer',
transition: 'all 0.2s ease', transition: 'all 0.2s ease',
opacity: isPresenceLoading ? 0.7 : 1,
}} }}
> >
{isPaused ? 'Retomar Atendimento' : 'Pausar'} {isPaused ? 'Retomar Atendimento' : 'Pausar'}
</button> </button>
</article> </article>
</div> </div>

View File

@ -200,6 +200,8 @@ export function MessagesWorkspace({
isDesktop = false, isDesktop = false,
isTablet = false, isTablet = false,
isMobile = false, isMobile = false,
isPaused = false,
pauseDurationLabel = '00:00',
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const messagesRef = useRef(null); const messagesRef = useRef(null);
@ -311,6 +313,7 @@ export function MessagesWorkspace({
} }
async function sendSuggestedReply() { async function sendSuggestedReply() {
if (isPaused) return;
if (!safeActiveConversation.id || safeActiveConversation.id === 'empty') return; if (!safeActiveConversation.id || safeActiveConversation.id === 'empty') return;
await onSendSuggestedReply?.(safeActiveConversation.id, selectedReply); await onSendSuggestedReply?.(safeActiveConversation.id, selectedReply);
@ -393,7 +396,10 @@ export function MessagesWorkspace({
{conversations.length > 3 ? ( {conversations.length > 3 ? (
<button <button
type="button" type="button"
onClick={() => navigate('/chat')} onClick={() => {
if (!isPaused) navigate('/chat');
}}
disabled={isPaused}
style={{ style={{
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
borderRadius: '16px', borderRadius: '16px',
@ -401,6 +407,8 @@ export function MessagesWorkspace({
background: '#fff', background: '#fff',
color: 'var(--color-primary)', color: 'var(--color-primary)',
fontWeight: 700, fontWeight: 700,
opacity: isPaused ? 0.55 : 1,
cursor: isPaused ? 'not-allowed' : 'pointer',
}} }}
> >
Ver todos no chat Ver todos no chat
@ -442,7 +450,10 @@ export function MessagesWorkspace({
<div style={{ display: 'flex', gap: '0.6rem', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: '0.6rem', flexWrap: 'wrap' }}>
<button <button
type="button" type="button"
onClick={() => navigate('/chat')} onClick={() => {
if (!isPaused) navigate('/chat');
}}
disabled={isPaused}
style={{ style={{
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
borderRadius: '14px', borderRadius: '14px',
@ -450,6 +461,8 @@ export function MessagesWorkspace({
background: '#fff', background: '#fff',
color: 'var(--color-primary)', color: 'var(--color-primary)',
fontWeight: 700, fontWeight: 700,
opacity: isPaused ? 0.55 : 1,
cursor: isPaused ? 'not-allowed' : 'pointer',
}} }}
> >
Abrir chat Abrir chat
@ -571,12 +584,15 @@ export function MessagesWorkspace({
type="button" type="button"
onClick={selectPreviousReply} onClick={selectPreviousReply}
title="Resposta anterior" title="Resposta anterior"
disabled={isPaused}
style={{ style={{
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
borderRadius: '14px', borderRadius: '14px',
background: '#fff', background: '#fff',
color: 'var(--color-primary)', color: 'var(--color-primary)',
fontWeight: 900, fontWeight: 900,
opacity: isPaused ? 0.55 : 1,
cursor: isPaused ? 'not-allowed' : 'pointer',
}} }}
> >
@ -584,6 +600,7 @@ export function MessagesWorkspace({
<button <button
type="button" type="button"
onClick={sendSuggestedReply} onClick={sendSuggestedReply}
disabled={isPaused}
style={{ style={{
border: '1px solid rgba(0, 164, 183, 0.32)', border: '1px solid rgba(0, 164, 183, 0.32)',
borderRadius: '16px', borderRadius: '16px',
@ -598,9 +615,11 @@ export function MessagesWorkspace({
display: '-webkit-box', display: '-webkit-box',
WebkitLineClamp: 2, WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical', WebkitBoxOrient: 'vertical',
opacity: isPaused ? 0.55 : 1,
cursor: isPaused ? 'not-allowed' : 'pointer',
}} }}
> >
{selectedReply} {isPaused ? `Voce esta em pausa ha ${pauseDurationLabel}. Retome para responder.` : selectedReply}
</button> </button>
<button <button
type="button" type="button"

View File

@ -37,6 +37,11 @@ export function HomePage() {
messages, messages,
sendMessage, sendMessage,
isLoadingChats, isLoadingChats,
isPaused,
pauseDurationLabel,
isPresenceLoading,
pauseAttendance,
resumeAttendance,
} = useChat(); } = useChat();
const [activeTab, setActiveTab] = useState('messages'); const [activeTab, setActiveTab] = useState('messages');
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
@ -122,7 +127,13 @@ export function HomePage() {
gap: '1rem', gap: '1rem',
}} }}
> >
<AttendantOpsPanel activeChatsCount={filteredConversations.length} /> <AttendantOpsPanel
activeChatsCount={filteredConversations.length}
isPaused={isPaused}
pauseDurationLabel={pauseDurationLabel}
isPresenceLoading={isPresenceLoading}
onTogglePause={isPaused ? resumeAttendance : pauseAttendance}
/>
{isLoadingChats ? ( {isLoadingChats ? (
<div <div
@ -152,6 +163,8 @@ export function HomePage() {
isDesktop={isDesktop} isDesktop={isDesktop}
isTablet={isTablet} isTablet={isTablet}
isMobile={isMobile} isMobile={isMobile}
isPaused={isPaused}
pauseDurationLabel={pauseDurationLabel}
/> />
) : ( ) : (
<CallsWorkspace <CallsWorkspace