Fix Tabla Columnas
This commit is contained in:
		| @@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'; | |||||||
| import Select from 'react-select'; | import Select from 'react-select'; | ||||||
| import { getSeccionesElectorales, getRankingMunicipiosPorSeccion } from '../apiService'; | import { getSeccionesElectorales, getRankingMunicipiosPorSeccion } from '../apiService'; | ||||||
| import type { MunicipioSimple, ApiResponseRankingMunicipio, RankingPartido } from '../types/types'; | import type { MunicipioSimple, ApiResponseRankingMunicipio, RankingPartido } from '../types/types'; | ||||||
| import './ResultadosTablaSeccionWidget.css';  | import './ResultadosTablaSeccionWidget.css'; | ||||||
|  |  | ||||||
| type DisplayMode = 'porcentaje' | 'votos' | 'ambos'; | type DisplayMode = 'porcentaje' | 'votos' | 'ambos'; | ||||||
| type DisplayOption = { | type DisplayOption = { | ||||||
| @@ -18,8 +18,8 @@ const displayModeOptions: readonly DisplayOption[] = [ | |||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const customSelectStyles = { | const customSelectStyles = { | ||||||
|   control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }), |     control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }), | ||||||
|   menu: (base: any) => ({ ...base, zIndex: 10 }), |     menu: (base: any) => ({ ...base, zIndex: 10 }), | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const formatPercent = (porcentaje: number) => `${porcentaje.toFixed(2).replace('.', ',')}%`; | const formatPercent = (porcentaje: number) => `${porcentaje.toFixed(2).replace('.', ',')}%`; | ||||||
| @@ -50,41 +50,41 @@ const CellRenderer = ({ partido, mode }: { partido?: RankingPartido, mode: Displ | |||||||
|  |  | ||||||
|  |  | ||||||
| export const ResultadosRankingMunicipioWidget = () => { | export const ResultadosRankingMunicipioWidget = () => { | ||||||
|   const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); |     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||||
|   const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null); |     const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null); | ||||||
|   const [displayMode, setDisplayMode] = useState<DisplayOption>(displayModeOptions[0]); |     const [displayMode, setDisplayMode] = useState<DisplayOption>(displayModeOptions[0]); | ||||||
|  |  | ||||||
|   useEffect(() => { |     useEffect(() => { | ||||||
|     const fetchSecciones = async () => { |         const fetchSecciones = async () => { | ||||||
|       const seccionesData = await getSeccionesElectorales(); |             const seccionesData = await getSeccionesElectorales(); | ||||||
|       if (seccionesData && seccionesData.length > 0) { |             if (seccionesData && seccionesData.length > 0) { | ||||||
|         const orden = new Map([ |                 const orden = new Map([ | ||||||
|           ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], |                     ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], | ||||||
|           ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] |                     ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] | ||||||
|         ]); |                 ]); | ||||||
|         const getOrden = (nombre: string) => { |                 const getOrden = (nombre: string) => { | ||||||
|           const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); |                     const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); | ||||||
|           return match ? orden.get(match[0]) ?? 99 : 99; |                     return match ? orden.get(match[0]) ?? 99 : 99; | ||||||
|  |                 }; | ||||||
|  |                 seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | ||||||
|  |                 setSecciones(seccionesData); | ||||||
|  |                 if (!selectedSeccion) { | ||||||
|  |                     setSelectedSeccion({ value: seccionesData[0].id, label: seccionesData[0].nombre }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }; |         }; | ||||||
|         seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); |         fetchSecciones(); | ||||||
|         setSecciones(seccionesData); |     }, [selectedSeccion]); | ||||||
|         if (!selectedSeccion) { |  | ||||||
|           setSelectedSeccion({ value: seccionesData[0].id, label: seccionesData[0].nombre }); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|     fetchSecciones(); |  | ||||||
|   }, [selectedSeccion]); |  | ||||||
|  |  | ||||||
|   const seccionOptions = useMemo(() => secciones.map(s => ({ value: s.id, label: s.nombre })), [secciones]); |     const seccionOptions = useMemo(() => secciones.map(s => ({ value: s.id, label: s.nombre })), [secciones]); | ||||||
|  |  | ||||||
|   const { data: rankingData, isLoading } = useQuery<ApiResponseRankingMunicipio>({ |     const { data: rankingData, isLoading } = useQuery<ApiResponseRankingMunicipio>({ | ||||||
|     queryKey: ['rankingMunicipiosPorSeccion', selectedSeccion?.value], |         queryKey: ['rankingMunicipiosPorSeccion', selectedSeccion?.value], | ||||||
|     queryFn: () => getRankingMunicipiosPorSeccion(selectedSeccion!.value), |         queryFn: () => getRankingMunicipiosPorSeccion(selectedSeccion!.value), | ||||||
|     enabled: !!selectedSeccion, |         enabled: !!selectedSeccion, | ||||||
|   }); |     }); | ||||||
|  |  | ||||||
|   return ( |     return ( | ||||||
|         <div className="tabla-resultados-widget"> |         <div className="tabla-resultados-widget"> | ||||||
|             <div className="tabla-header"> |             <div className="tabla-header"> | ||||||
|                 <h3>Resultados por Municipio</h3> |                 <h3>Resultados por Municipio</h3> | ||||||
| @@ -92,11 +92,7 @@ export const ResultadosRankingMunicipioWidget = () => { | |||||||
|                     <Select |                     <Select | ||||||
|                         options={displayModeOptions} |                         options={displayModeOptions} | ||||||
|                         value={displayMode} |                         value={displayMode} | ||||||
|                          |  | ||||||
|                         // --- CORRECCIÓN EN ONCHANGE --- |  | ||||||
|                         // 'option' ahora es del tipo correcto, por lo que no necesitamos aserción. |  | ||||||
|                         onChange={(option) => setDisplayMode(option as DisplayOption)} |                         onChange={(option) => setDisplayMode(option as DisplayOption)} | ||||||
|                          |  | ||||||
|                         styles={customSelectStyles} |                         styles={customSelectStyles} | ||||||
|                         isSearchable={false} |                         isSearchable={false} | ||||||
|                     /> |                     /> | ||||||
| @@ -115,16 +111,29 @@ export const ResultadosRankingMunicipioWidget = () => { | |||||||
|                 {isLoading ? <p>Cargando...</p> : !rankingData || rankingData.categorias.length === 0 ? <p>No hay datos.</p> : ( |                 {isLoading ? <p>Cargando...</p> : !rankingData || rankingData.categorias.length === 0 ? <p>No hay datos.</p> : ( | ||||||
|                     <table> |                     <table> | ||||||
|                         <thead> |                         <thead> | ||||||
|  |                             {/* --- Fila 1: Nombres de Categorías --- */} | ||||||
|                             <tr> |                             <tr> | ||||||
|                                 <th rowSpan={2} className="sticky-col municipio-header">Municipio</th> |                                 <th rowSpan={3} className="sticky-col municipio-header">Municipio</th> | ||||||
|                                 {rankingData.categorias.map(cat => ( |                                 {rankingData.categorias.map(cat => ( | ||||||
|                                     <th key={cat.id} colSpan={2} className="categoria-header">{cat.nombre}</th> |                                     <th key={cat.id} colSpan={4} className="categoria-header"> | ||||||
|  |                                         {cat.nombre} | ||||||
|  |                                     </th> | ||||||
|                                 ))} |                                 ))} | ||||||
|                             </tr> |                             </tr> | ||||||
|  |                             {/* --- Fila 2: Puestos --- */} | ||||||
|                             <tr> |                             <tr> | ||||||
|                                 {rankingData.categorias.flatMap(cat => [ |                                 {rankingData.categorias.flatMap(cat => [ | ||||||
|                                     <th key={`${cat.id}-1`} className="partido-header">1° Puesto</th>, |                                     <th key={`${cat.id}-p1`} colSpan={2} className="puesto-header">1° Puesto</th>, | ||||||
|                                     <th key={`${cat.id}-2`} className="partido-header category-divider-header">2° Puesto</th> |                                     <th key={`${cat.id}-p2`} colSpan={2} className="puesto-header category-divider-header">2° Puesto</th> | ||||||
|  |                                 ])} | ||||||
|  |                             </tr> | ||||||
|  |                             {/* --- Fila 3: Sub-cabeceras (Partido y %) --- */} | ||||||
|  |                             <tr> | ||||||
|  |                                 {rankingData.categorias.flatMap(cat => [ | ||||||
|  |                                     <th key={`${cat.id}-p1-partido`} className="sub-header">Partido</th>, | ||||||
|  |                                     <th key={`${cat.id}-p1-porc`} className="sub-header">%</th>, | ||||||
|  |                                     <th key={`${cat.id}-p2-partido`} className="sub-header category-divider-header">Partido</th>, | ||||||
|  |                                     <th key={`${cat.id}-p2-porc`} className="sub-header">%</th> | ||||||
|                                 ])} |                                 ])} | ||||||
|                             </tr> |                             </tr> | ||||||
|                         </thead> |                         </thead> | ||||||
| @@ -138,17 +147,20 @@ export const ResultadosRankingMunicipioWidget = () => { | |||||||
|                                         const segundoPuesto = resCategoria?.ranking[1]; |                                         const segundoPuesto = resCategoria?.ranking[1]; | ||||||
|  |  | ||||||
|                                         return [ |                                         return [ | ||||||
|                                             <td key={`${municipio.municipioId}-${cat.id}-1`} className="category-divider"> |                                             // --- Celdas para el 1° Puesto --- | ||||||
|                                                 <div className="cell-content"> |                                             <td key={`${municipio.municipioId}-${cat.id}-p1-partido`} className="cell-partido"> | ||||||
|                                                     <span className="cell-partido-nombre">{primerPuesto?.nombreCorto || '-'}</span> |                                                 {primerPuesto?.nombreCorto || '-'} | ||||||
|                                                     <CellRenderer partido={primerPuesto} mode={displayMode.value} /> |  | ||||||
|                                                 </div> |  | ||||||
|                                             </td>, |                                             </td>, | ||||||
|                                             <td key={`${municipio.municipioId}-${cat.id}-2`}> |                                             <td key={`${municipio.municipioId}-${cat.id}-p1-porc`} className="cell-porcentaje"> | ||||||
|                                                 <div className="cell-content"> |                                                 {primerPuesto ? <CellRenderer partido={primerPuesto} mode={displayMode.value} /> : '-'} | ||||||
|                                                     <span className="cell-partido-nombre">{segundoPuesto?.nombreCorto || '-'}</span> |                                             </td>, | ||||||
|                                                     <CellRenderer partido={segundoPuesto} mode={displayMode.value} /> |  | ||||||
|                                                 </div> |                                             // --- Celdas para el 2° Puesto --- | ||||||
|  |                                             <td key={`${municipio.municipioId}-${cat.id}-p2-partido`} className="cell-partido category-divider"> | ||||||
|  |                                                 {segundoPuesto?.nombreCorto || '-'} | ||||||
|  |                                             </td>, | ||||||
|  |                                             <td key={`${municipio.municipioId}-${cat.id}-p2-porc`} className="cell-porcentaje"> | ||||||
|  |                                                 {segundoPuesto ? <CellRenderer partido={segundoPuesto} mode={displayMode.value} /> : '-'} | ||||||
|                                             </td> |                                             </td> | ||||||
|                                         ]; |                                         ]; | ||||||
|                                     })} |                                     })} | ||||||
|   | |||||||
| @@ -106,12 +106,14 @@ | |||||||
| .tabla-container .cell-partido { | .tabla-container .cell-partido { | ||||||
|     text-align: left; |     text-align: left; | ||||||
|     font-size: 0.85rem; |     font-size: 0.85rem; | ||||||
|  |     padding-left: 15px; /* Añade un poco de espacio a la izquierda */ | ||||||
|  |     border-right: 1px solid #e9ecef; /* Línea fina entre partido y % */ | ||||||
| } | } | ||||||
| .tabla-container .cell-porcentaje { | .tabla-container .cell-porcentaje { | ||||||
|     text-align: right; |     text-align: right; | ||||||
|     font-family: 'Roboto Mono', 'Courier New', monospace; |     font-family: 'Roboto Mono', 'Courier New', monospace; | ||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
|     border-right: 1px solid #dee2e6; |     padding-right: 15px; /* Añade un poco de espacio a la derecha */ | ||||||
| } | } | ||||||
|  |  | ||||||
| .tabla-container th.sub-header-init { | .tabla-container th.sub-header-init { | ||||||
| @@ -123,6 +125,10 @@ | |||||||
|     border-left: 2px solid #adb5bd; |     border-left: 2px solid #adb5bd; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .tabla-container .category-divider { | ||||||
|  |     border-left: 2px solid #adb5bd; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Columna fija de Municipio */ | /* Columna fija de Municipio */ | ||||||
| .tabla-container th.sticky-col, | .tabla-container th.sticky-col, | ||||||
| .tabla-container td.sticky-col { | .tabla-container td.sticky-col { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user