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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user