opencand.ui/src/components/DataStatsPage.tsx

187 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;