Feat Widgets Cards y Optimización de Consultas
This commit is contained in:
		| @@ -1,96 +1,101 @@ | ||||
| // src/components/CandidatoOverridesManager.tsx | ||||
|  | ||||
| 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'; | ||||
| import { getProvinciasForAdmin, getMunicipiosForAdmin, getAgrupaciones, getCandidatos, updateCandidatos } from '../services/apiService'; | ||||
| import type { MunicipioSimple, AgrupacionPolitica, CandidatoOverride, ProvinciaSimple } from '../types'; | ||||
| import { CATEGORIAS_NACIONALES_OPTIONS, CATEGORIAS_PROVINCIALES_OPTIONS } from '../constants/categorias'; | ||||
|  | ||||
| const CATEGORIAS_OPTIONS = [ | ||||
|     { value: 5, label: 'Senadores' }, | ||||
|     { value: 6, label: 'Diputados' }, | ||||
|     { value: 7, label: 'Concejales' } | ||||
| const ELECCION_OPTIONS = [     | ||||
|     { value: 2, label: 'Elecciones Nacionales' }, | ||||
|     { value: 1, label: 'Elecciones Provinciales' } | ||||
| ]; | ||||
|  | ||||
| const AMBITO_LEVEL_OPTIONS = [ | ||||
|     { value: 'general', label: 'General (Toda la elección)' }, | ||||
|     { value: 'provincia', label: 'Por Provincia' }, | ||||
|     { value: 'municipio', label: 'Por Municipio' } | ||||
| ]; | ||||
|  | ||||
| export const CandidatoOverridesManager = () => { | ||||
|     const queryClient = useQueryClient(); | ||||
|     const { data: municipios = [] } = useQuery<MunicipioSimple[]>({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin }); | ||||
|     const { data: agrupaciones = [] } = useQuery<AgrupacionPolitica[]>({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones }); | ||||
|     const { data: candidatos = [] } = useQuery<CandidatoOverride[]>({ queryKey: ['candidatos'], queryFn: getCandidatos }); | ||||
|  | ||||
|     const [selectedEleccion, setSelectedEleccion] = useState(ELECCION_OPTIONS[0]); | ||||
|     const [selectedAmbitoLevel, setSelectedAmbitoLevel] = useState(AMBITO_LEVEL_OPTIONS[0]); | ||||
|     const [selectedProvincia, setSelectedProvincia] = useState<ProvinciaSimple | null>(null); | ||||
|     const [selectedMunicipio, setSelectedMunicipio] = useState<MunicipioSimple | null>(null); | ||||
|     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); | ||||
|     const [selectedAgrupacion, setSelectedAgrupacion] = useState<AgrupacionPolitica | null>(null); | ||||
|     const [nombreCandidato, setNombreCandidato] = useState(''); | ||||
|  | ||||
|     const municipioOptions = useMemo(() =>  | ||||
|         // Añadimos la opción "General" que representará un ámbito nulo | ||||
|         [{ value: 'general', label: 'General (Todos los Municipios)' }, ...municipios.map(m => ({ value: m.id, label: m.nombre }))] | ||||
|     , [municipios]); | ||||
|      | ||||
|     const agrupacionOptions = useMemo(() => agrupaciones.map(a => ({ value: a.id, label: a.nombre })), [agrupaciones]); | ||||
|     const { data: provincias = [] } = useQuery<ProvinciaSimple[]>({ queryKey: ['provinciasForAdmin'], queryFn: getProvinciasForAdmin }); | ||||
|     const { data: municipios = [] } = useQuery<MunicipioSimple[]>({ queryKey: ['municipiosForAdmin'], queryFn: getMunicipiosForAdmin }); | ||||
|     const { data: agrupaciones = [] } = useQuery<AgrupacionPolitica[]>({ queryKey: ['agrupaciones'], queryFn: getAgrupaciones }); | ||||
|     const { data: candidatos = [] } = useQuery<CandidatoOverride[]>({ | ||||
|         queryKey: ['candidatos', selectedEleccion.value], | ||||
|         queryFn: () => getCandidatos(selectedEleccion.value), | ||||
|     }); | ||||
|  | ||||
|     const categoriaOptions = selectedEleccion.value === 2 ? CATEGORIAS_NACIONALES_OPTIONS : CATEGORIAS_PROVINCIALES_OPTIONS; | ||||
|  | ||||
|     const getAmbitoId = () => { | ||||
|         if (selectedAmbitoLevel.value === 'municipio' && selectedMunicipio) return parseInt(selectedMunicipio.id); | ||||
|         if (selectedAmbitoLevel.value === 'provincia' && selectedProvincia) return parseInt(selectedProvincia.id); | ||||
|         return null; | ||||
|     }; | ||||
|  | ||||
|     const currentCandidato = useMemo(() => { | ||||
|         if (!selectedAgrupacion || !selectedCategoria) return ''; | ||||
|          | ||||
|         // Determina si estamos buscando un override general (null) o específico (ID numérico) | ||||
|         const ambitoIdBuscado = selectedMunicipio?.value === 'general' ? null : (selectedMunicipio ? parseInt(selectedMunicipio.value) : undefined); | ||||
|  | ||||
|         // Si no se ha seleccionado un municipio, no buscamos nada | ||||
|         if (ambitoIdBuscado === undefined) return ''; | ||||
|  | ||||
|         return candidatos.find(c =>  | ||||
|             c.ambitoGeograficoId === ambitoIdBuscado &&  | ||||
|             c.agrupacionPoliticaId === selectedAgrupacion.value && | ||||
|         const ambitoId = getAmbitoId(); | ||||
|         return candidatos.find(c => | ||||
|             c.ambitoGeograficoId === ambitoId && | ||||
|             c.agrupacionPoliticaId === selectedAgrupacion.id && | ||||
|             c.categoriaId === selectedCategoria.value | ||||
|         )?.nombreCandidato || ''; | ||||
|     }, [candidatos, selectedMunicipio, selectedAgrupacion, selectedCategoria]); | ||||
|      | ||||
|     useEffect(() => { setNombreCandidato(currentCandidato) }, [currentCandidato]); | ||||
|     }, [candidatos, selectedAmbitoLevel, selectedProvincia, selectedMunicipio, selectedAgrupacion, selectedCategoria]); | ||||
|  | ||||
|     useEffect(() => { setNombreCandidato(currentCandidato || ''); }, [currentCandidato]); | ||||
|  | ||||
|     const handleSave = async () => { | ||||
|         if (!selectedMunicipio || !selectedAgrupacion || !selectedCategoria) return; | ||||
|  | ||||
|         const ambitoIdParaEnviar = selectedMunicipio.value === 'general'  | ||||
|             ? null  | ||||
|             : parseInt(selectedMunicipio.value); | ||||
|  | ||||
|         if (!selectedAgrupacion || !selectedCategoria) return; | ||||
|         const newCandidatoEntry: CandidatoOverride = { | ||||
|             id: 0, // El backend no lo necesita para el upsert | ||||
|             agrupacionPoliticaId: selectedAgrupacion.value, | ||||
|             id: 0, | ||||
|             eleccionId: selectedEleccion.value, | ||||
|             agrupacionPoliticaId: selectedAgrupacion.id, | ||||
|             categoriaId: selectedCategoria.value, | ||||
|             ambitoGeograficoId: ambitoIdParaEnviar, | ||||
|             ambitoGeograficoId: getAmbitoId(), | ||||
|             nombreCandidato: nombreCandidato || null | ||||
|         }; | ||||
|  | ||||
|         try { | ||||
|             await updateCandidatos([newCandidatoEntry]); | ||||
|             queryClient.invalidateQueries({ queryKey: ['candidatos'] }); | ||||
|             queryClient.invalidateQueries({ queryKey: ['candidatos', selectedEleccion.value] }); | ||||
|             alert('Override de candidato guardado.'); | ||||
|         } catch (error) { | ||||
|             console.error(error); | ||||
|             alert('Error al guardar el override del candidato.'); | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|  | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Overrides de Nombres de Candidatos</h3> | ||||
|             <p>Configure un nombre de candidato específico para un partido, categoría y municipio (o general).</p> | ||||
|             <div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end', flexWrap: 'wrap' }}> | ||||
|             <p>Configure un nombre de candidato específico para un partido en un contexto determinado.</p> | ||||
|             <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem', alignItems: 'flex-end' }}> | ||||
|                 <Select options={ELECCION_OPTIONS} value={selectedEleccion} onChange={(opt) => { setSelectedEleccion(opt!); setSelectedCategoria(null); }} /> | ||||
|                 <Select options={categoriaOptions} value={selectedCategoria} onChange={setSelectedCategoria} placeholder="Seleccione Categoría..." isDisabled={!selectedEleccion} /> | ||||
|                 <Select options={agrupaciones.map(a => ({ value: a.id, label: a.nombre, ...a }))} getOptionValue={opt => opt.id} getOptionLabel={opt => opt.nombre} value={selectedAgrupacion} onChange={setSelectedAgrupacion} placeholder="Seleccione Agrupación..." /> | ||||
|                 <Select options={AMBITO_LEVEL_OPTIONS} value={selectedAmbitoLevel} onChange={(opt) => { setSelectedAmbitoLevel(opt!); setSelectedProvincia(null); setSelectedMunicipio(null); }} /> | ||||
|  | ||||
|                 {selectedAmbitoLevel.value === 'provincia' || selectedAmbitoLevel.value === 'municipio' ? ( | ||||
|                     <Select options={provincias.map(p => ({ value: p.id, label: p.nombre, ...p }))} getOptionValue={opt => opt.id} getOptionLabel={opt => opt.nombre} value={selectedProvincia} onChange={setSelectedProvincia} placeholder="Seleccione Provincia..." /> | ||||
|                 ) : <div />} | ||||
|  | ||||
|                 {selectedAmbitoLevel.value === 'municipio' ? ( | ||||
|                     <Select options={municipios.map(m => ({ value: m.id, label: m.nombre, ...m }))} getOptionValue={opt => opt.id} getOptionLabel={opt => opt.nombre} value={selectedMunicipio} onChange={setSelectedMunicipio} placeholder="Seleccione Municipio..." isDisabled={!selectedProvincia} /> | ||||
|                 ) : <div />} | ||||
|             </div> | ||||
|             <div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end', marginTop: '1rem' }}> | ||||
|                 <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 (Opcional)</label> | ||||
|                     <Select options={municipioOptions} value={selectedMunicipio} onChange={setSelectedMunicipio} isClearable placeholder="General..."/> | ||||
|                 </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={!selectedAgrupacion || !selectedCategoria} /> | ||||
|                 </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user