/** * Supera Glia - Tab Pedagógico (Dashboard) * @module tabs/PedagogicoTab * @version 2.0 - Com seção de Alunos Faltosos */ (function() { 'use strict'; SuperaGlia.initPedagogicoTab = function() { const { Icon } = SuperaGlia; const { ResponsiveContainer, AreaChart, Area, LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Cell } = SuperaGlia.charts; // ============================================================ // COMPONENTE: Tabela de Alunos Faltosos // ============================================================ const TabelaFaltosos = ({ faltosos, filtro, setFiltro }) => { const [ordenacao, setOrdenacao] = React.useState({ campo: 'perc_faltas', direcao: 'desc' }); const [busca, setBusca] = React.useState(''); // Filtrar e ordenar dados const dadosFiltrados = React.useMemo(() => { let dados = [...(faltosos || [])]; // Filtrar por busca if (busca) { const termoBusca = busca.toLowerCase(); dados = dados.filter(a => a.nome?.toLowerCase().includes(termoBusca) || a.turma?.toLowerCase().includes(termoBusca) || a.professor?.toLowerCase().includes(termoBusca) ); } // Filtrar por percentual mínimo de faltas if (filtro.percMinimo > 0) { dados = dados.filter(a => (a.perc_faltas || 0) >= filtro.percMinimo); } // Filtrar por professor if (filtro.professor) { dados = dados.filter(a => a.professor === filtro.professor); } // Filtrar por turma if (filtro.turma) { dados = dados.filter(a => a.turma === filtro.turma); } // Ordenar dados.sort((a, b) => { let valorA = a[ordenacao.campo]; let valorB = b[ordenacao.campo]; if (typeof valorA === 'string') valorA = valorA?.toLowerCase() || ''; if (typeof valorB === 'string') valorB = valorB?.toLowerCase() || ''; if (ordenacao.direcao === 'asc') { return valorA > valorB ? 1 : -1; } return valorA < valorB ? 1 : -1; }); return dados; }, [faltosos, busca, filtro, ordenacao]); // Extrair listas únicas para filtros const professores = [...new Set(faltosos?.map(a => a.professor).filter(Boolean))].sort(); const turmas = [...new Set(faltosos?.map(a => a.turma).filter(Boolean))].sort(); const alternarOrdenacao = (campo) => { setOrdenacao(prev => ({ campo, direcao: prev.campo === campo && prev.direcao === 'desc' ? 'asc' : 'desc' })); }; const IconeOrdenacao = ({ campo }) => { if (ordenacao.campo !== campo) return null; return React.createElement('span', { className: 'ml-1 text-orange-500' }, ordenacao.direcao === 'asc' ? '↑' : '↓' ); }; // Função para cor do badge de faltas const corFaltas = (perc) => { if (perc >= 75) return 'bg-red-100 text-red-700 border-red-200'; if (perc >= 50) return 'bg-orange-100 text-orange-700 border-orange-200'; if (perc >= 25) return 'bg-yellow-100 text-yellow-700 border-yellow-200'; return 'bg-gray-100 text-gray-600 border-gray-200'; }; return React.createElement('div', { className: 'space-y-4' }, // Filtros React.createElement('div', { className: 'flex flex-wrap gap-3' }, // Busca React.createElement('div', { className: 'flex-1 min-w-[200px]' }, React.createElement('input', { type: 'text', placeholder: 'Buscar por nome, turma ou professor...', value: busca, onChange: (e) => setBusca(e.target.value), className: 'w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent' }) ), // Filtro por % mínimo React.createElement('select', { value: filtro.percMinimo, onChange: (e) => setFiltro(prev => ({ ...prev, percMinimo: Number(e.target.value) })), className: 'px-3 py-2 text-sm border border-gray-200 rounded-lg focus:ring-2 focus:ring-orange-500' }, React.createElement('option', { value: 0 }, 'Todas as faltas'), React.createElement('option', { value: 25 }, '≥ 25% faltas'), React.createElement('option', { value: 50 }, '≥ 50% faltas'), React.createElement('option', { value: 75 }, '≥ 75% faltas') ), // Filtro por professor React.createElement('select', { value: filtro.professor, onChange: (e) => setFiltro(prev => ({ ...prev, professor: e.target.value })), className: 'px-3 py-2 text-sm border border-gray-200 rounded-lg focus:ring-2 focus:ring-orange-500' }, React.createElement('option', { value: '' }, 'Todos os professores'), professores.map(p => React.createElement('option', { key: p, value: p }, p)) ), // Filtro por turma React.createElement('select', { value: filtro.turma, onChange: (e) => setFiltro(prev => ({ ...prev, turma: e.target.value })), className: 'px-3 py-2 text-sm border border-gray-200 rounded-lg focus:ring-2 focus:ring-orange-500 max-w-[200px]' }, React.createElement('option', { value: '' }, 'Todas as turmas'), turmas.map(t => React.createElement('option', { key: t, value: t }, t)) ) ), // Resumo React.createElement('div', { className: 'text-sm text-gray-500' }, `Exibindo ${dadosFiltrados.length} de ${faltosos?.length || 0} alunos` ), // Tabela React.createElement('div', { className: 'overflow-x-auto rounded-xl border border-gray-100' }, React.createElement('table', { className: 'w-full text-sm' }, React.createElement('thead', null, React.createElement('tr', { className: 'bg-gray-50 text-gray-600 text-xs uppercase tracking-wider' }, React.createElement('th', { className: 'text-left py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('nome') }, 'Aluno', React.createElement(IconeOrdenacao, { campo: 'nome' })), React.createElement('th', { className: 'text-left py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('turma') }, 'Turma', React.createElement(IconeOrdenacao, { campo: 'turma' })), React.createElement('th', { className: 'text-left py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('professor') }, 'Professor', React.createElement(IconeOrdenacao, { campo: 'professor' })), React.createElement('th', { className: 'text-center py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('qt_aulas') }, 'Aulas', React.createElement(IconeOrdenacao, { campo: 'qt_aulas' })), React.createElement('th', { className: 'text-center py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('qt_faltas') }, 'Faltas', React.createElement(IconeOrdenacao, { campo: 'qt_faltas' })), React.createElement('th', { className: 'text-center py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('perc_faltas') }, '% Faltas', React.createElement(IconeOrdenacao, { campo: 'perc_faltas' })), React.createElement('th', { className: 'text-center py-3 px-4 cursor-pointer hover:bg-gray-100 transition-colors', onClick: () => alternarOrdenacao('qt_reposicao') }, 'Reposições', React.createElement(IconeOrdenacao, { campo: 'qt_reposicao' })), React.createElement('th', { className: 'text-left py-3 px-4' }, 'Entrada') ) ), React.createElement('tbody', null, dadosFiltrados.length === 0 ? React.createElement('tr', null, React.createElement('td', { colSpan: 8, className: 'py-8 text-center text-gray-400' }, 'Nenhum aluno encontrado com os filtros aplicados') ) : dadosFiltrados.map((aluno, i) => React.createElement('tr', { key: aluno.id_matricula || i, className: 'border-t border-gray-50 hover:bg-orange-50/30 transition-colors' }, React.createElement('td', { className: 'py-3 px-4 font-medium text-gray-800' }, aluno.nome || '-' ), React.createElement('td', { className: 'py-3 px-4 text-gray-600 max-w-[200px] truncate' }, aluno.turma || '-' ), React.createElement('td', { className: 'py-3 px-4 text-gray-600' }, aluno.professor || '-' ), React.createElement('td', { className: 'py-3 px-4 text-center text-gray-600' }, aluno.qt_aulas || 0 ), React.createElement('td', { className: 'py-3 px-4 text-center font-semibold text-red-600' }, aluno.qt_faltas || 0 ), React.createElement('td', { className: 'py-3 px-4 text-center' }, React.createElement('span', { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${corFaltas(aluno.perc_faltas || 0)}` }, `${aluno.perc_faltas || 0}%`) ), React.createElement('td', { className: 'py-3 px-4 text-center text-gray-600' }, aluno.qt_reposicao || 0 ), React.createElement('td', { className: 'py-3 px-4 text-gray-500 text-xs' }, aluno.data_entrada || '-' ) )) ) ) ) ); }; // ============================================================ // COMPONENTE: Cards de Resumo de Frequência // ============================================================ const CardsFrequencia = ({ faltosos }) => { const stats = React.useMemo(() => { if (!faltosos || faltosos.length === 0) { return { total: 0, criticos: 0, alerta: 0, atencao: 0, semFaltas: 0 }; } return { total: faltosos.length, criticos: faltosos.filter(a => (a.perc_faltas || 0) >= 75).length, alerta: faltosos.filter(a => (a.perc_faltas || 0) >= 50 && (a.perc_faltas || 0) < 75).length, atencao: faltosos.filter(a => (a.perc_faltas || 0) >= 25 && (a.perc_faltas || 0) < 50).length, semFaltas: faltosos.filter(a => (a.perc_faltas || 0) === 0).length }; }, [faltosos]); const cards = [ { label: 'Total Monitorados', value: stats.total, color: 'bg-gradient-to-br from-blue-500 to-blue-600', icon: 'users' }, { label: 'Crítico (≥75%)', value: stats.criticos, color: 'bg-gradient-to-br from-red-500 to-red-600', icon: 'alert-triangle' }, { label: 'Alerta (50-74%)', value: stats.alerta, color: 'bg-gradient-to-br from-orange-500 to-orange-600', icon: 'alert-circle' }, { label: 'Atenção (25-49%)', value: stats.atencao, color: 'bg-gradient-to-br from-yellow-500 to-yellow-600', icon: 'eye' }, ]; return React.createElement('div', { className: 'grid grid-cols-2 md:grid-cols-4 gap-4' }, cards.map((card, i) => React.createElement('div', { key: i, className: 'bg-white rounded-xl p-4 shadow-sm border border-gray-100' }, React.createElement('div', { className: 'flex items-center justify-between' }, React.createElement('div', null, React.createElement('p', { className: 'text-xs text-gray-500 font-medium' }, card.label), React.createElement('p', { className: 'text-2xl font-bold text-gray-900 mt-1' }, card.value) ), React.createElement('div', { className: `w-10 h-10 ${card.color} rounded-lg flex items-center justify-center` }, React.createElement(Icon, { name: card.icon, className: 'w-5 h-5 text-white' }) ) ) )) ); }; // ============================================================ // COMPONENTE: Gráfico de Distribuição por Professor // ============================================================ const GraficoFaltasPorProfessor = ({ faltosos }) => { const dados = React.useMemo(() => { if (!faltosos || faltosos.length === 0) return []; const porProfessor = {}; faltosos.forEach(a => { const prof = a.professor || 'Não informado'; if (!porProfessor[prof]) { porProfessor[prof] = { nome: prof, total: 0, comFaltas: 0, criticos: 0 }; } porProfessor[prof].total++; if ((a.perc_faltas || 0) > 0) porProfessor[prof].comFaltas++; if ((a.perc_faltas || 0) >= 50) porProfessor[prof].criticos++; }); return Object.values(porProfessor).sort((a, b) => b.criticos - a.criticos); }, [faltosos]); const COLORS = ['#F37021', '#ef4444', '#f97316', '#eab308', '#22c55e']; return React.createElement('div', { className: 'bg-white rounded-xl p-5 shadow-sm border border-gray-100' }, React.createElement('h4', { className: 'font-semibold text-gray-800 mb-4' }, 'Faltas por Professor/Aplicador'), React.createElement(ResponsiveContainer, { width: '100%', height: 250 }, React.createElement(BarChart, { data: dados, layout: 'vertical' }, React.createElement(CartesianGrid, { strokeDasharray: '3 3', stroke: '#f1f5f9' }), React.createElement(XAxis, { type: 'number', tick: { fontSize: 11 } }), React.createElement(YAxis, { type: 'category', dataKey: 'nome', width: 100, tick: { fontSize: 10 } }), React.createElement(Tooltip, null), React.createElement(Legend, null), React.createElement(Bar, { dataKey: 'criticos', fill: '#ef4444', name: 'Críticos (≥50%)' }), React.createElement(Bar, { dataKey: 'comFaltas', fill: '#f97316', name: 'Com faltas' }) ) ) ); }; // ============================================================ // COMPONENTE PRINCIPAL: PedagogicoTab // ============================================================ SuperaGlia.PedagogicoTab = ({ dados }) => { const { alunos, turmas, evolucaoMensal, motivosDesistencia, frequencia } = dados; const totalAtivos = alunos.length; // Estado para sub-abas const [subAba, setSubAba] = React.useState('visaoGeral'); // Estado para filtros de faltosos const [filtroFaltosos, setFiltroFaltosos] = React.useState({ percMinimo: 25, professor: '', turma: '' }); // Dados de frequência vindos da API ou mock const dadosFrequencia = frequencia || []; const cards = [ { label: 'Total Alunos Ativos', value: totalAtivos, color: 'bg-gradient-to-br from-orange-500 to-orange-600', change: '+5.4% este mês', icon: 'users' }, { label: 'Turmas Ativas', value: turmas.length || 0, color: 'bg-gradient-to-br from-blue-500 to-blue-600', sub: '3 salas', icon: 'calendar' }, { label: 'Matrículas (Mês)', value: 12, color: 'bg-gradient-to-br from-emerald-500 to-emerald-600', change: '+2 vs anterior', icon: 'user-plus' }, { label: 'Evasão (Mês)', value: 4, color: 'bg-gradient-to-br from-rose-500 to-rose-600', change: '-3 vs anterior', icon: 'user-minus' } ]; // Sub-abas disponíveis const subAbas = [ { id: 'visaoGeral', label: 'Visão Geral', icon: 'layout-dashboard' }, { id: 'faltosos', label: 'Alunos Faltosos', icon: 'user-x', badge: dadosFrequencia.filter(a => (a.perc_faltas || 0) >= 25).length } ]; return React.createElement('div', { className: 'space-y-6' }, // Sub-navegação React.createElement('div', { className: 'flex gap-2 border-b border-gray-200 pb-2' }, subAbas.map(aba => React.createElement('button', { key: aba.id, onClick: () => setSubAba(aba.id), className: `flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${ subAba === aba.id ? 'bg-orange-100 text-orange-700' : 'text-gray-600 hover:bg-gray-100' }` }, React.createElement(Icon, { name: aba.icon, className: 'w-4 h-4' }), aba.label, aba.badge > 0 && React.createElement('span', { className: 'ml-1 px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600 font-semibold' }, aba.badge) )) ), // Conteúdo da sub-aba selecionada subAba === 'visaoGeral' && React.createElement(React.Fragment, null, // Cards de estatísticas React.createElement('div', { className: 'grid grid-cols-2 md:grid-cols-4 gap-4' }, cards.map((card, i) => React.createElement('div', { key: i, className: 'bg-white rounded-2xl p-5 shadow-sm border border-gray-100 card-animate', style: { animationDelay: `${i * 0.1}s` } }, React.createElement('div', { className: 'flex items-start justify-between' }, React.createElement('div', null, React.createElement('p', { className: 'text-sm text-gray-500 font-medium' }, card.label), React.createElement('p', { className: 'text-3xl font-bold text-gray-900 mt-2' }, card.value), card.change && React.createElement('p', { className: `text-xs mt-2 font-medium ${card.change.startsWith('+') ? 'text-emerald-600' : 'text-rose-600'}` }, card.change), card.sub && React.createElement('p', { className: 'text-xs text-gray-400 mt-2' }, card.sub) ), React.createElement('div', { className: `w-12 h-12 ${card.color} rounded-xl flex items-center justify-center shadow-lg` }, React.createElement(Icon, { name: card.icon, className: 'w-6 h-6 text-white' }) ) ) )) ), // Gráficos React.createElement('div', { className: 'grid grid-cols-1 lg:grid-cols-2 gap-6' }, // Gráfico de Evolução React.createElement('div', { className: 'bg-white rounded-2xl p-5 shadow-sm border border-gray-100' }, React.createElement('h3', { className: 'font-bold text-gray-900 mb-4' }, 'Evolução de Alunos Ativos'), React.createElement(ResponsiveContainer, { width: '100%', height: 250 }, React.createElement(AreaChart, { data: evolucaoMensal }, React.createElement(CartesianGrid, { strokeDasharray: '3 3', stroke: '#f1f5f9' }), React.createElement(XAxis, { dataKey: 'mes', tick: { fontSize: 11 } }), React.createElement(YAxis, { tick: { fontSize: 11 } }), React.createElement(Tooltip, null), React.createElement(Area, { type: 'monotone', dataKey: 'ativos', stroke: '#F37021', fill: '#FEF3EC' }) ) ) ), // Gráfico Matrículas vs Evasão React.createElement('div', { className: 'bg-white rounded-2xl p-5 shadow-sm border border-gray-100' }, React.createElement('h3', { className: 'font-bold text-gray-900 mb-4' }, 'Matrículas vs Evasão'), React.createElement(ResponsiveContainer, { width: '100%', height: 250 }, React.createElement(LineChart, { data: evolucaoMensal }, React.createElement(CartesianGrid, { strokeDasharray: '3 3', stroke: '#f1f5f9' }), React.createElement(XAxis, { dataKey: 'mes', tick: { fontSize: 11 } }), React.createElement(YAxis, { tick: { fontSize: 11 } }), React.createElement(Tooltip, null), React.createElement(Legend, null), React.createElement(Line, { type: 'monotone', dataKey: 'matriculas', stroke: '#10b981', strokeWidth: 2, name: 'Matrículas' }), React.createElement(Line, { type: 'monotone', dataKey: 'evasao', stroke: '#F37021', strokeWidth: 2, name: 'Evasão' }) ) ) ) ), // Tabela de Motivos de Desistência React.createElement('div', { className: 'bg-white rounded-2xl p-5 shadow-sm border border-gray-100' }, React.createElement('h3', { className: 'font-bold text-gray-900 mb-4' }, 'Motivos de Desistência'), React.createElement('table', { className: 'w-full' }, React.createElement('thead', null, React.createElement('tr', { className: 'text-xs text-gray-500 uppercase' }, React.createElement('th', { className: 'text-left py-2' }, 'Motivo'), React.createElement('th', { className: 'text-right py-2' }, 'Quantidade') ) ), React.createElement('tbody', null, motivosDesistencia.map((m, i) => React.createElement('tr', { key: i, className: 'border-t border-gray-50' }, React.createElement('td', { className: 'py-3 text-gray-700' }, m.motivo), React.createElement('td', { className: 'py-3 text-right font-medium' }, m.quantidade) )) ) ) ) ), // ============================================================ // SUB-ABA: ALUNOS FALTOSOS // ============================================================ subAba === 'faltosos' && React.createElement('div', { className: 'space-y-6' }, // Cards de resumo React.createElement(CardsFrequencia, { faltosos: dadosFrequencia }), // Grid com tabela e gráfico React.createElement('div', { className: 'grid grid-cols-1 xl:grid-cols-3 gap-6' }, // Tabela de faltosos (ocupa 2 colunas) React.createElement('div', { className: 'xl:col-span-2 bg-white rounded-2xl p-5 shadow-sm border border-gray-100' }, React.createElement('h3', { className: 'font-bold text-gray-900 mb-4 flex items-center gap-2' }, React.createElement(Icon, { name: 'user-x', className: 'w-5 h-5 text-orange-500' }), 'Monitoramento de Frequência' ), React.createElement(TabelaFaltosos, { faltosos: dadosFrequencia, filtro: filtroFaltosos, setFiltro: setFiltroFaltosos }) ), // Gráfico por professor React.createElement('div', { className: 'xl:col-span-1' }, React.createElement(GraficoFaltasPorProfessor, { faltosos: dadosFrequencia }) ) ) ) ); }; }; })();