/** * Supera Glia - Tab de Reposições * @module tabs/turmas/ReposicoesTab */ (function() { 'use strict'; SuperaGlia.initReposicoesTab = function() { const { useState, useEffect, useMemo, useCallback } = SuperaGlia.hooks; const { API_BASE } = SuperaGlia.config; const { Icon, perfilColors, getStatusColor } = SuperaGlia; const { formatDate, formatDateTime, formatMoney } = SuperaGlia.utils; SuperaGlia.ReposicoesTab = ({ dados }) => { const [reposicoes, setReposicoes] = useState([]); const [carregando, setCarregando] = useState(true); const [filtroStatus, setFiltroStatus] = useState('todos'); const [filtroPeriodo, setFiltroPeriodo] = useState('365'); const [busca, setBusca] = useState(''); const [mostrarModal, setMostrarModal] = useState(false); const [salvando, setSalvando] = useState(false); // Dados para autocomplete const [alunos, setAlunos] = useState([]); const [turmas, setTurmas] = useState([]); const [buscaAluno, setBuscaAluno] = useState(''); const [mostrarSugestoes, setMostrarSugestoes] = useState(false); const [carregandoAlunos, setCarregandoAlunos] = useState(false); const [novaReposicao, setNovaReposicao] = useState({ nome_aluno: '', cod_aluno: '', turma_origem: '', sala_origem: '', data_falta: '', data_reposicao: '', turma_reposicao: '', sala_reposicao: '', observacao_roteiro: '', status: 'pendente' }); useEffect(() => { carregarReposicoes(); }, [filtroPeriodo]); // Carregar alunos e turmas quando abrir o modal useEffect(() => { if (mostrarModal && alunos.length === 0) { carregarAlunosETurmas(); } }, [mostrarModal]); const carregarAlunosETurmas = async () => { setCarregandoAlunos(true); try { const [alunosRes, turmasRes] = await Promise.all([ fetch(`${API_BASE}/alunos`), fetch(`${API_BASE}/turmas`) ]); const alunosData = await alunosRes.json(); const turmasData = await turmasRes.json(); // Filtrar apenas alunos com matrícula vigente const alunosAtivos = (alunosData.alunos || alunosData || []) .filter(a => a.situacao_matricula === 'Vigente'); console.log('Alunos carregados:', alunosAtivos.length); setAlunos(alunosAtivos); // Construir nomes das turmas a partir dos dados // dia: 1=2ª, 2=3ª, 3=4ª, 4=5ª, 5=6ª, 6=Sáb const diasSemana = { 1: '2ª', 2: '3ª', 3: '4ª', 4: '5ª', 5: '6ª', 6: 'Sáb' }; const turmasLista = (turmasData.turmas || turmasData || []).map(t => { const dia = diasSemana[t.dia] || t.dia; const horario = t.horario ? t.horario.split(' - ')[0] : ''; return { id: t.id, nome: `${dia} (${horario} - ${t.perfil}) - ${t.sala}`, sala: t.sala, dia: t.dia, horario: t.horario, perfil: t.perfil }; }).sort((a, b) => a.dia - b.dia || a.horario?.localeCompare(b.horario)); console.log('Turmas carregadas:', turmasLista.length); setTurmas(turmasLista); } catch (e) { console.error('Erro ao carregar dados:', e); } setCarregandoAlunos(false); }; const carregarReposicoes = async () => { setCarregando(true); try { const res = await fetch(`${API_BASE}/reposicoes?dias=${filtroPeriodo}`); const data = await res.json(); setReposicoes(data.reposicoes || data || []); } catch (e) { console.error('Erro ao carregar reposições:', e); } setCarregando(false); }; const selecionarAluno = (aluno) => { console.log('Aluno selecionado:', aluno); setNovaReposicao(prev => ({ ...prev, nome_aluno: aluno.nome, cod_aluno: aluno.cod_aluno || aluno.matricula || '', turma_origem: aluno.turma || '', sala_origem: aluno.sala || '' })); setBuscaAluno(aluno.nome); setMostrarSugestoes(false); }; const alunosFiltrados = useMemo(() => { if (!buscaAluno || buscaAluno.length < 2) return []; const termo = buscaAluno.toLowerCase().trim(); const resultado = alunos .filter(a => a.nome && a.nome.toLowerCase().includes(termo)) .slice(0, 10); console.log(`Busca "${termo}": ${resultado.length} resultados de ${alunos.length} alunos`); return resultado; }, [alunos, buscaAluno]); const salvarReposicao = async () => { if (!novaReposicao.nome_aluno || !novaReposicao.turma_origem || !novaReposicao.data_falta) { alert('Preencha os campos obrigatórios: Nome do Aluno, Turma Original e Data da Falta'); return; } setSalvando(true); try { const res = await fetch(`${API_BASE}/reposicoes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(novaReposicao) }); const data = await res.json(); if (data.success || data.id) { setMostrarModal(false); setNovaReposicao({ nome_aluno: '', cod_aluno: '', turma_origem: '', sala_origem: '', data_falta: '', data_reposicao: '', turma_reposicao: '', sala_reposicao: '', observacao_roteiro: '', status: 'pendente' }); setBuscaAluno(''); carregarReposicoes(); } else { alert('Erro ao salvar: ' + (data.error || 'Erro desconhecido')); } } catch (e) { console.error('Erro ao salvar reposição:', e); alert('Erro ao salvar reposição'); } setSalvando(false); }; const reposicoesFiltradas = useMemo(() => { return reposicoes.filter(r => { if (filtroStatus !== 'todos' && r.status !== filtroStatus) return false; if (busca) { const termo = busca.toLowerCase(); if (!r.nome_aluno?.toLowerCase().includes(termo) && !r.turma_origem?.toLowerCase().includes(termo) && !r.turma_reposicao?.toLowerCase().includes(termo)) return false; } return true; }); }, [reposicoes, filtroStatus, busca]); const resumo = useMemo(() => ({ total: reposicoes.length, pendentes: reposicoes.filter(r => r.status === 'pendente').length, agendadas: reposicoes.filter(r => r.status === 'agendada').length, lancadas: reposicoes.filter(r => r.status === 'lancada').length, nao_compareceu: reposicoes.filter(r => r.status === 'nao_compareceu').length }), [reposicoes]); const getStatusBadge = (status) => { const cores = { 'pendente': 'bg-amber-100 text-amber-700', 'agendada': 'bg-blue-100 text-blue-700', 'lancada': 'bg-emerald-100 text-emerald-700', 'realizada': 'bg-emerald-100 text-emerald-700', 'nao_compareceu': 'bg-red-100 text-red-700', 'expirada': 'bg-gray-100 text-gray-700' }; return cores[status] || 'bg-gray-100 text-gray-700'; }; const getStatusLabel = (status) => { const labels = { 'pendente': 'Pendente', 'agendada': 'Agendada', 'lancada': 'Lançada', 'realizada': 'Realizada', 'nao_compareceu': 'Não Compareceu', 'expirada': 'Expirada' }; return labels[status] || status; }; // Atualizar turma de reposição selecionada const atualizarTurmaReposicao = (turmaId) => { const turmaSelecionada = turmas.find(t => String(t.id) === String(turmaId)); console.log('Turma selecionada:', turmaId, turmaSelecionada); if (turmaSelecionada) { setNovaReposicao(prev => ({ ...prev, turma_reposicao: turmaSelecionada.nome, sala_reposicao: turmaSelecionada.sala })); } else { setNovaReposicao(prev => ({ ...prev, turma_reposicao: '', sala_reposicao: '' })); } }; if (carregando) { return React.createElement('div', { className: 'flex items-center justify-center h-64' }, React.createElement('div', { className: 'text-center' }, React.createElement('div', { className: 'w-10 h-10 border-4 border-orange-200 border-t-orange-500 rounded-full animate-spin mx-auto mb-4' }), React.createElement('p', { className: 'text-gray-500' }, 'Carregando reposições...') ) ); } return React.createElement('div', { className: 'space-y-4' }, // Cards de resumo React.createElement('div', { className: 'grid grid-cols-2 md:grid-cols-5 gap-4' }, [ { label: 'Total', value: resumo.total, color: 'bg-gray-100', textColor: 'text-gray-700', icon: 'calendar-clock' }, { label: 'Pendentes', value: resumo.pendentes, color: 'bg-amber-100', textColor: 'text-amber-700', icon: 'clock' }, { label: 'Agendadas', value: resumo.agendadas, color: 'bg-blue-100', textColor: 'text-blue-700', icon: 'calendar-check' }, { label: 'Lançadas', value: resumo.lancadas, color: 'bg-emerald-100', textColor: 'text-emerald-700', icon: 'check-circle' }, { label: 'Não Compareceu', value: resumo.nao_compareceu, color: 'bg-red-100', textColor: 'text-red-700', icon: 'x-circle' } ].map((card, i) => React.createElement('div', { key: i, className: `${card.color} rounded-xl p-4 flex items-center gap-3` }, 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 ${card.textColor}` }) ), React.createElement('div', null, React.createElement('p', { className: `text-2xl font-bold ${card.textColor}` }, card.value), React.createElement('p', { className: `text-xs ${card.textColor} opacity-75` }, card.label) ) )) ), // Filtros React.createElement('div', { className: 'bg-white rounded-xl p-4 shadow-sm border border-gray-100' }, React.createElement('div', { className: 'flex flex-wrap gap-4 items-end' }, React.createElement('div', { className: 'flex-1 min-w-[200px]' }, React.createElement('label', { className: 'block text-xs text-gray-500 mb-1' }, 'Buscar'), React.createElement('input', { type: 'text', placeholder: 'Nome do aluno ou turma...', value: busca, onChange: e => setBusca(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs text-gray-500 mb-1' }, 'Status'), React.createElement('select', { value: filtroStatus, onChange: e => setFiltroStatus(e.target.value), className: 'px-3 py-2 border border-gray-200 rounded-lg text-sm' }, React.createElement('option', { value: 'todos' }, 'Todos'), React.createElement('option', { value: 'pendente' }, 'Pendentes'), React.createElement('option', { value: 'agendada' }, 'Agendadas'), React.createElement('option', { value: 'lancada' }, 'Lançadas'), React.createElement('option', { value: 'nao_compareceu' }, 'Não Compareceu') ) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs text-gray-500 mb-1' }, 'Período'), React.createElement('select', { value: filtroPeriodo, onChange: e => setFiltroPeriodo(e.target.value), className: 'px-3 py-2 border border-gray-200 rounded-lg text-sm' }, React.createElement('option', { value: '7' }, 'Últimos 7 dias'), React.createElement('option', { value: '14' }, 'Últimos 14 dias'), React.createElement('option', { value: '30' }, 'Últimos 30 dias'), React.createElement('option', { value: '60' }, 'Últimos 60 dias'), React.createElement('option', { value: '90' }, 'Últimos 90 dias'), React.createElement('option', { value: '365' }, 'Último ano') ) ), React.createElement('button', { onClick: carregarReposicoes, className: 'px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg text-sm font-medium transition-colors flex items-center gap-2' }, React.createElement(Icon, { name: 'refresh-cw', className: 'w-4 h-4' }), 'Atualizar' ), React.createElement('button', { onClick: () => setMostrarModal(true), className: 'px-4 py-2 bg-orange-500 hover:bg-orange-600 text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-2' }, React.createElement(Icon, { name: 'plus', className: 'w-4 h-4' }), 'Nova Reposição' ) ) ), // Tabela React.createElement('div', { className: 'bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden' }, React.createElement('div', { className: 'p-4 border-b border-gray-100' }, React.createElement('h3', { className: 'font-semibold text-gray-900' }, `${reposicoesFiltradas.length} reposições encontradas` ) ), React.createElement('div', { className: 'overflow-x-auto' }, React.createElement('table', { className: 'w-full' }, React.createElement('thead', { className: 'bg-gray-50' }, React.createElement('tr', null, ['Aluno', 'Turma Original', 'Data Falta', 'Status', 'Data Reposição', 'Turma Reposição', 'Observação'].map(h => React.createElement('th', { key: h, className: 'text-left px-4 py-3 text-xs font-semibold text-gray-500 uppercase' }, h) ) ) ), React.createElement('tbody', { className: 'divide-y divide-gray-50' }, reposicoesFiltradas.length === 0 ? React.createElement('tr', null, React.createElement('td', { colSpan: 7, className: 'px-4 py-8 text-center text-gray-500' }, 'Nenhuma reposição encontrada') ) : reposicoesFiltradas.slice(0, 100).map((repo, i) => { return React.createElement('tr', { key: repo.id || i, className: 'hover:bg-gray-50' }, React.createElement('td', { className: 'px-4 py-3' }, React.createElement('div', null, React.createElement('p', { className: 'font-medium text-gray-900' }, repo.nome_aluno || '-'), repo.cod_aluno && React.createElement('p', { className: 'text-xs text-gray-500' }, `Cód: ${repo.cod_aluno}`) ) ), React.createElement('td', { className: 'px-4 py-3 text-sm text-gray-600' }, repo.turma_origem || '-'), React.createElement('td', { className: 'px-4 py-3 text-sm' }, formatDate(repo.data_falta)), React.createElement('td', { className: 'px-4 py-3' }, React.createElement('span', { className: `text-xs px-2 py-1 rounded-full ${getStatusBadge(repo.status)}` }, getStatusLabel(repo.status) ) ), React.createElement('td', { className: 'px-4 py-3 text-sm' }, repo.data_reposicao ? formatDate(repo.data_reposicao) : '-' ), React.createElement('td', { className: 'px-4 py-3 text-sm text-gray-600' }, repo.turma_reposicao || '-'), React.createElement('td', { className: 'px-4 py-3 text-sm text-gray-500 max-w-xs truncate' }, repo.observacao_roteiro || '-' ) ); }) ) ) ), reposicoesFiltradas.length > 100 && React.createElement('div', { className: 'p-3 bg-gray-50 border-t text-center text-sm text-gray-500' }, `Mostrando 100 de ${reposicoesFiltradas.length} registros`) ), // Modal Nova Reposição mostrarModal && React.createElement('div', { className: 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4', onClick: (e) => e.target === e.currentTarget && setMostrarModal(false) }, React.createElement('div', { className: 'bg-white rounded-2xl shadow-xl w-full max-w-lg max-h-[90vh] overflow-y-auto' }, React.createElement('div', { className: 'p-6 border-b border-gray-100 flex justify-between items-center' }, React.createElement('h2', { className: 'text-xl font-bold text-gray-900' }, 'Nova Reposição'), React.createElement('button', { onClick: () => setMostrarModal(false), className: 'text-gray-400 hover:text-gray-600' }, React.createElement(Icon, { name: 'x', className: 'w-6 h-6' }) ) ), React.createElement('div', { className: 'p-6 space-y-4' }, // Busca de Aluno com Autocomplete React.createElement('div', { className: 'relative' }, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Buscar Aluno *', carregandoAlunos && React.createElement('span', { className: 'ml-2 text-xs text-gray-400' }, '(carregando...)') ), React.createElement('input', { type: 'text', value: buscaAluno, onChange: e => { setBuscaAluno(e.target.value); setMostrarSugestoes(true); }, onFocus: () => setMostrarSugestoes(true), onBlur: () => setTimeout(() => setMostrarSugestoes(false), 200), className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-orange-500 focus:border-transparent', placeholder: 'Digite o nome do aluno para buscar...' }), // Lista de sugestões mostrarSugestoes && alunosFiltrados.length > 0 && React.createElement('div', { className: 'absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto' }, alunosFiltrados.map((aluno, idx) => React.createElement('button', { key: aluno.cod_aluno || idx, type: 'button', onClick: () => selecionarAluno(aluno), className: 'w-full px-4 py-2 text-left hover:bg-orange-50 border-b border-gray-100 last:border-0' }, React.createElement('p', { className: 'font-medium text-gray-900' }, aluno.nome), React.createElement('p', { className: 'text-xs text-gray-500' }, `${aluno.turma || 'Sem turma'} • Cód: ${aluno.cod_aluno || aluno.matricula || '-'}` ) )) ) ), // Dados do aluno selecionado (readonly) novaReposicao.nome_aluno && React.createElement('div', { className: 'bg-orange-50 rounded-lg p-3 space-y-2' }, React.createElement('p', { className: 'text-sm font-medium text-orange-800' }, 'Aluno selecionado:'), React.createElement('div', { className: 'grid grid-cols-2 gap-2 text-sm' }, React.createElement('div', null, React.createElement('span', { className: 'text-orange-600' }, 'Nome: '), React.createElement('span', { className: 'text-orange-900' }, novaReposicao.nome_aluno) ), React.createElement('div', null, React.createElement('span', { className: 'text-orange-600' }, 'Código: '), React.createElement('span', { className: 'text-orange-900' }, novaReposicao.cod_aluno || '-') ), React.createElement('div', { className: 'col-span-2' }, React.createElement('span', { className: 'text-orange-600' }, 'Turma: '), React.createElement('span', { className: 'text-orange-900' }, novaReposicao.turma_origem || '-') ) ) ), // Data da Falta React.createElement('div', null, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Data da Falta *'), React.createElement('input', { type: 'date', value: novaReposicao.data_falta, onChange: e => setNovaReposicao(prev => ({...prev, data_falta: e.target.value})), className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-orange-500 focus:border-transparent' }) ), // Data Reposição React.createElement('div', null, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Data da Reposição'), React.createElement('input', { type: 'date', value: novaReposicao.data_reposicao, onChange: e => setNovaReposicao(prev => ({...prev, data_reposicao: e.target.value})), className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-orange-500 focus:border-transparent' }) ), // Turma Reposição (Select) React.createElement('div', null, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Turma da Reposição'), React.createElement('select', { value: turmas.find(t => t.nome === novaReposicao.turma_reposicao)?.id || '', onChange: e => atualizarTurmaReposicao(e.target.value), className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-orange-500 focus:border-transparent' }, React.createElement('option', { value: '' }, 'Selecione a turma...'), turmas.map(turma => React.createElement('option', { key: turma.id, value: turma.id }, turma.nome)) ) ), // Sala Reposição (readonly, preenchida automaticamente) novaReposicao.sala_reposicao && React.createElement('div', null, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Sala da Reposição'), React.createElement('input', { type: 'text', value: novaReposicao.sala_reposicao, readOnly: true, className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm bg-gray-50 text-gray-600' }) ), // Status React.createElement('div', null, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Status'), React.createElement('select', { value: novaReposicao.status, onChange: e => setNovaReposicao(prev => ({...prev, status: e.target.value})), className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-orange-500 focus:border-transparent' }, React.createElement('option', { value: 'pendente' }, 'Pendente'), React.createElement('option', { value: 'agendada' }, 'Agendada'), React.createElement('option', { value: 'lancada' }, 'Lançada'), React.createElement('option', { value: 'nao_compareceu' }, 'Não Compareceu') ) ), // Observação React.createElement('div', null, React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Observação / Roteiro'), React.createElement('textarea', { value: novaReposicao.observacao_roteiro, onChange: e => setNovaReposicao(prev => ({...prev, observacao_roteiro: e.target.value})), className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-orange-500 focus:border-transparent', rows: 3, placeholder: 'Observações ou roteiro da aula' }) ) ), React.createElement('div', { className: 'p-6 border-t border-gray-100 flex justify-end gap-3' }, React.createElement('button', { onClick: () => { setMostrarModal(false); setBuscaAluno(''); setNovaReposicao({ nome_aluno: '', cod_aluno: '', turma_origem: '', sala_origem: '', data_falta: '', data_reposicao: '', turma_reposicao: '', sala_reposicao: '', observacao_roteiro: '', status: 'pendente' }); }, className: 'px-4 py-2 text-gray-600 hover:text-gray-800 font-medium' }, 'Cancelar'), React.createElement('button', { onClick: salvarReposicao, disabled: salvando || !novaReposicao.nome_aluno, className: 'px-6 py-2 bg-orange-500 hover:bg-orange-600 text-white rounded-lg font-medium disabled:opacity-50 flex items-center gap-2' }, salvando && React.createElement('div', { className: 'w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin' }), salvando ? 'Salvando...' : 'Salvar' ) ) ) ) ); }; }; })();