várias mudanças
All checks were successful
Frontend Build and Deploy / build (push) Successful in 1m2s
All checks were successful
Frontend Build and Deploy / build (push) Successful in 1m2s
This commit is contained in:
parent
add1db4376
commit
1e4f288ec4
@ -8,16 +8,16 @@ export interface Candidate {
|
||||
nome: string;
|
||||
cpf: string;
|
||||
dataNascimento: string;
|
||||
email: string;
|
||||
estadoCivil: string;
|
||||
sexo: string;
|
||||
ocupacao: string;
|
||||
apelido: string;
|
||||
fotoUrl: string;
|
||||
ultimoano: number;
|
||||
localidade: string;
|
||||
}
|
||||
|
||||
export interface CandidateDetails extends Candidate {
|
||||
eleicoes: Election[];
|
||||
candidatoExt: CandidateExt[];
|
||||
}
|
||||
|
||||
export interface Election {
|
||||
@ -33,6 +33,15 @@ export interface Election {
|
||||
partido: Partido;
|
||||
}
|
||||
|
||||
export interface CandidateExt {
|
||||
ano: number;
|
||||
apelido: string;
|
||||
email: string;
|
||||
estadoCivil: string;
|
||||
escolaridade: string;
|
||||
ocupacao: string;
|
||||
}
|
||||
|
||||
export interface CandidateAssets {
|
||||
bens: Asset[];
|
||||
}
|
||||
|
@ -38,6 +38,82 @@ const BasicCandidateInfoComponent: React.FC<BasicCandidateInfoComponentProps> =
|
||||
}
|
||||
};
|
||||
|
||||
const getDetailsList = () => {
|
||||
if (!candidateDetails) return null;
|
||||
}
|
||||
|
||||
const getHistoricalData = () => {
|
||||
if (!candidateDetails?.candidatoExt || candidateDetails.candidatoExt.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const historicalData = {
|
||||
emails: new Map<string, number[]>(),
|
||||
estadosCivis: new Map<string, number[]>(),
|
||||
escolaridades: new Map<string, number[]>(),
|
||||
ocupacoes: new Map<string, number[]>()
|
||||
};
|
||||
|
||||
// Group data by value and collect years
|
||||
candidateDetails.candidatoExt.forEach(ext => {
|
||||
if (ext.email) {
|
||||
const years = historicalData.emails.get(ext.email) || [];
|
||||
years.push(ext.ano);
|
||||
historicalData.emails.set(ext.email, years);
|
||||
}
|
||||
if (ext.estadoCivil) {
|
||||
const years = historicalData.estadosCivis.get(ext.estadoCivil) || [];
|
||||
years.push(ext.ano);
|
||||
historicalData.estadosCivis.set(ext.estadoCivil, years);
|
||||
}
|
||||
if (ext.escolaridade) {
|
||||
const years = historicalData.escolaridades.get(ext.escolaridade) || [];
|
||||
years.push(ext.ano);
|
||||
historicalData.escolaridades.set(ext.escolaridade, years);
|
||||
}
|
||||
if (ext.ocupacao) {
|
||||
const years = historicalData.ocupacoes.get(ext.ocupacao) || [];
|
||||
years.push(ext.ano);
|
||||
historicalData.ocupacoes.set(ext.ocupacao, years);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort years for each entry
|
||||
historicalData.emails.forEach((years, key) => {
|
||||
historicalData.emails.set(key, years.sort((a, b) => b - a));
|
||||
});
|
||||
historicalData.estadosCivis.forEach((years, key) => {
|
||||
historicalData.estadosCivis.set(key, years.sort((a, b) => b - a));
|
||||
});
|
||||
historicalData.escolaridades.forEach((years, key) => {
|
||||
historicalData.escolaridades.set(key, years.sort((a, b) => b - a));
|
||||
});
|
||||
historicalData.ocupacoes.forEach((years, key) => {
|
||||
historicalData.ocupacoes.set(key, years.sort((a, b) => b - a));
|
||||
});
|
||||
|
||||
return historicalData;
|
||||
};
|
||||
|
||||
const renderHistoricalField = (title: string, dataMap: Map<string, number[]>) => {
|
||||
if (dataMap.size === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-gray-600 uppercase tracking-wide">{title}</label>
|
||||
<div className="mt-1 space-y-1">
|
||||
{Array.from(dataMap.entries()).map(([value, years], index) => (
|
||||
<div key={index} className="text-center">
|
||||
<p className="text-lg text-gray-900">
|
||||
{value} <span className="text-xs text-gray-500 ml-2">{years.join(', ')}</span>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white/95 backdrop-blur-sm rounded-xl shadow-lg hover:shadow-xl transform hover:scale-[1.01] transition-all duration-200 p-6">
|
||||
<div className="flex items-center mb-6">
|
||||
@ -58,7 +134,7 @@ const BasicCandidateInfoComponent: React.FC<BasicCandidateInfoComponentProps> =
|
||||
<img
|
||||
src={candidateDetails.fotoUrl}
|
||||
alt={`Foto de ${candidateDetails.nome}`}
|
||||
className="w-32 h-32 rounded-full object-cover border-4 border-blue-200 shadow-lg"
|
||||
className="w-36 h-36 rounded-full object-cover border-4 border-blue-200 shadow-lg"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = 'none';
|
||||
@ -101,27 +177,27 @@ const BasicCandidateInfoComponent: React.FC<BasicCandidateInfoComponentProps> =
|
||||
<p className="text-lg text-gray-900 mt-1">{formatDate(candidateDetails.dataNascimento)}</p>
|
||||
</div>
|
||||
|
||||
{candidateDetails.email && (
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-gray-600 uppercase tracking-wide">Email</label>
|
||||
<p className="text-lg text-gray-900 mt-1">{candidateDetails.email}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-gray-600 uppercase tracking-wide">Estado Civil</label>
|
||||
<p className="text-lg text-gray-900 mt-1">{candidateDetails.estadoCivil}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-gray-600 uppercase tracking-wide">Sexo</label>
|
||||
<p className="text-lg text-gray-900 mt-1">{candidateDetails.sexo}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-gray-600 uppercase tracking-wide">Ocupação</label>
|
||||
<p className="text-lg text-gray-900 mt-1">{candidateDetails.ocupacao}</p>
|
||||
</div>
|
||||
{/* Historical Data Section */}
|
||||
{(() => {
|
||||
const historicalData = getHistoricalData();
|
||||
if (!historicalData) return null;
|
||||
|
||||
return (
|
||||
<div className="border-t border-gray-200 pt-4 mt-6">
|
||||
<div className="space-y-4">
|
||||
{renderHistoricalField("Email", historicalData.emails)}
|
||||
{renderHistoricalField("Estado Civil", historicalData.estadosCivis)}
|
||||
{renderHistoricalField("Escolaridade", historicalData.escolaridades)}
|
||||
{renderHistoricalField("Ocupação", historicalData.ocupacoes)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-gray-500 py-8">
|
||||
|
@ -8,6 +8,7 @@ import BasicCandidateInfoComponent from './BasicCandidateInfoComponent';
|
||||
import SocialMediaComponent from './SocialMediaComponent';
|
||||
import IncomeExpenseComponent from './IncomeExpenseComponent';
|
||||
import Button from '../../shared/Button';
|
||||
import ErrorPage from '../ErrorPage';
|
||||
|
||||
const CandidatePage: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@ -113,19 +114,11 @@ const CandidatePage: React.FC = () => {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<main className="flex-grow flex items-center justify-center min-h-screen p-4">
|
||||
<div className="text-center text-white">
|
||||
<h1 className="text-2xl font-bold mb-4">Erro</h1>
|
||||
<p className="text-red-400 mb-4">{error}</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-block px-8 py-3 bg-gradient-to-r from-indigo-500 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-600 hover:to-purple-700 transition-all duration-300 transform shadow-lg hover:shadow-xl"
|
||||
>
|
||||
Voltar para a página inicial
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
<ErrorPage
|
||||
title="Erro"
|
||||
description={error}
|
||||
helperText="Tente novamente ou volte para a página inicial."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { openCandApi } from '../api';
|
||||
import type { OpenCandDataAvailabilityStats } from '../api/apiModels';
|
||||
import Card from '../shared/Card';
|
||||
import ErrorPage from './ErrorPage';
|
||||
|
||||
const DataStatsPage: React.FC = () => {
|
||||
const [stats, setStats] = useState<OpenCandDataAvailabilityStats | null>(null);
|
||||
@ -15,7 +16,7 @@ const DataStatsPage: React.FC = () => {
|
||||
const data = await openCandApi.getDataAvailabilityStats();
|
||||
setStats(data);
|
||||
} catch (err) {
|
||||
setError('Erro ao carregar estatísticas de disponibilidade de dados');
|
||||
setError('Erro ao carregar matriz de disponibilidade de dados');
|
||||
console.error('Error fetching data availability stats:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -38,12 +39,11 @@ const DataStatsPage: React.FC = () => {
|
||||
|
||||
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>
|
||||
<ErrorPage
|
||||
title="Erro"
|
||||
description={error}
|
||||
helperText="Tente novamente ou volte para a página inicial."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
64
src/components/ErrorPage.tsx
Normal file
64
src/components/ErrorPage.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface ErrorPageProps {
|
||||
title: string;
|
||||
description: string;
|
||||
helperText?: string;
|
||||
}
|
||||
|
||||
const ErrorPage: React.FC<ErrorPageProps> = ({ title, description, helperText }) => {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center px-4">
|
||||
<div className="text-center">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-9xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-purple-400 mb-4">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-gray-300 text-lg mb-8 max-w-md mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-block px-8 py-3 bg-gradient-to-r from-indigo-500 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-600 hover:to-purple-700 transition-all duration-300 transform shadow-lg hover:shadow-xl"
|
||||
>
|
||||
Voltar para a página inicial
|
||||
</Link>
|
||||
|
||||
{helperText && (
|
||||
<div className="mt-6">
|
||||
<p className="text-gray-400 text-sm">
|
||||
{helperText}
|
||||
</p>
|
||||
<div className="flex justify-center space-x-4 mt-3">
|
||||
<Link
|
||||
to="/"
|
||||
className="text-indigo-400 hover:text-indigo-300 transition-colors duration-200"
|
||||
>
|
||||
Início
|
||||
</Link>
|
||||
<span className="text-gray-500">•</span>
|
||||
<Link
|
||||
to="/dados-disponiveis"
|
||||
className="text-indigo-400 hover:text-indigo-300 transition-colors duration-200"
|
||||
>
|
||||
Dados Disponíveis
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 -z-10">
|
||||
<div className="w-96 h-96 bg-gradient-to-r from-indigo-500/10 to-purple-500/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
@ -1,58 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ErrorPage from './ErrorPage';
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center px-4">
|
||||
<div className="text-center">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-9xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-purple-400 mb-4">
|
||||
404
|
||||
</h1>
|
||||
<h2 className="text-3xl font-semibold text-white mb-4">
|
||||
Página não encontrada
|
||||
</h2>
|
||||
<p className="text-gray-300 text-lg mb-8 max-w-md mx-auto">
|
||||
A página que você está procurando não existe ou foi movida para outro local.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-block px-8 py-3 bg-gradient-to-r from-indigo-500 to-purple-600 text-white font-semibold rounded-lg hover:from-indigo-600 hover:to-purple-700 transition-all duration-300 transform shadow-lg hover:shadow-xl"
|
||||
>
|
||||
Voltar para a página inicial
|
||||
</Link>
|
||||
|
||||
<div className="mt-6">
|
||||
<p className="text-gray-400 text-sm">
|
||||
Ou tente uma dessas páginas:
|
||||
</p>
|
||||
<div className="flex justify-center space-x-4 mt-3">
|
||||
<Link
|
||||
to="/"
|
||||
className="text-indigo-400 hover:text-indigo-300 transition-colors duration-200"
|
||||
>
|
||||
Início
|
||||
</Link>
|
||||
<span className="text-gray-500">•</span>
|
||||
<Link
|
||||
to="/dados-disponiveis"
|
||||
className="text-indigo-400 hover:text-indigo-300 transition-colors duration-200"
|
||||
>
|
||||
Dados Disponíveis
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 -z-10">
|
||||
<div className="w-96 h-96 bg-gradient-to-r from-indigo-500/10 to-purple-500/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorPage
|
||||
title="404"
|
||||
description="A página que você está procurando não existe ou foi movida para outro local."
|
||||
helperText="Ou tente uma dessas páginas:"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/solid';
|
||||
import { MagnifyingGlassIcon, XMarkIcon, MapPinIcon } from '@heroicons/react/24/solid';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { openCandApi, type Candidate, ApiError } from '../api';
|
||||
import { formatDateToDDMMYYYY, maskCpf } from '../utils/utils';
|
||||
@ -98,7 +98,6 @@ const SearchBar: React.FC<SearchBarProps> = ({ className = '' }) => {
|
||||
|
||||
if (candidate.cpf) desc.push(`CPF: ${maskCpf(candidate.cpf)}`);
|
||||
if (candidate.apelido) desc.push(`"${candidate.apelido}"`);
|
||||
if (candidate.ocupacao && candidate.ocupacao != 'OUTROS') desc.push(`${candidate.ocupacao}`);
|
||||
if (desc.length == 0)
|
||||
if (candidate.dataNascimento) desc.push(`${formatDateToDDMMYYYY(candidate.dataNascimento)}`);
|
||||
|
||||
@ -172,8 +171,11 @@ const SearchBar: React.FC<SearchBarProps> = ({ className = '' }) => {
|
||||
<div className="text-gray-600 text-sm mt-1">
|
||||
{getCandidateDescription(candidate)}
|
||||
</div>
|
||||
{candidate.email && (
|
||||
<div className="text-gray-500 text-xs mt-1">{candidate.email}</div>
|
||||
{candidate.localidade && (
|
||||
<div className="flex items-center text-gray-500 text-xs mt-1">
|
||||
<MapPinIcon className="h-3 w-3 mr-1" />
|
||||
{candidate.localidade}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
Loading…
x
Reference in New Issue
Block a user