import { useEffect, useMemo, useState } from 'react'; import { DataPanel } from '../components/DataPanel'; import { ManagementLayout } from '../components/ManagementLayout'; import { ManagementTable } from '../components/ManagementTable'; import { MetricGrid } from '../components/MetricGrid'; import { OperationalDashboard } from '../components/OperationalDashboard'; import { TemplateManagementPanel } from '../components/TemplateManagementPanel'; import { aiContentRows, areaRows, userRows } from '../services/managementMocks'; import { AttendantOpsPanel } from '../../home/components/AttendantOpsPanel'; import { MessagesWorkspace } from '../../home/components/MessagesWorkspace'; import { useChat } from '../../chat/hooks/useChat'; import { createAccessArea, getAccessAreas, getAccessOptions, getAccessUsers, getAdminOverview, updateUserAccess, } from '../services/adminAccessService'; import { useViewport } from '../../../shared/hooks/useViewport'; import { getCurrentUserDisplay } from '../../auth/services/sessionService'; const contentColumns = [ { key: 'title', label: 'Conteúdo' }, { key: 'area', label: 'Especialidade' }, { key: 'status', label: 'Status' }, { key: 'updatedAt', label: 'Atualizado' }, ]; const selectStyle = { width: '100%', border: '1px solid var(--color-border)', borderRadius: '14px', padding: '0.75rem 0.85rem', background: '#fff', color: 'var(--color-text)', fontWeight: 600, }; const compactSelectStyle = { ...selectStyle, borderRadius: '10px', padding: '0.45rem 0.55rem', fontSize: '0.82rem', }; const monthlyKpis = [ { label: 'Total de Atendimentos', value: '1.284', detail: '+12% vs mês anterior' }, { label: 'Tempo Médio de Atendimento', value: '8m 42s', detail: 'média mensal' }, { label: 'Taxa de Satisfação', value: '91%', detail: 'avaliações positivas' }, { label: 'Volume por Canal', value: 'W 982 · E 184 · S 118', detail: 'WhatsApp · Email · SMS' }, { label: 'Atendentes Ativos', value: '14 de 17', detail: 'ativos no mês' }, ]; const dailyAttendance = [28, 34, 42, 39, 51, 47, 58, 62, 55, 69, 73, 66, 71, 88, 79, 84, 91, 86, 94, 101, 97, 108, 112, 104, 118, 123, 116, 129, 134, 141]; const channelDistribution = [ { label: 'WhatsApp', value: 982, color: '#2bb741' }, { label: 'Email', value: 184, color: '#e5a22a' }, { label: 'SMS', value: 118, color: '#00a4b7' }, ]; const attendantRanking = [ { id: 1, name: 'Ana Camolesi', area: 'Suporte', closed: 186, avgTime: '7m 12s', satisfaction: '94%' }, { id: 2, name: 'Rafael Lopes', area: 'Suporte', closed: 172, avgTime: '8m 01s', satisfaction: '92%' }, { id: 3, name: 'Marina Alves', area: 'Financeiro', closed: 161, avgTime: '8m 44s', satisfaction: '91%' }, { id: 4, name: 'Lucas Nunes', area: 'Comercial', closed: 148, avgTime: '9m 02s', satisfaction: '89%' }, { id: 5, name: 'Camila Rocha', area: 'Comercial', closed: 139, avgTime: '7m 58s', satisfaction: '93%' }, { id: 6, name: 'Joao Pedro', area: 'Financeiro', closed: 127, avgTime: '10m 11s', satisfaction: '88%' }, { id: 7, name: 'Beatriz Lima', area: 'Suporte', closed: 121, avgTime: '8m 39s', satisfaction: '90%' }, { id: 8, name: 'Roberto Pera', area: 'Financeiro', closed: 116, avgTime: '9m 21s', satisfaction: '87%' }, { id: 9, name: 'Helena Costa', area: 'Comercial', closed: 109, avgTime: '8m 55s', satisfaction: '92%' }, { id: 10, name: 'Pedro Santos', area: 'Suporte', closed: 103, avgTime: '9m 48s', satisfaction: '86%' }, ]; const initialNotices = [ { id: 'n1', text: 'Revisar atendimentos financeiros com SLA abaixo de 15 minutos.' }, { id: 'n2', text: 'Templates de abertura ativa atualizados para WhatsApp.' }, ]; function mapMockUsers() { return userRows.map((user) => ({ id: user.id, nome: user.name, email: user.email, perfilPrincipal: { id: user.role, nome: user.role }, areaPrincipal: { id: user.area, nome: user.area }, accessStatus: 'assigned', })); } function formatMinutes(minutes) { if (minutes === null || minutes === undefined || Number.isNaN(Number(minutes))) return 'Sem dados'; return `${Number(minutes)} min`; } function toHomeConversation(contact, messages = []) { return { id: contact.id, name: contact.name, channel: contact.channel || 'WhatsApp', status: contact.status || 'online', lastMessage: contact.preview || messages[messages.length - 1]?.text || '', unread: contact.unread || 0, time: contact.time || 'Agora', lastSeen: contact.lastSeen, messages: messages.map((message) => ({ id: message.id, from: message.sender === 'agent' ? 'agent' : 'customer', text: message.text || (message.hasMedia ? '[Mídia]' : ''), timestamp: message.timestamp, })), }; } export function AdminAttendanceWorkspace({ isWideDesktop, isDesktop, isTablet, isMobile }) { const { contacts, activeContactId, setActiveContactId, messages, sendMessage, isLoadingChats, } = useChat(); const conversations = contacts.map((contact) => toHomeConversation(contact, contact.id === activeContactId ? messages : []), ); const safeConversationId = conversations.find((conversation) => conversation.id === activeContactId)?.id || conversations[0]?.id; return (
{isLoadingChats ? (
Atualizando conversas do WhatsApp...
) : null} { setActiveContactId(conversationId); await sendMessage(reply, conversationId); }} isWideDesktop={isWideDesktop} isDesktop={isDesktop} isTablet={isTablet} isMobile={isMobile} />
); } export function AdminPage() { const { isWideDesktop, isDesktop, isTablet, isMobile } = useViewport(); const userDisplay = getCurrentUserDisplay(); const [activeAdminSection, setActiveAdminSection] = useState('home'); const [selectedAreaFilter, setSelectedAreaFilter] = useState('all'); const [overview, setOverview] = useState(null); const [notices, setNotices] = useState(initialNotices); const [noticeDraft, setNoticeDraft] = useState(''); const [users, setUsers] = useState(mapMockUsers); const [profiles, setProfiles] = useState([]); const [areas, setAreas] = useState([]); const [areaRowsState, setAreaRowsState] = useState(areaRows); const [userSearch, setUserSearch] = useState(''); const [newAreaName, setNewAreaName] = useState(''); const [isLoadingAccess, setIsLoadingAccess] = useState(true); const [accessError, setAccessError] = useState(''); const [editingUser, setEditingUser] = useState(null); const [editUserProfileId, setEditUserProfileId] = useState(''); const [editUserSpecialties, setEditUserSpecialties] = useState([]); const [specialtyToAdd, setSpecialtyToAdd] = useState(''); useEffect(() => { let isMounted = true; async function loadAccessData() { try { const [options, accessUsers, accessAreas, adminOverview] = await Promise.all([ getAccessOptions(), getAccessUsers(), getAccessAreas(), getAdminOverview(), ]); if (!isMounted) { return; } setProfiles(options.profiles || []); setAreas(options.areas || []); setUsers(accessUsers || []); setAreaRowsState(accessAreas || []); setOverview(adminOverview || null); setAccessError(''); } catch { if (isMounted) { setAccessError('Backend indisponivel. Exibindo dados demonstrativos.'); } } finally { if (isMounted) { setIsLoadingAccess(false); } } } loadAccessData(); return () => { isMounted = false; }; }, []); function openUserEditor(user) { setEditingUser(user); setEditUserProfileId(user.perfilPrincipal?.id ? String(user.perfilPrincipal.id) : ''); setEditUserSpecialties(Array.isArray(user.areas) ? user.areas : []); setSpecialtyToAdd(''); } function closeUserEditor() { setEditingUser(null); setEditUserProfileId(''); setEditUserSpecialties([]); setSpecialtyToAdd(''); } function getProfileName(profileId) { return profiles.find((profile) => profile.id === Number(profileId))?.nome || ''; } async function saveUserAccess(user, nextProfileId, nextSpecialties) { try { const updatedUser = await updateUserAccess(user.id, { perfilIds: nextProfileId ? [Number(nextProfileId)] : [], especialidades: nextSpecialties.map((specialty, index) => ({ areaId: Number(specialty.id), funcao: specialty.funcao || 'Agente', principal: index === 0, ativo: true, })), }); if (updatedUser) { setUsers((current) => current.map((item) => (item.id === updatedUser.id ? updatedUser : item)), ); } setAccessError(''); await refreshAreas(); } catch { setAccessError('Não foi possível salvar a atribuição. Confira o backend.'); } } function handleEditProfileChange(value) { setEditUserProfileId(value); if (getProfileName(value) === 'Admin') { setEditUserSpecialties([]); setSpecialtyToAdd(''); } } function addSpecialtyToEdit() { const area = areas.find((item) => item.id === Number(specialtyToAdd)); if (!area) return; setEditUserSpecialties((current) => { if (current.some((specialty) => specialty.id === area.id)) return current; return [ ...current, { id: area.id, nome: area.nome, funcao: 'Agente', principal: current.length === 0 }, ]; }); setSpecialtyToAdd(''); } function removeSpecialtyFromEdit(areaId) { setEditUserSpecialties((current) => current.filter((specialty) => specialty.id !== areaId)); } function updateEditSpecialtyRole(areaId, role) { setEditUserSpecialties((current) => current.map((specialty) => specialty.id === areaId ? { ...specialty, funcao: role } : specialty, ), ); } async function submitUserEditor() { if (!editingUser) return; const isAdmin = getProfileName(editUserProfileId) === 'Admin'; await saveUserAccess(editingUser, editUserProfileId, isAdmin ? [] : editUserSpecialties); closeUserEditor(); } async function refreshAreas() { const [accessAreas, options] = await Promise.all([getAccessAreas(), getAccessOptions()]); setAreaRowsState(accessAreas || []); setAreas(options.areas || []); } async function handleCreateArea() { const nome = newAreaName.trim(); if (!nome) return; try { await createAccessArea({ nome, }); setNewAreaName(''); await refreshAreas(); setAccessError(''); } catch { setAccessError('Não foi possível criar a especialidade.'); } } const realMonthlyKpis = [ { label: 'Total de Atendimentos', value: overview ? String(overview.totalAttendances) : '...', detail: overview?.previousMonthVariation === null || overview?.previousMonthVariation === undefined ? 'sem base do mês anterior' : `${overview.previousMonthVariation >= 0 ? '+' : ''}${overview.previousMonthVariation}% vs mês anterior`, }, { label: 'TMA', value: formatMinutes(overview?.avgHandlingMinutes), detail: overview?.avgHandlingMinutes === null ? 'aguardando histórico' : 'média mensal', }, { label: 'TME', value: formatMinutes(overview?.avgFirstResponseMinutes), detail: 'tempo médio de espera', }, { label: 'TMR', value: 'Sem dados', detail: 'requer eventos de resposta', }, { label: 'Atendentes Ativos', value: overview ? `${overview.activeAttendants} de ${overview.totalActiveUsers}` : '...', detail: 'ativos no mês', }, ]; const filteredUsers = users.filter((user) => { const search = userSearch.trim().toLowerCase(); if (!search) return true; return `${user.nome} ${user.email || ''} ${user.perfilPrincipal?.nome || ''} ${user.areaPrincipal?.nome || ''}` .toLowerCase() .includes(search); }); const availableSpecialtiesToAdd = areas.filter( (area) => !editUserSpecialties.some((specialty) => specialty.id === area.id), ); const isEditingAdmin = getProfileName(editUserProfileId) === 'Admin'; const channelDistributionData = overview ? [ { label: 'WhatsApp', value: overview.channels?.whatsapp || 0, color: '#2bb741' }, { label: 'Email', value: overview.channels?.email || 0, color: '#e5a22a' }, { label: 'SMS', value: overview.channels?.sms || 0, color: '#00a4b7' }, ] : channelDistribution; const userColumns = useMemo( () => [ { key: 'nome', label: 'Usuario', render: (row) => {row.nome}, }, { key: 'perfil', label: 'Perfil', render: (row) => {row.perfilPrincipal?.nome || 'Sem perfil'}, }, { key: 'status', label: 'Status', render: (row) => { const isAssigned = row.accessStatus === 'assigned'; return ( {isAssigned ? 'Atribuido' : 'Pendente'} ); }, }, { key: 'actions', label: 'Ações', render: (row) => ( ), }, ], [], ); const areaColumns = useMemo( () => [ { key: 'nome', label: 'Especialidade' }, { key: 'supervisores', label: 'Supervisores', render: (row) => { const supervisors = Array.isArray(row.supervisores) ? row.supervisores : []; return supervisors.length ? (
{supervisors.map((supervisor) => ( {supervisor.nome} ))}
) : ( Sem supervisor ); }, }, { key: 'members', label: 'Usuarios' }, { key: 'status', label: 'Status', render: (row) => (row.ativo ? 'Ativa' : 'Inativa'), }, ], [], ); const filteredRanking = selectedAreaFilter === 'all' ? attendantRanking : attendantRanking.filter((row) => row.area === selectedAreaFilter); const rankingColumns = [ { key: 'name', label: 'Nome' }, { key: 'area', label: 'Especialidade' }, { key: 'closed', label: 'Atendimentos finalizados' }, { key: 'avgTime', label: 'Tempo médio' }, { key: 'satisfaction', label: 'Satisfação' }, ]; function sendNotice() { const text = noticeDraft.trim(); if (!text) return; setNotices((current) => [{ id: `notice-${Date.now()}`, text }, ...current]); setNoticeDraft(''); } function renderLineChart() { const maxValue = Math.max(...dailyAttendance); const points = dailyAttendance .map((value, index) => { const x = (index / (dailyAttendance.length - 1)) * 100; const y = 100 - (value / maxValue) * 86 - 7; return `${x},${y}`; }) .join(' '); return ( ); } function renderDonutChart() { const total = channelDistributionData.reduce((sum, item) => sum + item.value, 0) || 1; let offset = 0; return (
{channelDistributionData.map((item) => { const dash = (item.value / total) * 100; const circle = ( ); offset += dash; return circle; })}
{channelDistributionData.map((item) => ( {item.label} {item.value} ))}
); } function renderMonthlyHome() { return ( <>
{renderLineChart()} {renderDonutChart()}
row.id} isMobile={isMobile} />
{notices.map((notice) => (
{notice.text}
))}