2026-03-19 18:22:18 -03:00
|
|
|
function ChannelBadge({ channel }) {
|
|
|
|
|
const colors = {
|
|
|
|
|
WhatsApp: '#2bb741',
|
|
|
|
|
Email: '#e5a22a',
|
|
|
|
|
SMS: '#00a4b7',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span
|
|
|
|
|
style={{
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
borderRadius: 999,
|
|
|
|
|
padding: '0.22rem 0.6rem',
|
|
|
|
|
background: `${colors[channel] || '#003150'}16`,
|
|
|
|
|
color: colors[channel] || '#003150',
|
|
|
|
|
fontSize: '0.8rem',
|
|
|
|
|
fontWeight: 700,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{channel}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:51:07 -03:00
|
|
|
function AssignmentDot({ contact, currentUserId }) {
|
|
|
|
|
const assignment = contact.assignment;
|
|
|
|
|
const assignedUserId = assignment?.user_id ? Number(assignment.user_id) : null;
|
|
|
|
|
const isQueued = assignment?.status === 'queued' && !assignedUserId;
|
|
|
|
|
const isMine = assignedUserId && currentUserId && assignedUserId === Number(currentUserId);
|
|
|
|
|
const meta = isQueued
|
|
|
|
|
? {
|
|
|
|
|
color: '#e5a22a',
|
|
|
|
|
label: 'Chamado na fila da especialidade, ainda sem atribuição',
|
|
|
|
|
}
|
|
|
|
|
: isMine
|
|
|
|
|
? {
|
|
|
|
|
color: '#00a4b7',
|
|
|
|
|
label: 'Chamado atribuído a mim',
|
|
|
|
|
}
|
|
|
|
|
: assignedUserId
|
|
|
|
|
? {
|
|
|
|
|
color: '#d62828',
|
|
|
|
|
label: `Chamado atribuído a ${assignment?.user_nome || 'outra pessoa'}`,
|
|
|
|
|
}
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
if (!meta) return null;
|
2026-05-19 09:45:00 -03:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span
|
2026-05-22 10:51:07 -03:00
|
|
|
title={meta.label}
|
|
|
|
|
aria-label={meta.label}
|
2026-05-19 09:45:00 -03:00
|
|
|
style={{
|
|
|
|
|
width: 10,
|
|
|
|
|
height: 10,
|
|
|
|
|
borderRadius: 999,
|
2026-05-22 10:51:07 -03:00
|
|
|
background: meta.color,
|
|
|
|
|
boxShadow: `0 0 0 3px ${meta.color}22`,
|
2026-05-19 09:45:00 -03:00
|
|
|
flex: '0 0 auto',
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:51:07 -03:00
|
|
|
function SpecialtyBadge({ contact }) {
|
|
|
|
|
const specialty = contact.assignment?.area_nome || contact.area;
|
|
|
|
|
if (!specialty || specialty === 'Sem fila') return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span
|
|
|
|
|
title={`Especialidade: ${specialty}`}
|
|
|
|
|
style={{
|
|
|
|
|
color: 'var(--color-primary)',
|
|
|
|
|
flex: '0 0 auto',
|
|
|
|
|
fontSize: '0.72rem',
|
|
|
|
|
fontWeight: 800,
|
|
|
|
|
lineHeight: 1,
|
|
|
|
|
borderRadius: 999,
|
|
|
|
|
padding: '0.2rem 0.5rem',
|
|
|
|
|
background: 'rgba(0, 49, 80, 0.08)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{specialty}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 15:29:43 -03:00
|
|
|
function UnreadBadge({ count }) {
|
|
|
|
|
if (!count) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span
|
|
|
|
|
style={{
|
|
|
|
|
width: 26,
|
|
|
|
|
height: 26,
|
|
|
|
|
borderRadius: '50%',
|
|
|
|
|
background: 'var(--color-secondary)',
|
|
|
|
|
color: '#fff',
|
|
|
|
|
fontSize: '0.78rem',
|
|
|
|
|
fontWeight: 800,
|
|
|
|
|
display: 'inline-grid',
|
|
|
|
|
placeItems: 'center',
|
|
|
|
|
lineHeight: 1,
|
|
|
|
|
flex: '0 0 auto',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{count > 99 ? '99+' : count}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 14:38:16 -03:00
|
|
|
function SavedContactIcon({ contact }) {
|
2026-05-20 11:37:29 -03:00
|
|
|
const profile = contact.contactProfile;
|
|
|
|
|
const hasSavedContact = Boolean(profile?.created_at || profile?.name || profile?.company || profile?.note);
|
|
|
|
|
if (!hasSavedContact) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span
|
|
|
|
|
title="Contato salvo na agenda"
|
2026-05-22 14:38:16 -03:00
|
|
|
aria-label="Contato salvo na agenda"
|
2026-05-20 11:37:29 -03:00
|
|
|
style={{
|
2026-05-22 14:38:16 -03:00
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
|
|
|
|
borderRadius: '50%',
|
|
|
|
|
border: '1px solid rgba(183, 121, 31, 0.28)',
|
|
|
|
|
background: 'rgba(183, 121, 31, 0.1)',
|
|
|
|
|
backgroundImage:
|
|
|
|
|
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b7791f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='10' cy='7' r='4'/%3E%3Cpath d='m17 11 2 2 4-4'/%3E%3C/svg%3E\")",
|
|
|
|
|
backgroundPosition: 'center',
|
|
|
|
|
backgroundRepeat: 'no-repeat',
|
|
|
|
|
backgroundSize: 14,
|
2026-05-20 11:37:29 -03:00
|
|
|
color: '#b7791f',
|
|
|
|
|
flex: '0 0 auto',
|
2026-05-22 14:38:16 -03:00
|
|
|
display: 'inline-grid',
|
|
|
|
|
placeItems: 'center',
|
|
|
|
|
fontSize: 0,
|
2026-05-20 11:37:29 -03:00
|
|
|
}}
|
|
|
|
|
>
|
2026-05-22 10:51:07 -03:00
|
|
|
•Salvo•
|
2026-05-20 11:37:29 -03:00
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-05-19 15:29:43 -03:00
|
|
|
|
2026-05-20 13:56:04 -03:00
|
|
|
const CHAT_LIST_HEIGHT = 'min(760px, calc(100vh - 160px))';
|
|
|
|
|
|
2026-03-19 18:22:18 -03:00
|
|
|
export function ChatConversationList({
|
|
|
|
|
contacts,
|
|
|
|
|
activeContactId,
|
|
|
|
|
onSelectContact,
|
2026-05-20 11:37:29 -03:00
|
|
|
onOpenContact,
|
2026-05-22 10:51:07 -03:00
|
|
|
currentUserId,
|
2026-03-19 18:22:18 -03:00
|
|
|
isMobile = false,
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<aside
|
|
|
|
|
style={{
|
|
|
|
|
background: '#fff',
|
|
|
|
|
border: '1px solid var(--color-border)',
|
|
|
|
|
borderRadius: '28px',
|
|
|
|
|
padding: '1rem',
|
|
|
|
|
display: 'grid',
|
2026-05-19 09:45:00 -03:00
|
|
|
gridTemplateRows: 'auto minmax(0, 1fr)',
|
2026-03-19 18:22:18 -03:00
|
|
|
gap: '0.85rem',
|
2026-05-20 13:56:04 -03:00
|
|
|
height: isMobile ? 'auto' : CHAT_LIST_HEIGHT,
|
|
|
|
|
maxHeight: isMobile ? 'none' : CHAT_LIST_HEIGHT,
|
|
|
|
|
alignSelf: 'start',
|
2026-05-19 09:45:00 -03:00
|
|
|
minHeight: 0,
|
2026-03-19 18:22:18 -03:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<strong style={{ display: 'block', fontSize: '1.08rem' }}>Conversas ativas</strong>
|
|
|
|
|
<span style={{ color: 'var(--color-text-soft)' }}>
|
2026-05-20 11:37:29 -03:00
|
|
|
WhatsApp, SMS e e-mail em uma fila visual.
|
2026-03-19 18:22:18 -03:00
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: 'grid',
|
|
|
|
|
gap: '0.75rem',
|
|
|
|
|
gridTemplateColumns: isMobile ? '1fr' : '1fr',
|
2026-05-19 15:29:43 -03:00
|
|
|
gridAutoRows: 'max-content',
|
|
|
|
|
alignContent: 'start',
|
2026-05-19 09:45:00 -03:00
|
|
|
overflowY: 'auto',
|
|
|
|
|
minHeight: 0,
|
|
|
|
|
paddingRight: '0.15rem',
|
2026-03-19 18:22:18 -03:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{contacts.map((contact) => {
|
|
|
|
|
const isActive = contact.id === activeContactId;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={contact.id}
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onSelectContact(contact.id)}
|
2026-05-20 11:37:29 -03:00
|
|
|
onContextMenu={(event) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
onSelectContact(contact.id);
|
|
|
|
|
onOpenContact?.(contact);
|
|
|
|
|
}}
|
2026-03-19 18:22:18 -03:00
|
|
|
style={{
|
|
|
|
|
border: '1px solid',
|
|
|
|
|
borderColor: isActive ? 'rgba(0, 164, 183, 0.26)' : 'var(--color-border)',
|
|
|
|
|
background: isActive ? 'rgba(0, 164, 183, 0.08)' : '#fff',
|
|
|
|
|
borderRadius: '20px',
|
|
|
|
|
padding: '1rem',
|
|
|
|
|
textAlign: 'left',
|
|
|
|
|
display: 'grid',
|
|
|
|
|
gap: '0.6rem',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem' }}>
|
2026-05-19 09:45:00 -03:00
|
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', minWidth: 0 }}>
|
2026-05-22 10:51:07 -03:00
|
|
|
<AssignmentDot contact={contact} currentUserId={currentUserId} />
|
2026-05-19 09:45:00 -03:00
|
|
|
<strong style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
|
|
|
{contact.name}
|
|
|
|
|
</strong>
|
|
|
|
|
</span>
|
2026-03-19 18:22:18 -03:00
|
|
|
<span style={{ fontSize: '0.82rem', color: 'var(--color-text-soft)' }}>
|
|
|
|
|
{contact.time}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.75rem' }}>
|
2026-05-20 11:37:29 -03:00
|
|
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.45rem', minWidth: 0 }}>
|
2026-05-22 14:38:16 -03:00
|
|
|
<SavedContactIcon contact={contact} />
|
2026-05-20 11:37:29 -03:00
|
|
|
<ChannelBadge channel={contact.channel} />
|
2026-05-22 10:51:07 -03:00
|
|
|
<SpecialtyBadge contact={contact} />
|
2026-05-20 11:37:29 -03:00
|
|
|
</span>
|
2026-05-19 15:29:43 -03:00
|
|
|
<UnreadBadge count={contact.unread} />
|
2026-03-19 18:22:18 -03:00
|
|
|
</div>
|
|
|
|
|
<span style={{ color: 'var(--color-text-soft)' }}>{contact.preview}</span>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
2026-05-19 15:29:43 -03:00
|
|
|
|
|
|
|
|
{contacts.length === 0 ? (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
border: '1px solid var(--color-border)',
|
|
|
|
|
borderRadius: '18px',
|
|
|
|
|
padding: '1rem',
|
|
|
|
|
background: 'rgba(0, 49, 80, 0.04)',
|
|
|
|
|
color: 'var(--color-text-soft)',
|
|
|
|
|
fontWeight: 700,
|
|
|
|
|
lineHeight: 1.45,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Nenhuma conversa ativa na sua fila. Conversas em triagem do Omnino aparecem aqui depois de classificadas.
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
2026-03-19 18:22:18 -03:00
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
);
|
|
|
|
|
}
|