Compare commits

...

4 Commits

Author SHA1 Message Date
720596f6f8 adding dynamic hero text header
All checks were successful
Frontend Build and Deploy / build (push) Successful in 44s
2025-09-12 21:36:04 -03:00
dd2af50722 scroll not working for recursos 2025-09-12 21:30:30 -03:00
f56b030435 removing firebase hard-dependency 2025-09-12 21:27:05 -03:00
4e12ab32e8 add filters to statistics 2025-09-12 21:26:58 -03:00
6 changed files with 100 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React, { useEffect } from 'react';
import { Routes, Route } from 'react-router-dom'; import { Routes, Route, useLocation } from 'react-router-dom';
import Navbar from './components/Navbar'; import Navbar from './components/Navbar';
import HeroSection from './components/HeroSection'; import HeroSection from './components/HeroSection';
import StatisticsSection from './components/StatisticsSection'; import StatisticsSection from './components/StatisticsSection';
@@ -14,17 +14,42 @@ import MatrixBackground from './components/MatrixBackground';
import './App.css'; import './App.css';
// HomePage component // HomePage component
const HomePage: React.FC = () => ( const HomePage: React.FC = () => {
<main className="flex-grow"> const location = useLocation();
<HeroSection />
<div className="w-32 h-1 bg-gradient-to-r from-indigo-400 to-purple-400 mx-auto mt-12 rounded-full"></div> useEffect(() => {
<StatisticsSection /> if (location.hash) {
<div className="w-32 h-1 bg-gradient-to-r from-indigo-400 to-purple-400 mx-auto mt-6 rounded-full"></div> const element = document.getElementById(location.hash.substring(1));
<FeaturesSection /> if (element) {
</main> element.scrollIntoView({ behavior: 'smooth' });
); }
}
}, [location]);
return (
<main className="flex-grow">
<HeroSection />
<div className="w-32 h-1 bg-gradient-to-r from-indigo-400 to-purple-400 mx-auto mt-12 rounded-full"></div>
<StatisticsSection />
<div className="w-32 h-1 bg-gradient-to-r from-indigo-400 to-purple-400 mx-auto mt-6 rounded-full"></div>
<FeaturesSection />
</main>
);
};
function App() { function App() {
const location = useLocation();
useEffect(() => {
const segment = location.hash;
if (segment) {
const element = document.getElementById(segment.replace('#', ''));
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
}, [location]);
return ( return (
<div className="min-h-screen w-full flex flex-col relative" style={{ backgroundColor: 'transparent' }}> <div className="min-h-screen w-full flex flex-col relative" style={{ backgroundColor: 'transparent' }}>
<MatrixBackground /> <MatrixBackground />

View File

@@ -2,6 +2,7 @@ import { BaseApiClient } from './base';
import { API_CONFIG } from '../config/api'; import { API_CONFIG } from '../config/api';
import type { CandidateAssets, CandidateDetails, CandidateExpenses, CandidateIncome, CandidateRedesSociais, CandidateSearchResult, CpfRevealResult, OpenCandDataAvailabilityStats, OpenCandDatabaseStats, PlatformStats, RandomCandidate } from './apiModels'; import type { CandidateAssets, CandidateDetails, CandidateExpenses, CandidateIncome, CandidateRedesSociais, CandidateSearchResult, CpfRevealResult, OpenCandDataAvailabilityStats, OpenCandDatabaseStats, PlatformStats, RandomCandidate } from './apiModels';
import type { EnrichmentResponse, StatisticsConfig, ValueSumRequest, ValueSumResponse } from './apiStatisticsModels'; import type { EnrichmentResponse, StatisticsConfig, ValueSumRequest, ValueSumResponse } from './apiStatisticsModels';
import type { StatisticsRequestFilters, StatisticsRequestOptions } from '../components/StatisticsPage/statisticsRequests';
/** /**
* OpenCand API client for interacting with the OpenCand platform * OpenCand API client for interacting with the OpenCand platform
@@ -101,8 +102,28 @@ export class OpenCandApi extends BaseApiClient {
/** /**
* Get the enrichment statistics for candidates * Get the enrichment statistics for candidates
*/ */
async getStatisticsEnrichment(): Promise<EnrichmentResponse[]> { async getStatisticsEnrichment(filters?: StatisticsRequestFilters): Promise<EnrichmentResponse[]> {
return this.get<EnrichmentResponse[]>(`/v1/estatistica/enriquecimento`, { timeout: 90000 }); let url = `/v1/estatistica/enriquecimento`;
if (filters) {
const params = new URLSearchParams();
if (filters.partido !== null && filters.partido !== undefined) {
params.append('partido', filters.partido);
}
if (filters.uf !== null && filters.uf !== undefined) {
params.append('uf', filters.uf);
}
if (filters.ano !== null && filters.ano !== undefined) {
params.append('ano', String(filters.ano));
}
if (filters.cargo !== null && filters.cargo !== undefined) {
params.append('cargo', filters.cargo);
}
const queryString = params.toString();
if (queryString) {
url += `?${queryString}`;
}
}
return this.get<EnrichmentResponse[]>(url, { timeout: 90000 });
} }
/** /**

View File

@@ -1,8 +1,23 @@
import React from 'react'; import React, { useState } from 'react';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
import RandomCandButton from '../shared/RandomCandButton'; import RandomCandButton from '../shared/RandomCandButton';
const HeroSection: React.FC = () => { const HeroSection: React.FC = () => {
const headers = [
"Explore Dados Eleitorais",
"Analise Dados Eleitorais",
"Consulte Dados Eleitorais",
"Verifique Dados Eleitorais",
"Descubra Dados Eleitorais",
"Acesse Informações Eleitorais",
"Verifique Candidatos",
"Descubra Candidatos",
"Pesquise Candidaturas",
"Consulte Candidatos",
"Navegue pelos Dados do TSE"
];
const [header] = useState(headers[Math.floor(Math.random() * headers.length)]);
return ( return (
<section <section
className="min-h-screen flex flex-col justify-center items-center text-white bg-cover bg-center bg-no-repeat bg-gray-900 relative" className="min-h-screen flex flex-col justify-center items-center text-white bg-cover bg-center bg-no-repeat bg-gray-900 relative"
@@ -12,7 +27,7 @@ const HeroSection: React.FC = () => {
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-[#0a0f1a] to-transparent"></div> <div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-[#0a0f1a] to-transparent"></div>
<div className="relative z-10 text-center max-w-6xl"> <div className="relative z-10 text-center max-w-6xl">
<h1 className="text-5xl md:text-7xl font-bold mb-6"> <h1 className="text-5xl md:text-7xl font-bold mb-6">
Explore Dados Eleitorais {header}
</h1> </h1>
<p className="text-lg md:text-xl mb-10 text-gray-300"> <p className="text-lg md:text-xl mb-10 text-gray-300">
OpenCand oferece acesso fácil e visualizações intuitivas de dados abertos do Tribunal Superior Eleitoral (TSE). OpenCand oferece acesso fácil e visualizações intuitivas de dados abertos do Tribunal Superior Eleitoral (TSE).

View File

@@ -79,7 +79,7 @@ const StatisticsFilters: React.FC<StatisticsFiltersProps> = ({
{/* Party Filter */} {/* Party Filter */}
<div className="space-y-2"> <div className="space-y-2">
<label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide"> <label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide">
Partido (Opcional) Partido
</label> </label>
<select <select
value={localFilters.partido || ''} value={localFilters.partido || ''}
@@ -99,7 +99,7 @@ const StatisticsFilters: React.FC<StatisticsFiltersProps> = ({
{/* UF Filter */} {/* UF Filter */}
<div className="space-y-2"> <div className="space-y-2">
<label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide"> <label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide">
UF (Opcional) UF
</label> </label>
<select <select
value={localFilters.uf || ''} value={localFilters.uf || ''}
@@ -119,7 +119,7 @@ const StatisticsFilters: React.FC<StatisticsFiltersProps> = ({
{/* Year Filter */} {/* Year Filter */}
<div className="space-y-2"> <div className="space-y-2">
<label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide"> <label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide">
Ano (Opcional) Ano
</label> </label>
<select <select
value={localFilters.ano || ''} value={localFilters.ano || ''}
@@ -139,7 +139,7 @@ const StatisticsFilters: React.FC<StatisticsFiltersProps> = ({
{/* Cargo Filter */} {/* Cargo Filter */}
<div className="space-y-2"> <div className="space-y-2">
<label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide"> <label className="block text-sm font-semibold text-gray-600 uppercase tracking-wide">
Cargo (Opcional) Cargo
</label> </label>
<select <select
value={localFilters.cargo || ''} value={localFilters.cargo || ''}

View File

@@ -24,12 +24,14 @@ export interface StatisticsData {
} }
export interface StatisticsRequestOptions { export interface StatisticsRequestOptions {
filters?: { filters?: StatisticsRequestFilters;
partido?: string | null; }
uf?: string | null;
ano?: number | null; export interface StatisticsRequestFilters {
cargo?: CargoFilter; partido?: string | null;
}; uf?: string | null;
ano?: number | null;
cargo?: CargoFilter;
} }
// First Row Requests // First Row Requests
@@ -44,9 +46,9 @@ export async function getCandidatesWithMostAssets(options?: StatisticsRequestOpt
return Array.isArray(response) ? response : [response]; return Array.isArray(response) ? response : [response];
} }
export async function getEnrichmentData(): Promise<EnrichmentResponse[] | null> { export async function getEnrichmentData(filters?: StatisticsRequestFilters): Promise<EnrichmentResponse[] | null> {
try { try {
return await openCandApi.getStatisticsEnrichment(); return await openCandApi.getStatisticsEnrichment(filters);
} catch (error) { } catch (error) {
console.error('Error fetching enrichment data:', error); console.error('Error fetching enrichment data:', error);
return null; return null;
@@ -167,7 +169,7 @@ export async function fetchAllStatisticsData(options?: StatisticsRequestOptions)
statesWithMostRevenue statesWithMostRevenue
] = await Promise.all([ ] = await Promise.all([
getCandidatesWithMostAssets(options), getCandidatesWithMostAssets(options),
getEnrichmentData(), getEnrichmentData(options?.filters),
getCandidatesWithMostRevenue(options), getCandidatesWithMostRevenue(options),
getCandidatesWithMostExpenses(options), getCandidatesWithMostExpenses(options),
getPartiesWithMostAssets(options), getPartiesWithMostAssets(options),

View File

@@ -12,7 +12,14 @@ const firebaseConfig = {
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID
}; };
const app = initializeApp(firebaseConfig); let app = null;
const analytics = getAnalytics(app); let analytics = null;
if (firebaseConfig.apiKey) {
app = initializeApp(firebaseConfig);
analytics = getAnalytics(app);
} else {
console.warn("Firebase API key is not set. Firebase will not be initialized.");
}
export { app, analytics }; export { app, analytics };