Feat Widgets
Se añade la tabla CandidatosOverrides Se añade el Overrides de Candidatos al panel de administrador Se Añade el nombre de los candidatos a los Widgets de categorias por municipio
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getMunicipiosForAdmin, getAgrupaciones, getCandidatos, updateCandidatos } from '../services/apiService';
|
||||
import type { MunicipioSimple, AgrupacionPolitica, CandidatoOverride } from '../types';
|
||||
|
||||
// Las categorías son las mismas que para los logos
|
||||
const CATEGORIAS_OPTIONS = [
|
||||
{ value: 5, label: 'Senadores' },
|
||||
{ value: 6, label: 'Diputados' },
|
||||
{ value: 7, label: 'Concejales' }
|
||||
];
|
||||
|
||||
export const CandidatoOverridesManager = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: municipios = [] } = useQuery<MunicipioSimple[]>({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin });
|
||||
const { data: agrupaciones = [] } = useQuery<AgrupacionPolitica[]>({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones });
|
||||
// --- Usar la query para candidatos ---
|
||||
const { data: candidatos = [] } = useQuery<CandidatoOverride[]>({ queryKey: ['candidatos'], queryFn: getCandidatos });
|
||||
|
||||
const [selectedCategoria, setSelectedCategoria] = useState<{ value: number; label: string } | null>(null);
|
||||
const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null);
|
||||
const [selectedAgrupacion, setSelectedAgrupacion] = useState<{ value: string; label: string } | null>(null);
|
||||
// --- El estado es para el nombre del candidato ---
|
||||
const [nombreCandidato, setNombreCandidato] = useState('');
|
||||
|
||||
const municipioOptions = useMemo(() => municipios.map(m => ({ value: m.id, label: m.nombre })), [municipios]);
|
||||
const agrupacionOptions = useMemo(() => agrupaciones.map(a => ({ value: a.id, label: a.nombre })), [agrupaciones]);
|
||||
|
||||
// --- Lógica para encontrar el nombre del candidato actual ---
|
||||
const currentCandidato = useMemo(() => {
|
||||
if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return '';
|
||||
return candidatos.find(c =>
|
||||
c.ambitoGeograficoId === parseInt(selectedMunicipio.value) &&
|
||||
c.agrupacionPoliticaId === selectedAgrupacion.value &&
|
||||
c.categoriaId === selectedCategoria.value
|
||||
)?.nombreCandidato || '';
|
||||
}, [candidatos, selectedMunicipio, selectedAgrupacion, selectedCategoria]);
|
||||
|
||||
useEffect(() => { setNombreCandidato(currentCandidato) }, [currentCandidato]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return;
|
||||
|
||||
// --- Construir el objeto CandidatoOverride ---
|
||||
const newCandidatoEntry: CandidatoOverride = {
|
||||
id: 0, // El backend no necesita el ID para un upsert
|
||||
agrupacionPoliticaId: selectedAgrupacion.value,
|
||||
categoriaId: selectedCategoria.value,
|
||||
ambitoGeograficoId: parseInt(selectedMunicipio.value),
|
||||
nombreCandidato: nombreCandidato
|
||||
};
|
||||
|
||||
try {
|
||||
await updateCandidatos([newCandidatoEntry]);
|
||||
queryClient.invalidateQueries({ queryKey: ['candidatos'] });
|
||||
alert('Override de candidato guardado.');
|
||||
} catch { alert('Error al guardar.'); }
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="admin-module">
|
||||
<h3>Overrides de Nombres de Candidatos</h3>
|
||||
<p>Configure un nombre de candidato específico para un partido en un municipio y categoría determinados.</p>
|
||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label>Categoría</label>
|
||||
<Select options={CATEGORIAS_OPTIONS} value={selectedCategoria} onChange={setSelectedCategoria} isClearable placeholder="Seleccione..."/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label>Municipio</label>
|
||||
<Select options={municipioOptions} value={selectedMunicipio} onChange={setSelectedMunicipio} isClearable placeholder="Seleccione..."/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label>Agrupación</label>
|
||||
<Select options={agrupacionOptions} value={selectedAgrupacion} onChange={setSelectedAgrupacion} isClearable placeholder="Seleccione..."/>
|
||||
</div>
|
||||
<div style={{ flex: 2 }}>
|
||||
<label>Nombre del Candidato</label>
|
||||
<input type="text" value={nombreCandidato} onChange={e => setNombreCandidato(e.target.value)} style={{ width: '100%' }} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria} />
|
||||
</div>
|
||||
<button onClick={handleSave} disabled={!selectedMunicipio || !selectedAgrupacion || !selectedCategoria}>Guardar</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { OrdenSenadoresManager } from './OrdenSenadoresManager';
|
||||
import { ConfiguracionGeneral } from './ConfiguracionGeneral';
|
||||
import { BancasManager } from './BancasManager';
|
||||
import { LogoOverridesManager } from './LogoOverridesManager';
|
||||
import { CandidatoOverridesManager } from './CandidatoOverridesManager';
|
||||
|
||||
export const DashboardPage = () => {
|
||||
const { logout } = useAuth();
|
||||
@@ -16,9 +17,14 @@ export const DashboardPage = () => {
|
||||
<h1>Panel de Administración Electoral</h1>
|
||||
<button onClick={logout}>Cerrar Sesión</button>
|
||||
</header>
|
||||
<main style={{ marginTop: '2rem' }}>
|
||||
<main style={{ marginTop: '2rem' }}>
|
||||
<AgrupacionesManager />
|
||||
<LogoOverridesManager />
|
||||
<div style={{ flex: '1 1 800px' }}>
|
||||
<LogoOverridesManager />
|
||||
</div>
|
||||
<div style={{ flex: '1 1 800px' }}>
|
||||
<CandidatoOverridesManager />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap', marginTop: '2rem' }}>
|
||||
<div style={{ flex: '1 1 400px' }}>
|
||||
<OrdenDiputadosManager />
|
||||
@@ -27,7 +33,7 @@ export const DashboardPage = () => {
|
||||
<OrdenSenadoresManager />
|
||||
</div>
|
||||
</div>
|
||||
<ConfiguracionGeneral />
|
||||
<ConfiguracionGeneral />
|
||||
<BancasManager />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/services/apiService.ts
|
||||
import axios from 'axios';
|
||||
import { triggerLogout } from '../context/authUtils';
|
||||
import type { AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria, MunicipioSimple } from '../types';
|
||||
import type { CandidatoOverride, AgrupacionPolitica, UpdateAgrupacionData, Bancada, LogoAgrupacionCategoria, MunicipioSimple } from '../types';
|
||||
|
||||
/**
|
||||
* URL base para las llamadas a la API.
|
||||
@@ -125,4 +125,14 @@ export const getMunicipiosForAdmin = async (): Promise<MunicipioSimple[]> => {
|
||||
// La URL final será /api/admin/catalogos/municipios
|
||||
const response = await adminApiClient.get('/catalogos/municipios');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// 6. Overrides de Candidatos
|
||||
export const getCandidatos = async (): Promise<CandidatoOverride[]> => {
|
||||
const response = await adminApiClient.get('/candidatos');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateCandidatos = async (data: CandidatoOverride[]): Promise<void> => {
|
||||
await adminApiClient.put('/candidatos', data);
|
||||
};
|
||||
@@ -48,4 +48,12 @@ export interface LogoAgrupacionCategoria {
|
||||
ambitoGeograficoId: number | null;
|
||||
}
|
||||
|
||||
export interface MunicipioSimple { id: string; nombre: string; }
|
||||
export interface MunicipioSimple { id: string; nombre: string; }
|
||||
|
||||
export interface CandidatoOverride {
|
||||
id: number;
|
||||
agrupacionPoliticaId: string;
|
||||
categoriaId: number;
|
||||
ambitoGeograficoId: number | null;
|
||||
nombreCandidato: string;
|
||||
}
|
||||
Reference in New Issue
Block a user