187 lines
7.6 KiB
TypeScript
187 lines
7.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { openCandApi } from '../api';
|
||
import type { OpenCandDataAvailabilityStats } from '../api/apiModels';
|
||
import Card from '../Components/Card';
|
||
|
||
const DataStatsPage: React.FC = () => {
|
||
const [stats, setStats] = useState<OpenCandDataAvailabilityStats | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
const fetchStats = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const data = await openCandApi.getDataAvailabilityStats();
|
||
setStats(data);
|
||
} catch (err) {
|
||
setError('Erro ao carregar estatísticas de disponibilidade de dados');
|
||
console.error('Error fetching data availability stats:', err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchStats();
|
||
}, []);
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||
<div className="text-center">
|
||
<div className="animate-spin rounded-full h-16 w-16 border-4 border-indigo-600 border-t-transparent mx-auto mb-4"></div>
|
||
<p className="text-lg text-gray-700">Carregando dados...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||
<div className="text-center">
|
||
<div className="text-red-600 text-6xl mb-4">⚠</div>
|
||
<p className="text-lg text-gray-700">{error}</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!stats) {
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||
<p className="text-lg text-gray-700">Nenhum dado disponível</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Get all unique years from all data types
|
||
const allYears = new Set<number>();
|
||
Object.values(stats).forEach((yearArray: number[]) => {
|
||
yearArray.forEach((year: number) => allYears.add(year));
|
||
});
|
||
const sortedYears = Array.from(allYears).sort((a, b) => b - a);
|
||
|
||
const dataTypes = [
|
||
{ key: 'candidatos', label: 'Candidatos', icon: '👤' },
|
||
{ key: 'bemCandidatos', label: 'Bens de Candidatos', icon: '💰' },
|
||
{ key: 'despesaCandidatos', label: 'Despesas de Candidatos', icon: '💸' },
|
||
{ key: 'receitaCandidatos', label: 'Receitas de Candidatos', icon: '💵' },
|
||
{ key: 'redeSocialCandidatos', label: 'Redes Sociais', icon: '📱' },
|
||
{ key: 'fotosCandidatos', label: 'Fotos de Candidatos', icon: '📸' },
|
||
];
|
||
|
||
return (
|
||
<div className="min-h-screen py-20 px-4">
|
||
<div className="max-w-7xl mx-auto">
|
||
{/* Header */}
|
||
<div className="text-center mb-12 animate-fade-in">
|
||
<h1 className="text-4xl md:text-6xl font-bold mb-4 text-white">
|
||
Disponibilidade de Dados
|
||
</h1>
|
||
<p className="text-xl text-gray-400">
|
||
Visualize a disponibilidade dos dados por ano em nossa base
|
||
</p>
|
||
<div className="w-32 h-1 bg-gradient-to-r from-indigo-400 to-purple-400 mx-auto mt-6 rounded-full"></div>
|
||
</div>
|
||
|
||
{/* Stats Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
||
<Card hasAnimation={true} className="bg-gray-800/10 rounded-xl">
|
||
<div className="text-center">
|
||
<div className="text-3xl mb-2">📊</div>
|
||
<div className="text-2xl font-bold text-white">{dataTypes.length}</div>
|
||
<div className="text-gray-400">Tipos de Dados</div>
|
||
</div>
|
||
</Card>
|
||
<Card hasAnimation={true} className="bg-gray-800/10 rounded-xl">
|
||
<div className="text-center">
|
||
<div className="text-3xl mb-2">📅</div>
|
||
<div className="text-2xl font-bold text-white">{sortedYears.length}</div>
|
||
<div className="text-gray-400">Anos Disponíveis</div>
|
||
</div>
|
||
</Card>
|
||
<Card hasAnimation={true} className="bg-gray-800/10 rounded-xl">
|
||
<div className="text-center">
|
||
<div className="text-3xl mb-2">🗓️</div>
|
||
<div className="text-2xl font-bold text-white">
|
||
{sortedYears.length > 0 ? `${Math.min(...sortedYears)} - ${Math.max(...sortedYears)}` : 'N/A'}
|
||
</div>
|
||
<div className="text-gray-400">Período</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Data Availability Table */}
|
||
<div className="bg-gray-800/10 backdrop-blur-xs rounded-xl shadow-lg hover:shadow-xl transform hover:scale-[1.005] transition-all duration-200 overflow-hidden ring-1 ring-gray-700 hover:ring-indigo-300">
|
||
<div className="p-6 border-b border-gray-700/30 bg-gray-800/10">
|
||
<h2 className="text-2xl font-bold text-white">
|
||
Matriz de Disponibilidade
|
||
</h2>
|
||
<p className="text-gray-400 mt-2">
|
||
✅ Disponível • ❌ Não Disponível
|
||
</p>
|
||
</div>
|
||
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead>
|
||
<tr className="bg-gray-800/10">
|
||
<th className="text-left p-4 text-white font-semibold border-b border-gray-700/30 sticky left-0 bg-gray-800/10">
|
||
Tipo de Dado
|
||
</th>
|
||
{sortedYears.map((year, index) => (
|
||
<th
|
||
key={year}
|
||
className="text-center p-4 text-white font-semibold border-b border-gray-700/30 animate-fade-in"
|
||
style={{ animationDelay: `${index * 50}ms` }}
|
||
>
|
||
{year}
|
||
</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{dataTypes.map((dataType, rowIndex) => (
|
||
<tr
|
||
key={dataType.key}
|
||
className="hover:bg-gray-800/10 transition-all duration-300 animate-slide-in-left"
|
||
style={{ animationDelay: `${rowIndex * 100}ms` }}
|
||
>
|
||
<td className="p-4 border-b border-gray-700/20 text-white sticky left-0 bg-gray-800/10">
|
||
<div className="flex items-center space-x-3">
|
||
<span className="text-xl">{dataType.icon}</span>
|
||
<span>{dataType.label}</span>
|
||
</div>
|
||
</td>
|
||
{sortedYears.map((year, cellIndex) => {
|
||
const isAvailable = (stats[dataType.key as keyof OpenCandDataAvailabilityStats] as number[]).includes(year);
|
||
return (
|
||
<td
|
||
key={year}
|
||
className="text-center p-4 border-b border-gray-700/20 animate-fade-in"
|
||
style={{ animationDelay: `${(rowIndex * sortedYears.length + cellIndex) * 30}ms` }}
|
||
>
|
||
<div className={`inline-flex items-center justify-center w-8 h-8 rounded-full transition-all duration-300 ${
|
||
isAvailable
|
||
? 'bg-green-500/20 text-green-300 hover:bg-green-500/30 hover:scale-110 hover:cursor-default'
|
||
: 'bg-red-500/20 text-red-300 hover:bg-red-500/30 hover:cursor-default'
|
||
}`}>
|
||
{isAvailable ? '✅' : '❌'}
|
||
</div>
|
||
</td>
|
||
);
|
||
})}
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default DataStatsPage;
|