Feat Se añade Id de Agrupaciones en Componentes
This commit is contained in:
		| @@ -123,7 +123,7 @@ export const AgrupacionesManager = () => { | ||||
|                             <tbody> | ||||
|                                 {agrupaciones.map(agrupacion => ( | ||||
|                                     <tr key={agrupacion.id}> | ||||
|                                         <td>{agrupacion.nombre}</td> | ||||
|                                         <td>({agrupacion.id}) {agrupacion.nombre}</td> | ||||
|                                         <td><input type="text" value={editedAgrupaciones[agrupacion.id]?.nombreCorto ?? ''} onChange={(e) => handleInputChange(agrupacion.id, 'nombreCorto', e.target.value)} /></td> | ||||
|                                         <td><input type="color" value={sanitizeColor(editedAgrupaciones[agrupacion.id]?.color)} onChange={(e) => handleInputChange(agrupacion.id, 'color', e.target.value)} /></td> | ||||
|                                         <td> | ||||
|   | ||||
| @@ -89,7 +89,7 @@ export const BancasNacionalesManager = () => { | ||||
|                       onChange={(e) => handleAgrupacionChange(bancada.id, e.target.value || null)} | ||||
|                     > | ||||
|                       <option value="">-- Vacante --</option> | ||||
|                       {agrupaciones.map(a => <option key={a.id} value={a.id}>{a.nombre}</option>)} | ||||
|                       {agrupaciones.map(a => <option key={a.id} value={a.id}>{`(${a.id}) ${a.nombre}`}</option>)} | ||||
|                     </select> | ||||
|                   </td> | ||||
|                   <td>{bancada.ocupante?.nombreOcupante || 'Sin asignar'}</td> | ||||
|   | ||||
| @@ -95,7 +95,7 @@ export const BancasPreviasManager = () => { | ||||
|                             <tbody> | ||||
|                                 {agrupaciones.map(agrupacion => ( | ||||
|                                     <tr key={agrupacion.id}> | ||||
|                                         <td>{agrupacion.nombre}</td> | ||||
|                                         <td>({agrupacion.id}) {agrupacion.nombre}</td> | ||||
|                                         <td> | ||||
|                                             <input | ||||
|                                                 type="number" | ||||
|   | ||||
| @@ -91,7 +91,7 @@ export const BancasProvincialesManager = () => { | ||||
|                     onChange={(e) => handleAgrupacionChange(bancada.id, e.target.value || null)} | ||||
|                   > | ||||
|                     <option value="">-- Vacante --</option> | ||||
|                     {agrupaciones.map(a => <option key={a.id} value={a.id}>{a.nombre}</option>)} | ||||
|                     {agrupaciones.map(a => <option key={a.id} value={a.id}>{`(${a.id}) ${a.nombre}`}</option>)} | ||||
|                   </select> | ||||
|                 </td> | ||||
|                 <td>{bancada.ocupante?.nombreOcupante || 'Sin asignar'}</td> | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import { getProvinciasForAdmin, getMunicipiosForAdmin, getAgrupaciones, getCandi | ||||
| import type { MunicipioSimple, AgrupacionPolitica, CandidatoOverride, ProvinciaSimple } from '../types'; | ||||
| import { CATEGORIAS_NACIONALES_OPTIONS, CATEGORIAS_PROVINCIALES_OPTIONS } from '../constants/categorias'; | ||||
|  | ||||
| const ELECCION_OPTIONS = [     | ||||
| const ELECCION_OPTIONS = [ | ||||
|     { value: 2, label: 'Elecciones Nacionales' }, | ||||
|     { value: 1, label: 'Elecciones Provinciales' } | ||||
| ]; | ||||
| @@ -83,7 +83,14 @@ export const CandidatoOverridesManager = () => { | ||||
|             <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={agrupaciones.map(a => ({ value: a.id, label: a.nombre, ...a }))} | ||||
|                     getOptionValue={opt => opt.id} | ||||
|                     getOptionLabel={opt => `(${opt.id}) ${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' ? ( | ||||
|   | ||||
| @@ -9,7 +9,7 @@ export const ConfiguracionNacional = () => { | ||||
|     const queryClient = useQueryClient(); | ||||
|     const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|     const [loading, setLoading] = useState(true); | ||||
|      | ||||
|  | ||||
|     const [presidenciaDiputadosId, setPresidenciaDiputadosId] = useState<string>(''); | ||||
|     const [presidenciaSenadoId, setPresidenciaSenadoId] = useState<string>(''); | ||||
|     const [modoOficialActivo, setModoOficialActivo] = useState(false); | ||||
| @@ -30,7 +30,7 @@ export const ConfiguracionNacional = () => { | ||||
|                 setModoOficialActivo(configData.UsarDatosOficialesNacionales === 'true'); | ||||
|                 setDiputadosTipoBanca(configData.PresidenciaDiputadosNacional_TipoBanca === 'previa' ? 'previa' : 'ganada'); | ||||
|                 setSenadoTipoBanca(configData.PresidenciaSenadoNacional_TipoBanca === 'previa' ? 'previa' : 'ganada'); | ||||
|             } catch (err) { console.error("Error al cargar datos de configuración nacional:", err); }  | ||||
|             } catch (err) { console.error("Error al cargar datos de configuración nacional:", err); } | ||||
|             finally { setLoading(false); } | ||||
|         }; | ||||
|         loadInitialData(); | ||||
| @@ -56,16 +56,7 @@ export const ConfiguracionNacional = () => { | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Configuración de Widgets Nacionales</h3> | ||||
|             {/*<div className="form-group"> | ||||
|                 <label> | ||||
|                     <input type="checkbox" checked={modoOficialActivo} onChange={e => setModoOficialActivo(e.target.checked)} /> | ||||
|                     **Activar Modo "Resultados Oficiales" para Widgets Nacionales** | ||||
|                 </label> | ||||
|                 <p style={{ fontSize: '0.8rem', color: '#666' }}> | ||||
|                     Si está activo, los widgets nacionales usarán la composición manual de bancas. Si no, usarán la proyección en tiempo real. | ||||
|                 </p> | ||||
|             </div>*/} | ||||
|              | ||||
|  | ||||
|             <div style={{ display: 'flex', gap: '2rem', marginTop: '1rem' }}> | ||||
|                 {/* Columna Diputados */} | ||||
|                 <div style={{ flex: 1, borderRight: '1px solid #ccc', paddingRight: '1rem' }}> | ||||
| @@ -77,14 +68,14 @@ export const ConfiguracionNacional = () => { | ||||
|                     </p> | ||||
|                     <select id="presidencia-diputados-nacional" value={presidenciaDiputadosId} onChange={e => setPresidenciaDiputadosId(e.target.value)} style={{ width: '100%', padding: '8px', marginBottom: '0.5rem' }}> | ||||
|                         <option value="">-- No Asignado --</option> | ||||
|                         {agrupaciones.map(a => (<option key={a.id} value={a.id}>{a.nombre}</option>))} | ||||
|                         {agrupaciones.map(a => (<option key={a.id} value={a.id}>{`(${a.id}) ${a.nombre}`}</option>))} | ||||
|                     </select> | ||||
|                     {presidenciaDiputadosId && ( | ||||
|                         <div> | ||||
|                             <label><input type="radio" value="ganada" checked={diputadosTipoBanca === 'ganada'} onChange={() => setDiputadosTipoBanca('ganada')} /> Descontar de Banca Ganada</label> | ||||
|                             <label style={{marginLeft: '1rem'}}><input type="radio" value="previa" checked={diputadosTipoBanca === 'previa'} onChange={() => setDiputadosTipoBanca('previa')} /> Descontar de Banca Previa</label> | ||||
|                             <label style={{ marginLeft: '1rem' }}><input type="radio" value="previa" checked={diputadosTipoBanca === 'previa'} onChange={() => setDiputadosTipoBanca('previa')} /> Descontar de Banca Previa</label> | ||||
|                         </div> | ||||
|                     )}                     | ||||
|                     )} | ||||
|                 </div> | ||||
|  | ||||
|                 {/* Columna Senadores */} | ||||
| @@ -97,11 +88,11 @@ export const ConfiguracionNacional = () => { | ||||
|                     </p> | ||||
|                     <select id="presidencia-senado-nacional" value={presidenciaSenadoId} onChange={e => setPresidenciaSenadoId(e.target.value)} style={{ width: '100%', padding: '8px' }}> | ||||
|                         <option value="">-- No Asignado --</option> | ||||
|                         {agrupaciones.map(a => (<option key={a.id} value={a.id}>{a.nombre}</option>))} | ||||
|                     </select>                     | ||||
|                         {agrupaciones.map(a => (<option key={a.id} value={a.id}>{`(${a.id}) ${a.nombre}`}</option>))} | ||||
|                     </select> | ||||
|                 </div> | ||||
|             </div> | ||||
|              | ||||
|  | ||||
|             <button onClick={handleSave} style={{ marginTop: '1.5rem' }}> | ||||
|                 Guardar Configuración | ||||
|             </button> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import type { MunicipioSimple, AgrupacionPolitica, LogoAgrupacionCategoria, Prov | ||||
| import { CATEGORIAS_NACIONALES_OPTIONS, CATEGORIAS_PROVINCIALES_OPTIONS } from '../constants/categorias'; | ||||
|  | ||||
| const ELECCION_OPTIONS = [ | ||||
|     { value: 0, label: 'General (Toda la elección)' }, | ||||
|     { value: 0, label: 'General (Todas las elecciones)' }, | ||||
|     { value: 2, label: 'Elecciones Nacionales' }, | ||||
|     { value: 1, label: 'Elecciones Provinciales' } | ||||
| ]; | ||||
| @@ -84,7 +84,14 @@ export const LogoOverridesManager = () => { | ||||
|             <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={agrupaciones.map(a => ({ value: a.id, label: a.nombre, ...a }))} | ||||
|                     getOptionValue={opt => opt.id} | ||||
|                     getOptionLabel={opt => `(${opt.id}) ${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' ? ( | ||||
|   | ||||
| @@ -25,12 +25,12 @@ import './AgrupacionesManager.css'; // Reutilizamos los estilos | ||||
| const updateOrdenDiputadosApi = async (ids: string[]) => { | ||||
|   const token = localStorage.getItem('admin-jwt-token'); | ||||
|   const response = await fetch('http://localhost:5217/api/admin/agrupaciones/orden-diputados', { | ||||
|       method: 'PUT', | ||||
|       headers: { | ||||
|           'Content-Type': 'application/json', | ||||
|           'Authorization': `Bearer ${token}` | ||||
|       }, | ||||
|       body: JSON.stringify(ids) | ||||
|     method: 'PUT', | ||||
|     headers: { | ||||
|       'Content-Type': 'application/json', | ||||
|       'Authorization': `Bearer ${token}` | ||||
|     }, | ||||
|     body: JSON.stringify(ids) | ||||
|   }); | ||||
|   if (!response.ok) { | ||||
|     throw new Error("Failed to save Diputados order"); | ||||
| @@ -38,77 +38,77 @@ const updateOrdenDiputadosApi = async (ids: string[]) => { | ||||
| }; | ||||
|  | ||||
| export const OrdenDiputadosManager = () => { | ||||
|     const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|     const [loading, setLoading] = useState(true); | ||||
|     const sensors = useSensors( | ||||
|       useSensor(PointerSensor), | ||||
|       useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|     ); | ||||
|   const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|   const [loading, setLoading] = useState(true); | ||||
|   const sensors = useSensors( | ||||
|     useSensor(PointerSensor), | ||||
|     useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|   ); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         const fetchAndSortAgrupaciones = async () => { | ||||
|             setLoading(true); | ||||
|             try { | ||||
|                 const data = await getAgrupaciones(); | ||||
|                 // Ordenar por el orden de Diputados. Los nulos van al final. | ||||
|                 data.sort((a, b) => (a.ordenDiputados || 999) - (b.ordenDiputados || 999)); | ||||
|                 setAgrupaciones(data); | ||||
|             } catch (error) { | ||||
|                 console.error("Failed to fetch agrupaciones for Diputados:", error); | ||||
|             } finally { | ||||
|                 setLoading(false); | ||||
|             } | ||||
|         }; | ||||
|         fetchAndSortAgrupaciones(); | ||||
|     }, []); | ||||
|  | ||||
|     const handleDragEnd = (event: DragEndEvent) => { | ||||
|       const { active, over } = event; | ||||
|       if (over && active.id !== over.id) { | ||||
|         setAgrupaciones((items) => { | ||||
|           const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|           const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|           return arrayMove(items, oldIndex, newIndex); | ||||
|         }); | ||||
|   useEffect(() => { | ||||
|     const fetchAndSortAgrupaciones = async () => { | ||||
|       setLoading(true); | ||||
|       try { | ||||
|         const data = await getAgrupaciones(); | ||||
|         // Ordenar por el orden de Diputados. Los nulos van al final. | ||||
|         data.sort((a, b) => (a.ordenDiputados || 999) - (b.ordenDiputados || 999)); | ||||
|         setAgrupaciones(data); | ||||
|       } catch (error) { | ||||
|         console.error("Failed to fetch agrupaciones for Diputados:", error); | ||||
|       } finally { | ||||
|         setLoading(false); | ||||
|       } | ||||
|     }; | ||||
|     fetchAndSortAgrupaciones(); | ||||
|   }, []); | ||||
|  | ||||
|     const handleSaveOrder = async () => { | ||||
|         const idsOrdenados = agrupaciones.map(a => a.id); | ||||
|         try { | ||||
|             await updateOrdenDiputadosApi(idsOrdenados); | ||||
|             alert('Orden de Diputados guardado con éxito!'); | ||||
|         } catch (error) { | ||||
|             alert('Error al guardar el orden de Diputados.'); | ||||
|         } | ||||
|     }; | ||||
|   const handleDragEnd = (event: DragEndEvent) => { | ||||
|     const { active, over } = event; | ||||
|     if (over && active.id !== over.id) { | ||||
|       setAgrupaciones((items) => { | ||||
|         const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|         const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|         return arrayMove(items, oldIndex, newIndex); | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|     if (loading) return <p>Cargando orden de Diputados...</p>; | ||||
|   const handleSaveOrder = async () => { | ||||
|     const idsOrdenados = agrupaciones.map(a => a.id); | ||||
|     try { | ||||
|       await updateOrdenDiputadosApi(idsOrdenados); | ||||
|       alert('Orden de Diputados guardado con éxito!'); | ||||
|     } catch (error) { | ||||
|       alert('Error al guardar el orden de Diputados.'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Ordenar Agrupaciones (Diputados)</h3> | ||||
|             <p>Arrastre para reordenar.</p> | ||||
|             <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|             <DndContext | ||||
|               sensors={sensors} | ||||
|               collisionDetection={closestCenter} | ||||
|               onDragEnd={handleDragEnd} | ||||
|             > | ||||
|               <SortableContext | ||||
|                 items={agrupaciones.map(a => a.id)} | ||||
|                 strategy={horizontalListSortingStrategy} | ||||
|               > | ||||
|                 <ul className="sortable-list-horizontal"> | ||||
|                   {agrupaciones.map(agrupacion => ( | ||||
|                     <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                       {agrupacion.nombreCorto || agrupacion.nombre} | ||||
|                     </SortableItem> | ||||
|                   ))} | ||||
|                 </ul> | ||||
|               </SortableContext> | ||||
|             </DndContext> | ||||
|             <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden Diputados</button> | ||||
|         </div> | ||||
|     ); | ||||
|   if (loading) return <p>Cargando orden de Diputados...</p>; | ||||
|  | ||||
|   return ( | ||||
|     <div className="admin-module"> | ||||
|       <h3>Ordenar Agrupaciones (Diputados)</h3> | ||||
|       <p>Arrastre para reordenar.</p> | ||||
|       <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|       <DndContext | ||||
|         sensors={sensors} | ||||
|         collisionDetection={closestCenter} | ||||
|         onDragEnd={handleDragEnd} | ||||
|       > | ||||
|         <SortableContext | ||||
|           items={agrupaciones.map(a => a.id)} | ||||
|           strategy={horizontalListSortingStrategy} | ||||
|         > | ||||
|           <ul className="sortable-list-horizontal"> | ||||
|             {agrupaciones.map(agrupacion => ( | ||||
|               <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                 {`(${agrupacion.id}) ${agrupacion.nombreCorto || agrupacion.nombre}`} | ||||
|               </SortableItem> | ||||
|             ))} | ||||
|           </ul> | ||||
|         </SortableContext> | ||||
|       </DndContext> | ||||
|       <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden Diputados</button> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @@ -11,92 +11,92 @@ import './AgrupacionesManager.css'; | ||||
| const ELECCION_ID_NACIONAL = 2; | ||||
|  | ||||
| export const OrdenDiputadosNacionalesManager = () => { | ||||
|     // Estado para la lista que el usuario puede ordenar | ||||
|     const [agrupacionesOrdenadas, setAgrupacionesOrdenadas] = useState<AgrupacionPolitica[]>([]); | ||||
|      | ||||
|     // Query 1: Obtener TODAS las agrupaciones para tener sus datos completos (nombre, etc.) | ||||
|     const { data: todasAgrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery<AgrupacionPolitica[]>({ | ||||
|         queryKey: ['agrupaciones'], | ||||
|         queryFn: getAgrupaciones, | ||||
|     }); | ||||
|      | ||||
|     // Query 2: Obtener los datos de composición para saber qué partidos tienen bancas | ||||
|     const { data: composicionData, isLoading: isLoadingComposicion } = useQuery({ | ||||
|         queryKey: ['composicionNacional', ELECCION_ID_NACIONAL], | ||||
|         queryFn: () => getComposicionNacional(ELECCION_ID_NACIONAL), | ||||
|     }); | ||||
|   // Estado para la lista que el usuario puede ordenar | ||||
|   const [agrupacionesOrdenadas, setAgrupacionesOrdenadas] = useState<AgrupacionPolitica[]>([]); | ||||
|  | ||||
|     // Este efecto se ejecuta cuando los datos de las queries estén disponibles | ||||
|     useEffect(() => { | ||||
|         // No hacemos nada hasta que ambas queries hayan cargado sus datos | ||||
|         if (!composicionData || !todasAgrupaciones || todasAgrupaciones.length === 0) { | ||||
|             return; | ||||
|         } | ||||
|   // Query 1: Obtener TODAS las agrupaciones para tener sus datos completos (nombre, etc.) | ||||
|   const { data: todasAgrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery<AgrupacionPolitica[]>({ | ||||
|     queryKey: ['agrupaciones'], | ||||
|     queryFn: getAgrupaciones, | ||||
|   }); | ||||
|  | ||||
|         // Creamos un Set con los IDs de los partidos que tienen al menos una banca de diputado | ||||
|         const partidosConBancasIds = new Set( | ||||
|             composicionData.diputados.partidos | ||||
|                 .filter(p => p.bancasTotales > 0) | ||||
|                 .map(p => p.id) | ||||
|         ); | ||||
|   // Query 2: Obtener los datos de composición para saber qué partidos tienen bancas | ||||
|   const { data: composicionData, isLoading: isLoadingComposicion } = useQuery({ | ||||
|     queryKey: ['composicionNacional', ELECCION_ID_NACIONAL], | ||||
|     queryFn: () => getComposicionNacional(ELECCION_ID_NACIONAL), | ||||
|   }); | ||||
|  | ||||
|         // Filtramos la lista completa de agrupaciones, quedándonos solo con las relevantes | ||||
|         const agrupacionesFiltradas = todasAgrupaciones.filter(a => partidosConBancasIds.has(a.id)); | ||||
|   // Este efecto se ejecuta cuando los datos de las queries estén disponibles | ||||
|   useEffect(() => { | ||||
|     // No hacemos nada hasta que ambas queries hayan cargado sus datos | ||||
|     if (!composicionData || !todasAgrupaciones || todasAgrupaciones.length === 0) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|         // Ordenamos la lista filtrada según el orden guardado en la BD | ||||
|         agrupacionesFiltradas.sort((a, b) => (a.ordenDiputadosNacionales || 999) - (b.ordenDiputadosNacionales || 999)); | ||||
|          | ||||
|         // Actualizamos el estado que se renderiza y que el usuario puede ordenar | ||||
|         setAgrupacionesOrdenadas(agrupacionesFiltradas); | ||||
|  | ||||
|     }, [todasAgrupaciones, composicionData]); // Dependencias: se re-ejecuta si los datos cambian | ||||
|  | ||||
|     const sensors = useSensors( | ||||
|       useSensor(PointerSensor), | ||||
|       useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|     // Creamos un Set con los IDs de los partidos que tienen al menos una banca de diputado | ||||
|     const partidosConBancasIds = new Set( | ||||
|       composicionData.diputados.partidos | ||||
|         .filter(p => p.bancasTotales > 0) | ||||
|         .map(p => p.id) | ||||
|     ); | ||||
|  | ||||
|     const handleDragEnd = (event: DragEndEvent) => { | ||||
|       const { active, over } = event; | ||||
|       if (over && active.id !== over.id) { | ||||
|         setAgrupacionesOrdenadas((items) => { | ||||
|           const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|           const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|           return arrayMove(items, oldIndex, newIndex); | ||||
|         }); | ||||
|       } | ||||
|     }; | ||||
|     // Filtramos la lista completa de agrupaciones, quedándonos solo con las relevantes | ||||
|     const agrupacionesFiltradas = todasAgrupaciones.filter(a => partidosConBancasIds.has(a.id)); | ||||
|  | ||||
|     const handleSaveOrder = async () => { | ||||
|         const idsOrdenados = agrupacionesOrdenadas.map(a => a.id); | ||||
|         try { | ||||
|             await updateOrden('diputados-nacionales', idsOrdenados); | ||||
|             alert('Orden de Diputados Nacionales guardado con éxito!'); | ||||
|         } catch (error) { | ||||
|             alert('Error al guardar el orden de Diputados Nacionales.'); | ||||
|         } | ||||
|     }; | ||||
|     // Ordenamos la lista filtrada según el orden guardado en la BD | ||||
|     agrupacionesFiltradas.sort((a, b) => (a.ordenDiputadosNacionales || 999) - (b.ordenDiputadosNacionales || 999)); | ||||
|  | ||||
|     const isLoading = isLoadingAgrupaciones || isLoadingComposicion; | ||||
|     if (isLoading) return <p>Cargando orden de Diputados Nacionales...</p>; | ||||
|     // Actualizamos el estado que se renderiza y que el usuario puede ordenar | ||||
|     setAgrupacionesOrdenadas(agrupacionesFiltradas); | ||||
|  | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Ordenar Agrupaciones (Diputados Nacionales)</h3> | ||||
|             <p>Arrastre para reordenar. Solo se muestran los partidos con bancas.</p> | ||||
|             <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|             <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> | ||||
|               <SortableContext items={agrupacionesOrdenadas.map(a => a.id)} strategy={horizontalListSortingStrategy}> | ||||
|                 <ul className="sortable-list-horizontal"> | ||||
|                   {agrupacionesOrdenadas.map(agrupacion => ( | ||||
|                     <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                       {agrupacion.nombreCorto || agrupacion.nombre} | ||||
|                     </SortableItem> | ||||
|                   ))} | ||||
|                 </ul> | ||||
|               </SortableContext> | ||||
|             </DndContext> | ||||
|             <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden</button> | ||||
|         </div> | ||||
|     ); | ||||
|   }, [todasAgrupaciones, composicionData]); // Dependencias: se re-ejecuta si los datos cambian | ||||
|  | ||||
|   const sensors = useSensors( | ||||
|     useSensor(PointerSensor), | ||||
|     useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|   ); | ||||
|  | ||||
|   const handleDragEnd = (event: DragEndEvent) => { | ||||
|     const { active, over } = event; | ||||
|     if (over && active.id !== over.id) { | ||||
|       setAgrupacionesOrdenadas((items) => { | ||||
|         const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|         const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|         return arrayMove(items, oldIndex, newIndex); | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleSaveOrder = async () => { | ||||
|     const idsOrdenados = agrupacionesOrdenadas.map(a => a.id); | ||||
|     try { | ||||
|       await updateOrden('diputados-nacionales', idsOrdenados); | ||||
|       alert('Orden de Diputados Nacionales guardado con éxito!'); | ||||
|     } catch (error) { | ||||
|       alert('Error al guardar el orden de Diputados Nacionales.'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const isLoading = isLoadingAgrupaciones || isLoadingComposicion; | ||||
|   if (isLoading) return <p>Cargando orden de Diputados Nacionales...</p>; | ||||
|  | ||||
|   return ( | ||||
|     <div className="admin-module"> | ||||
|       <h3>Ordenar Agrupaciones (Diputados Nacionales)</h3> | ||||
|       <p>Arrastre para reordenar. Solo se muestran los partidos con bancas.</p> | ||||
|       <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|       <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> | ||||
|         <SortableContext items={agrupacionesOrdenadas.map(a => a.id)} strategy={horizontalListSortingStrategy}> | ||||
|           <ul className="sortable-list-horizontal"> | ||||
|             {agrupacionesOrdenadas.map(agrupacion => ( | ||||
|               <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                 {`(${agrupacion.id}) ${agrupacion.nombreCorto || agrupacion.nombre}`} | ||||
|               </SortableItem> | ||||
|             ))} | ||||
|           </ul> | ||||
|         </SortableContext> | ||||
|       </DndContext> | ||||
|       <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden</button> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @@ -25,12 +25,12 @@ import './AgrupacionesManager.css'; // Reutilizamos los estilos | ||||
| const updateOrdenSenadoresApi = async (ids: string[]) => { | ||||
|   const token = localStorage.getItem('admin-jwt-token'); | ||||
|   const response = await fetch('http://localhost:5217/api/admin/agrupaciones/orden-senadores', { | ||||
|       method: 'PUT', | ||||
|       headers: { | ||||
|           'Content-Type': 'application/json', | ||||
|           'Authorization': `Bearer ${token}` | ||||
|       }, | ||||
|       body: JSON.stringify(ids) | ||||
|     method: 'PUT', | ||||
|     headers: { | ||||
|       'Content-Type': 'application/json', | ||||
|       'Authorization': `Bearer ${token}` | ||||
|     }, | ||||
|     body: JSON.stringify(ids) | ||||
|   }); | ||||
|   if (!response.ok) { | ||||
|     throw new Error("Failed to save Senadores order"); | ||||
| @@ -38,77 +38,77 @@ const updateOrdenSenadoresApi = async (ids: string[]) => { | ||||
| }; | ||||
|  | ||||
| export const OrdenSenadoresManager = () => { | ||||
|     const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|     const [loading, setLoading] = useState(true); | ||||
|     const sensors = useSensors( | ||||
|       useSensor(PointerSensor), | ||||
|       useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|     ); | ||||
|   const [agrupaciones, setAgrupaciones] = useState<AgrupacionPolitica[]>([]); | ||||
|   const [loading, setLoading] = useState(true); | ||||
|   const sensors = useSensors( | ||||
|     useSensor(PointerSensor), | ||||
|     useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|   ); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         const fetchAndSortAgrupaciones = async () => { | ||||
|             setLoading(true); | ||||
|             try { | ||||
|                 const data = await getAgrupaciones(); | ||||
|                 // Ordenar por el orden de Senadores. Los nulos van al final. | ||||
|                 data.sort((a, b) => (a.ordenSenadores || 999) - (b.ordenSenadores || 999)); | ||||
|                 setAgrupaciones(data); | ||||
|             } catch (error) { | ||||
|                 console.error("Failed to fetch agrupaciones for Senadores:", error); | ||||
|             } finally { | ||||
|                 setLoading(false); | ||||
|             } | ||||
|         }; | ||||
|         fetchAndSortAgrupaciones(); | ||||
|     }, []); | ||||
|  | ||||
|     const handleDragEnd = (event: DragEndEvent) => { | ||||
|       const { active, over } = event; | ||||
|       if (over && active.id !== over.id) { | ||||
|         setAgrupaciones((items) => { | ||||
|           const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|           const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|           return arrayMove(items, oldIndex, newIndex); | ||||
|         }); | ||||
|   useEffect(() => { | ||||
|     const fetchAndSortAgrupaciones = async () => { | ||||
|       setLoading(true); | ||||
|       try { | ||||
|         const data = await getAgrupaciones(); | ||||
|         // Ordenar por el orden de Senadores. Los nulos van al final. | ||||
|         data.sort((a, b) => (a.ordenSenadores || 999) - (b.ordenSenadores || 999)); | ||||
|         setAgrupaciones(data); | ||||
|       } catch (error) { | ||||
|         console.error("Failed to fetch agrupaciones for Senadores:", error); | ||||
|       } finally { | ||||
|         setLoading(false); | ||||
|       } | ||||
|     }; | ||||
|     fetchAndSortAgrupaciones(); | ||||
|   }, []); | ||||
|  | ||||
|     const handleSaveOrder = async () => { | ||||
|         const idsOrdenados = agrupaciones.map(a => a.id); | ||||
|         try { | ||||
|             await updateOrdenSenadoresApi(idsOrdenados); | ||||
|             alert('Orden de Senadores guardado con éxito!'); | ||||
|         } catch (error) { | ||||
|             alert('Error al guardar el orden de Senadores.'); | ||||
|         } | ||||
|     }; | ||||
|   const handleDragEnd = (event: DragEndEvent) => { | ||||
|     const { active, over } = event; | ||||
|     if (over && active.id !== over.id) { | ||||
|       setAgrupaciones((items) => { | ||||
|         const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|         const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|         return arrayMove(items, oldIndex, newIndex); | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|     if (loading) return <p>Cargando orden de Senadores...</p>; | ||||
|   const handleSaveOrder = async () => { | ||||
|     const idsOrdenados = agrupaciones.map(a => a.id); | ||||
|     try { | ||||
|       await updateOrdenSenadoresApi(idsOrdenados); | ||||
|       alert('Orden de Senadores guardado con éxito!'); | ||||
|     } catch (error) { | ||||
|       alert('Error al guardar el orden de Senadores.'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Ordenar Agrupaciones (Senado)</h3> | ||||
|             <p>Arrastre para reordenar.</p> | ||||
|             <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|             <DndContext | ||||
|               sensors={sensors} | ||||
|               collisionDetection={closestCenter} | ||||
|               onDragEnd={handleDragEnd} | ||||
|             > | ||||
|               <SortableContext | ||||
|                 items={agrupaciones.map(a => a.id)} | ||||
|                 strategy={horizontalListSortingStrategy} | ||||
|               > | ||||
|                 <ul className="sortable-list-horizontal"> | ||||
|                   {agrupaciones.map(agrupacion => ( | ||||
|                     <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                       {agrupacion.nombreCorto || agrupacion.nombre} | ||||
|                     </SortableItem> | ||||
|                   ))} | ||||
|                 </ul> | ||||
|               </SortableContext> | ||||
|             </DndContext> | ||||
|             <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden Senado</button> | ||||
|         </div> | ||||
|     ); | ||||
|   if (loading) return <p>Cargando orden de Senadores...</p>; | ||||
|  | ||||
|   return ( | ||||
|     <div className="admin-module"> | ||||
|       <h3>Ordenar Agrupaciones (Senado)</h3> | ||||
|       <p>Arrastre para reordenar.</p> | ||||
|       <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|       <DndContext | ||||
|         sensors={sensors} | ||||
|         collisionDetection={closestCenter} | ||||
|         onDragEnd={handleDragEnd} | ||||
|       > | ||||
|         <SortableContext | ||||
|           items={agrupaciones.map(a => a.id)} | ||||
|           strategy={horizontalListSortingStrategy} | ||||
|         > | ||||
|           <ul className="sortable-list-horizontal"> | ||||
|             {agrupaciones.map(agrupacion => ( | ||||
|               <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                 {`(${agrupacion.id}) ${agrupacion.nombreCorto || agrupacion.nombre}`} | ||||
|               </SortableItem> | ||||
|             ))} | ||||
|           </ul> | ||||
|         </SortableContext> | ||||
|       </DndContext> | ||||
|       <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden Senado</button> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @@ -11,84 +11,84 @@ import './AgrupacionesManager.css'; | ||||
| const ELECCION_ID_NACIONAL = 2; | ||||
|  | ||||
| export const OrdenSenadoresNacionalesManager = () => { | ||||
|     const [agrupacionesOrdenadas, setAgrupacionesOrdenadas] = useState<AgrupacionPolitica[]>([]); | ||||
|      | ||||
|     const { data: todasAgrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery<AgrupacionPolitica[]>({ | ||||
|         queryKey: ['agrupaciones'], | ||||
|         queryFn: getAgrupaciones, | ||||
|     }); | ||||
|      | ||||
|     const { data: composicionData, isLoading: isLoadingComposicion } = useQuery({ | ||||
|         queryKey: ['composicionNacional', ELECCION_ID_NACIONAL], | ||||
|         queryFn: () => getComposicionNacional(ELECCION_ID_NACIONAL), | ||||
|     }); | ||||
|   const [agrupacionesOrdenadas, setAgrupacionesOrdenadas] = useState<AgrupacionPolitica[]>([]); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         if (!composicionData || !todasAgrupaciones || todasAgrupaciones.length === 0) { | ||||
|             return; | ||||
|         } | ||||
|   const { data: todasAgrupaciones = [], isLoading: isLoadingAgrupaciones } = useQuery<AgrupacionPolitica[]>({ | ||||
|     queryKey: ['agrupaciones'], | ||||
|     queryFn: getAgrupaciones, | ||||
|   }); | ||||
|  | ||||
|         // Creamos un Set con los IDs de los partidos que tienen al menos una banca de senador | ||||
|         const partidosConBancasIds = new Set( | ||||
|             composicionData.senadores.partidos | ||||
|                 .filter(p => p.bancasTotales > 0) | ||||
|                 .map(p => p.id) | ||||
|         ); | ||||
|   const { data: composicionData, isLoading: isLoadingComposicion } = useQuery({ | ||||
|     queryKey: ['composicionNacional', ELECCION_ID_NACIONAL], | ||||
|     queryFn: () => getComposicionNacional(ELECCION_ID_NACIONAL), | ||||
|   }); | ||||
|  | ||||
|         const agrupacionesFiltradas = todasAgrupaciones.filter(a => partidosConBancasIds.has(a.id)); | ||||
|   useEffect(() => { | ||||
|     if (!composicionData || !todasAgrupaciones || todasAgrupaciones.length === 0) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|         agrupacionesFiltradas.sort((a, b) => (a.ordenSenadoresNacionales || 999) - (b.ordenSenadoresNacionales || 999)); | ||||
|          | ||||
|         setAgrupacionesOrdenadas(agrupacionesFiltradas); | ||||
|  | ||||
|     }, [todasAgrupaciones, composicionData]); | ||||
|  | ||||
|     const sensors = useSensors( | ||||
|       useSensor(PointerSensor), | ||||
|       useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|     // Creamos un Set con los IDs de los partidos que tienen al menos una banca de senador | ||||
|     const partidosConBancasIds = new Set( | ||||
|       composicionData.senadores.partidos | ||||
|         .filter(p => p.bancasTotales > 0) | ||||
|         .map(p => p.id) | ||||
|     ); | ||||
|  | ||||
|     const handleDragEnd = (event: DragEndEvent) => { | ||||
|       const { active, over } = event; | ||||
|       if (over && active.id !== over.id) { | ||||
|         setAgrupacionesOrdenadas((items) => { | ||||
|           const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|           const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|           return arrayMove(items, oldIndex, newIndex); | ||||
|         }); | ||||
|       } | ||||
|     }; | ||||
|     const agrupacionesFiltradas = todasAgrupaciones.filter(a => partidosConBancasIds.has(a.id)); | ||||
|  | ||||
|     const handleSaveOrder = async () => { | ||||
|         const idsOrdenados = agrupacionesOrdenadas.map(a => a.id); | ||||
|         try { | ||||
|             await updateOrden('senadores-nacionales', idsOrdenados); | ||||
|             alert('Orden de Senadores Nacionales guardado con éxito!'); | ||||
|         } catch (error) { | ||||
|             alert('Error al guardar el orden de Senadores Nacionales.'); | ||||
|         } | ||||
|     }; | ||||
|     agrupacionesFiltradas.sort((a, b) => (a.ordenSenadoresNacionales || 999) - (b.ordenSenadoresNacionales || 999)); | ||||
|  | ||||
|     const isLoading = isLoadingAgrupaciones || isLoadingComposicion; | ||||
|     if (isLoading) return <p>Cargando orden de Senadores Nacionales...</p>; | ||||
|     setAgrupacionesOrdenadas(agrupacionesFiltradas); | ||||
|  | ||||
|     return ( | ||||
|         <div className="admin-module"> | ||||
|             <h3>Ordenar Agrupaciones (Senado de la Nación)</h3> | ||||
|             <p>Arrastre para reordenar. Solo se muestran los partidos con bancas.</p> | ||||
|             <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|             <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> | ||||
|               <SortableContext items={agrupacionesOrdenadas.map(a => a.id)} strategy={horizontalListSortingStrategy}> | ||||
|                 <ul className="sortable-list-horizontal"> | ||||
|                   {agrupacionesOrdenadas.map(agrupacion => ( | ||||
|                     <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                       {agrupacion.nombreCorto || agrupacion.nombre} | ||||
|                     </SortableItem> | ||||
|                   ))} | ||||
|                 </ul> | ||||
|               </SortableContext> | ||||
|             </DndContext> | ||||
|             <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden</button> | ||||
|         </div> | ||||
|     ); | ||||
|   }, [todasAgrupaciones, composicionData]); | ||||
|  | ||||
|   const sensors = useSensors( | ||||
|     useSensor(PointerSensor), | ||||
|     useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) | ||||
|   ); | ||||
|  | ||||
|   const handleDragEnd = (event: DragEndEvent) => { | ||||
|     const { active, over } = event; | ||||
|     if (over && active.id !== over.id) { | ||||
|       setAgrupacionesOrdenadas((items) => { | ||||
|         const oldIndex = items.findIndex((item) => item.id === active.id); | ||||
|         const newIndex = items.findIndex((item) => item.id === over.id); | ||||
|         return arrayMove(items, oldIndex, newIndex); | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleSaveOrder = async () => { | ||||
|     const idsOrdenados = agrupacionesOrdenadas.map(a => a.id); | ||||
|     try { | ||||
|       await updateOrden('senadores-nacionales', idsOrdenados); | ||||
|       alert('Orden de Senadores Nacionales guardado con éxito!'); | ||||
|     } catch (error) { | ||||
|       alert('Error al guardar el orden de Senadores Nacionales.'); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const isLoading = isLoadingAgrupaciones || isLoadingComposicion; | ||||
|   if (isLoading) return <p>Cargando orden de Senadores Nacionales...</p>; | ||||
|  | ||||
|   return ( | ||||
|     <div className="admin-module"> | ||||
|       <h3>Ordenar Agrupaciones (Senado de la Nación)</h3> | ||||
|       <p>Arrastre para reordenar. Solo se muestran los partidos con bancas.</p> | ||||
|       <p>Ancla izquierda. Prioridad de izquierda a derecha y de arriba abajo.</p> | ||||
|       <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> | ||||
|         <SortableContext items={agrupacionesOrdenadas.map(a => a.id)} strategy={horizontalListSortingStrategy}> | ||||
|           <ul className="sortable-list-horizontal"> | ||||
|             {agrupacionesOrdenadas.map(agrupacion => ( | ||||
|               <SortableItem key={agrupacion.id} id={agrupacion.id}> | ||||
|                 {`(${agrupacion.id}) ${agrupacion.nombreCorto || agrupacion.nombre}`} | ||||
|               </SortableItem> | ||||
|             ))} | ||||
|           </ul> | ||||
|         </SortableContext> | ||||
|       </DndContext> | ||||
|       <button onClick={handleSaveOrder} style={{ marginTop: '1rem' }}>Guardar Orden</button> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user