feat(export): adicionar exportação Excel do ranking com barra de progresso
- Novo endpoint GET /api/v1/ranking/exportar/excel - Exporta apenas consultores com pontuação > 0 - Usa xlsxwriter para geração rápida (~40s para 300k registros) - Layout profissional com formatação, filtros e cores condicionais - Barra de progresso real no frontend com dois estados: - Animação indeterminada durante geração no servidor - Progresso real durante download do arquivo - Botão de exportação integrado ao layout do sistema - Suporte a cancelamento da exportação
This commit is contained in:
@@ -4,6 +4,7 @@ import ConsultorCard from './components/ConsultorCard';
|
||||
import CompararModal from './components/CompararModal';
|
||||
import FiltroSelos from './components/FiltroSelos';
|
||||
import SugerirConsultores from './components/SugerirConsultores';
|
||||
import ExportProgress from './components/ExportProgress';
|
||||
import { rankingService } from './services/api';
|
||||
import './App.css';
|
||||
|
||||
@@ -25,6 +26,10 @@ function App() {
|
||||
const [modalAberto, setModalAberto] = useState(false);
|
||||
const [filtroSelos, setFiltroSelos] = useState([]);
|
||||
const [sugerirAberto, setSugerirAberto] = useState(false);
|
||||
const [exportando, setExportando] = useState(false);
|
||||
const [exportProgress, setExportProgress] = useState({ loaded: 0, total: 0, percent: 0 });
|
||||
const [exportStatus, setExportStatus] = useState('preparing');
|
||||
const abortControllerRef = useRef(null);
|
||||
|
||||
const toggleSelecionado = (consultor) => {
|
||||
setSelecionados((prev) => {
|
||||
@@ -60,6 +65,45 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportarExcel = async () => {
|
||||
if (exportando) return;
|
||||
try {
|
||||
setExportando(true);
|
||||
setExportStatus('preparing');
|
||||
setExportProgress({ loaded: 0, total: 0, percent: 0 });
|
||||
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
||||
await rankingService.downloadRankingExcel(
|
||||
filtroSelos,
|
||||
(progress) => {
|
||||
setExportStatus('downloading');
|
||||
setExportProgress(progress);
|
||||
},
|
||||
abortControllerRef.current
|
||||
);
|
||||
|
||||
setExportStatus('complete');
|
||||
setTimeout(() => setExportando(false), 1000);
|
||||
} catch (err) {
|
||||
if (err.name === 'CanceledError' || err.code === 'ERR_CANCELED') {
|
||||
console.log('Exportação cancelada pelo usuário');
|
||||
} else {
|
||||
console.error('Erro ao exportar Excel:', err);
|
||||
setExportStatus('error');
|
||||
setTimeout(() => setExportando(false), 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelExport = () => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
setExportando(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadRanking();
|
||||
}, [page, pageSize, filtroSelos]);
|
||||
@@ -220,6 +264,15 @@ function App() {
|
||||
Sugerir por Tema
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="btn-exportar"
|
||||
onClick={handleExportarExcel}
|
||||
disabled={exportando || loading}
|
||||
title="Exportar ranking para Excel (apenas consultores com pontuação)"
|
||||
>
|
||||
{exportando ? 'Exportando...' : '📊 Exportar Excel'}
|
||||
</button>
|
||||
|
||||
<form className="search-box" onSubmit={handleSubmitBuscar}>
|
||||
<input
|
||||
type="text"
|
||||
@@ -290,6 +343,14 @@ function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{exportando && (
|
||||
<ExportProgress
|
||||
progress={exportProgress}
|
||||
status={exportStatus}
|
||||
onCancel={handleCancelExport}
|
||||
/>
|
||||
)}
|
||||
|
||||
<footer>
|
||||
<p>Dados: ATUACAPES (Elasticsearch) + Oracle</p>
|
||||
<p>Clique em qualquer consultor para ver detalhes</p>
|
||||
|
||||
Reference in New Issue
Block a user