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 { Routes, Route } from 'react-router-dom';
import React, { useEffect } from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';
import Navbar from './components/Navbar';
import HeroSection from './components/HeroSection';
import StatisticsSection from './components/StatisticsSection';
@@ -14,17 +14,42 @@ import MatrixBackground from './components/MatrixBackground';
import './App.css';
// HomePage component
const HomePage: React.FC = () => (
<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>
);
const HomePage: React.FC = () => {
const location = useLocation();
useEffect(() => {
if (location.hash) {
const element = document.getElementById(location.hash.substring(1));
if (element) {
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() {
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 (
<div className="min-h-screen w-full flex flex-col relative" style={{ backgroundColor: 'transparent' }}>
<MatrixBackground />

View File

@@ -2,6 +2,7 @@ import { BaseApiClient } from './base';
import { API_CONFIG } from '../config/api';
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 { StatisticsRequestFilters, StatisticsRequestOptions } from '../components/StatisticsPage/statisticsRequests';
/**
* OpenCand API client for interacting with the OpenCand platform
@@ -101,8 +102,28 @@ export class OpenCandApi extends BaseApiClient {
/**
* Get the enrichment statistics for candidates
*/
async getStatisticsEnrichment(): Promise<EnrichmentResponse[]> {
return this.get<EnrichmentResponse[]>(`/v1/estatistica/enriquecimento`, { timeout: 90000 });
async getStatisticsEnrichment(filters?: StatisticsRequestFilters): Promise<EnrichmentResponse[]> {
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 RandomCandButton from '../shared/RandomCandButton';
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 (
<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"
@@ -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="relative z-10 text-center max-w-6xl">
<h1 className="text-5xl md:text-7xl font-bold mb-6">
Explore Dados Eleitorais
{header}
</h1>
<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).

View File

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

View File

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

View File

@@ -12,7 +12,14 @@ const firebaseConfig = {
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID
};
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
let app = null;
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 };