Refinamiento de Funciones y Estética de Mapa
This commit is contained in:
@@ -5,7 +5,7 @@ import './DevAppStyle.css'
|
|||||||
export const DevAppLegislativas = () => {
|
export const DevAppLegislativas = () => {
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h1>Il visualizzatore di widget - Elecciones Nacionales 2025</h1>
|
<h1>Visor de Widgets</h1>
|
||||||
|
|
||||||
{/* Le pasamos el ID de la elección que queremos visualizar.
|
{/* Le pasamos el ID de la elección que queremos visualizar.
|
||||||
Para tus datos de prueba provinciales, este ID es 1. */}
|
Para tus datos de prueba provinciales, este ID es 1. */}
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
.panel-header {
|
.panel-header {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
position: relative;
|
||||||
|
/* Necesario para que z-index funcione */
|
||||||
|
z-index: 20;
|
||||||
|
/* Un número alto para ponerlo al frente */
|
||||||
|
background-color: white;
|
||||||
|
/* Asegura que no sea transparente */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Contenedor para alinear título y selector */
|
/* Contenedor para alinear título y selector */
|
||||||
@@ -29,20 +35,89 @@
|
|||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* El contenedor principal del selector (la parte visible antes de hacer clic) */
|
||||||
|
.categoria-selector__control {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
/* Coincide con el radio de los otros elementos */
|
||||||
|
border: 1px solid #e0e0e0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
/* Quitamos la sombra por defecto */
|
||||||
|
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo cuando el selector está enfocado (seleccionado) */
|
||||||
|
.categoria-selector__control--is-focused {
|
||||||
|
border-color: #007bff !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* El texto del valor seleccionado */
|
||||||
|
.categoria-selector__single-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* El menú desplegable que contiene las opciones */
|
||||||
|
.categoria-selector__menu {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
border: 1px solid #e0e0e0 !important;
|
||||||
|
margin-top: 4px !important;
|
||||||
|
/* Pequeño espacio entre el control y el menú */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cada una de las opciones en la lista */
|
||||||
|
.categoria-selector__option {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo de una opción cuando pasas el mouse por encima (estado 'focused') */
|
||||||
|
.categoria-selector__option--is-focused {
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
/* Un azul muy claro */
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo de la opción que está actualmente seleccionada */
|
||||||
|
.categoria-selector__option--is-selected {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* La pequeña línea vertical que separa el contenido del indicador (la flecha) */
|
||||||
|
.categoria-selector__indicator-separator {
|
||||||
|
display: none;
|
||||||
|
/* La ocultamos para un look más limpio */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* El indicador (la flecha hacia abajo) */
|
||||||
|
.categoria-selector__indicator {
|
||||||
|
color: #a0a0a0;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoria-selector__indicator:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- ESTILOS MODERNOS PARA BREADCRUMBS --- */
|
/* --- ESTILOS MODERNOS PARA BREADCRUMBS --- */
|
||||||
|
|
||||||
.breadcrumbs-container {
|
.breadcrumbs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem; /* Espacio entre elementos */
|
gap: 0.5rem;
|
||||||
font-size: 0.9rem;
|
/* Espacio entre elementos */
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item, .breadcrumb-item-actual {
|
.breadcrumb-item,
|
||||||
|
.breadcrumb-item-actual {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.4rem 0.8rem;
|
padding: 0.4rem 0.8rem;
|
||||||
border-radius: 8px; /* Bordes redondeados para efecto píldora */
|
border-radius: 8px;
|
||||||
|
/* Bordes redondeados para efecto píldora */
|
||||||
transition: background-color 0.2s ease-in-out;
|
transition: background-color 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +137,8 @@
|
|||||||
.breadcrumb-item-actual {
|
.breadcrumb-item-actual {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-weight: 700; /* Más peso para el nivel actual */
|
font-weight: 700;
|
||||||
|
/* Más peso para el nivel actual */
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-icon {
|
.breadcrumb-icon {
|
||||||
@@ -71,7 +147,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-separator {
|
.breadcrumb-separator {
|
||||||
color: #a0a0a0; /* Color sutil para el separador */
|
color: #a0a0a0;
|
||||||
|
/* Color sutil para el separador */
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,18 +162,21 @@
|
|||||||
|
|
||||||
/* Columna del mapa */
|
/* Columna del mapa */
|
||||||
.mapa-column {
|
.mapa-column {
|
||||||
flex: 2; /* Por defecto, ocupa 2/3 del espacio */
|
flex: 2;
|
||||||
|
/* Por defecto, ocupa 2/3 del espacio */
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: flex 0.5s ease-in-out;
|
transition: flex 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Columna de resultados */
|
/* Columna de resultados */
|
||||||
.resultados-column {
|
.resultados-column {
|
||||||
flex: 1; /* Por defecto, ocupa 1/3 */
|
flex: 1;
|
||||||
|
/* Por defecto, ocupa 1/3 */
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
transition: all 0.5s ease-in-out;
|
transition: all 0.5s ease-in-out;
|
||||||
min-width: 320px; /* Un ancho mínimo para que no se comprima demasiado */
|
min-width: 320px;
|
||||||
|
/* Un ancho mínimo para que no se comprima demasiado */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- NUEVO LAYOUT PARA TARJETAS DE PARTIDO --- */
|
/* --- NUEVO LAYOUT PARA TARJETAS DE PARTIDO --- */
|
||||||
@@ -105,8 +185,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
border-bottom: 1px solid #f0f0f0; /* Separador sutil */
|
border-bottom: 1px solid #f0f0f0;
|
||||||
border-left: 5px solid; /* El color se aplica inline */
|
border-left: 5px solid;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,15 +205,22 @@
|
|||||||
|
|
||||||
.partido-main-content {
|
.partido-main-content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
/* CAMBIO: De flex a grid */
|
||||||
gap: 0.5rem; /* Espacio entre la fila superior y la barra */
|
grid-template-columns: 1fr auto;
|
||||||
|
/* Columna 1 (nombre) flexible, Columna 2 (stats) se ajusta al contenido */
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
/* Dos filas: una para la info, otra para la barra */
|
||||||
|
align-items: center;
|
||||||
|
/* Alinea verticalmente el contenido de ambas filas */
|
||||||
|
gap: 0.25rem 1rem;
|
||||||
|
/* Espacio entre filas y columnas (0.25rem vertical, 1rem horizontal) */
|
||||||
}
|
}
|
||||||
|
|
||||||
.partido-top-row {
|
.partido-top-row {
|
||||||
display: flex;
|
/* Hacemos que este contenedor sea "invisible" para el grid,
|
||||||
justify-content: space-between;
|
promoviendo a sus hijos (info y stats) a la cuadrícula principal. */
|
||||||
align-items: flex-start; /* Alinea los elementos al tope */
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
.partido-info-wrapper {
|
.partido-info-wrapper {
|
||||||
@@ -142,7 +229,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.partido-nombre {
|
.partido-nombre {
|
||||||
font-weight: 500;
|
font-weight: 800;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -156,7 +243,8 @@
|
|||||||
.partido-stats {
|
.partido-stats {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-left: 1rem; /* Espacio para que no se pegue al nombre */
|
padding-left: 1rem;
|
||||||
|
/* Ya no necesita ser un contenedor flex, el grid lo posiciona */
|
||||||
}
|
}
|
||||||
|
|
||||||
.partido-porcentaje {
|
.partido-porcentaje {
|
||||||
@@ -175,6 +263,8 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
/* Le indicamos que ocupe ambas columnas (de la línea 1 a la 3) */
|
||||||
}
|
}
|
||||||
|
|
||||||
.partido-barra-foreground {
|
.partido-barra-foreground {
|
||||||
@@ -182,6 +272,7 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: width 0.5s ease-in-out;
|
transition: width 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------- */
|
/* ------------------------------------------- */
|
||||||
|
|
||||||
.panel-estado-recuento {
|
.panel-estado-recuento {
|
||||||
@@ -212,13 +303,15 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapa-render-area {
|
.mapa-render-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapa-volver-btn {
|
.mapa-volver-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
@@ -231,12 +324,15 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsm-zoomable-group {
|
.rsm-zoomable-group {
|
||||||
transition: transform 0.75s ease-in-out;
|
transition: transform 0.75s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-main-content.panel-collapsed .mapa-column {
|
.panel-main-content.panel-collapsed .mapa-column {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-main-content.panel-collapsed .resultados-column {
|
.panel-main-content.panel-collapsed .resultados-column {
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -244,6 +340,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-toggle-btn {
|
.panel-toggle-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -256,107 +353,435 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 4px 0 0 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.5rem;
|
font-size: 1.3rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #555;
|
color: #555;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-toggle-btn:hover {
|
.panel-toggle-btn:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsm-geography {
|
.rsm-geography {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
stroke: #000000;
|
stroke: #000000;
|
||||||
stroke-width: 0.25px;
|
stroke-width: 0.25px;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: filter 0.2s ease-in-out;
|
transition: filter 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsm-geography:not(.selected):hover {
|
.rsm-geography:not(.selected):hover {
|
||||||
filter: brightness(1.15);
|
filter: brightness(1.25); /* Mantenemos el brillo */
|
||||||
stroke: #ffffff;
|
stroke: #ffffff; /* Color del borde a blanco */
|
||||||
stroke-width: 0.25px;
|
stroke-width: 0.25px;
|
||||||
|
paint-order: stroke; /* Asegura que el borde se dibuje encima del relleno */
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsm-geography.selected {
|
.rsm-geography.selected {
|
||||||
stroke: #000000;
|
stroke: #000000;
|
||||||
stroke-width: 0.25px;
|
stroke-width: 0.25px;
|
||||||
filter: none;
|
filter: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsm-geography-faded,
|
.rsm-geography-faded,
|
||||||
.rsm-geography-faded-municipality {
|
.rsm-geography-faded-municipality {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caba-comuna-geography {
|
.caba-comuna-geography {
|
||||||
stroke: #000000;
|
stroke: #000000;
|
||||||
stroke-width: 0.05px;
|
stroke-width: 0.05px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caba-comuna-geography:not(.selected):hover {
|
.caba-comuna-geography:not(.selected):hover {
|
||||||
stroke: #000000;
|
stroke: #000000;
|
||||||
stroke-width: 0.055px;
|
stroke-width: 0.055px;
|
||||||
filter: brightness(1.25);
|
filter: brightness(1.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transition-spinner {
|
.transition-spinner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transition-spinner::after {
|
.transition-spinner::after {
|
||||||
content: '';
|
content: '';
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border: 5px solid rgba(0, 0, 0, 0.2);
|
border: 5px solid rgba(0, 0, 0, 0.2);
|
||||||
border-top-color: #007bff;
|
border-top-color: #007bff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.caba-magnifier-container {
|
.caba-magnifier-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: auto;
|
height: auto;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caba-lupa-svg {
|
.caba-lupa-svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caba-lupa-interactive-area {
|
.caba-lupa-interactive-area {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.25));
|
filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.25));
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caba-lupa-interactive-area:hover {
|
.caba-lupa-interactive-area:hover {
|
||||||
filter: brightness(1.15);
|
filter: brightness(1.15);
|
||||||
stroke: #ffffff;
|
stroke: #ffffff;
|
||||||
stroke-width: 0.25px;
|
stroke-width: 0.25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-fila div {
|
.skeleton-fila div {
|
||||||
background: #f6f7f8;
|
background: #f6f7f8;
|
||||||
background-image: linear-gradient(to right, #f6f7f8 0%, #edeef1 20%, #f6f7f8 40%, #f6f7f8 100%);
|
background-image: linear-gradient(to right, #f6f7f8 0%, #edeef1 20%, #f6f7f8 40%, #f6f7f8 100%);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 800px 104px;
|
background-size: 800px 104px;
|
||||||
animation: shimmer 1s linear infinite;
|
animation: shimmer 1s linear infinite;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-logo { width: 65px; height: 65px; }
|
.skeleton-logo {
|
||||||
.skeleton-text { height: 1em; }
|
width: 65px;
|
||||||
.skeleton-bar { height: 20px; margin-top: 4px; }
|
height: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-bar {
|
||||||
|
height: 20px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- NUEVOS ESTILOS PARA EL TOGGLE MÓVIL --- */
|
||||||
|
.mobile-view-toggle {
|
||||||
|
display: none;
|
||||||
|
/* Oculto por defecto */
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
gap: 5px;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
-webkit-backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-view-toggle .toggle-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-view-toggle .toggle-btn.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- ESTILOS PARA LOS BOTONES DE ZOOM DEL MAPA --- */
|
||||||
|
.zoom-controls-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 30;
|
||||||
|
/* Debe ser MAYOR que el z-index del header (20) */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-icon-wrapper {
|
||||||
|
/* Contenedor del icono */
|
||||||
|
display: flex;
|
||||||
|
/* Necesario para que el SVG interno se alinee */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-icon-wrapper svg {
|
||||||
|
/* Apunta directamente al SVG del icono */
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
/* Lo hace semitransparente */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* Muestra el cursor de "no permitido" */
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- ESTILOS DE CURSOR PARA EL ARRASTRE DEL MAPA --- */
|
||||||
|
.map-locked .rsm-geography {
|
||||||
|
cursor: pointer;
|
||||||
|
/* Cursor normal de clic */
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pannable .rsm-geography {
|
||||||
|
cursor: grab;
|
||||||
|
/* Indica que el mapa se puede arrastrar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pannable .rsm-geography:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
/* Indica que se está arrastrando */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- MEDIA QUERY PARA RESPONSIVE (ENFOQUE FINAL CON CAPAS) --- */
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
|
||||||
|
/* --- CONFIGURACIÓN GENERAL --- */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controles de vista y header (sin cambios) */
|
||||||
|
.mobile-view-toggle {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-toggle-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-top-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoria-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- NUEVO LAYOUT DE CAPAS SUPERPUESTAS --- */
|
||||||
|
|
||||||
|
/* 1. El contenedor principal ahora es un ancla de posicionamiento */
|
||||||
|
.panel-main-content {
|
||||||
|
position: relative;
|
||||||
|
/* Clave para que los hijos se posicionen dentro de él */
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
/* Le damos una altura fija y predecible */
|
||||||
|
min-height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Ambas columnas son capas que ocupan el 100% del espacio del padre */
|
||||||
|
.mapa-column,
|
||||||
|
.resultados-column {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Le damos un estilo específico a la columna del mapa para subirla */
|
||||||
|
.mapa-column {
|
||||||
|
top: -50px;
|
||||||
|
left: -10px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Hacemos que la columna de resultados pueda tener su propio scroll... */
|
||||||
|
.resultados-column {
|
||||||
|
top: 0;
|
||||||
|
/* Aseguramos que los resultados se queden en su sitio */
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Lógica de visibilidad: controlamos qué capa está "arriba" */
|
||||||
|
.panel-main-content.mobile-view-mapa .resultados-column {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
/* Esta es la propiedad clave que ya tenías, pero es importante verificarla */
|
||||||
|
pointer-events: none;
|
||||||
|
/* Asegura que la capa oculta no bloquee el mapa */
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-main-content.mobile-view-resultados .mapa-column {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hacemos que la columna de resultados pueda tener su propio scroll si el contenido es largo */
|
||||||
|
.resultados-column {
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Estilos de los resultados (ya estaban bien, se mantienen) */
|
||||||
|
.partido-fila {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partido-logo {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partido-main-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partido-top-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partido-info-wrapper {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partido-nombre {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partido-stats {
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- AJUSTE DE TAMAÑO DEL CONTENEDOR INTERNO DEL MAPA --- */
|
||||||
|
.mapa-column .mapa-componente-container,
|
||||||
|
.mapa-column .mapa-render-area {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Margen de seguridad para el último elemento de la lista de resultados */
|
||||||
|
.panel-partidos-container .partido-fila:last-child {
|
||||||
|
margin-bottom: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls-container {
|
||||||
|
top: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapa-volver-btn {
|
||||||
|
top: 55px;
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- MEDIA QUERY ADICIONAL PARA MÓVIL EN HORIZONTAL --- */
|
||||||
|
/* Se activa cuando la pantalla es ancha pero no muy alta, como un teléfono en landscape */
|
||||||
|
@media (max-width: 900px) and (orientation: landscape) {
|
||||||
|
|
||||||
|
/* Layout flexible de dos columnas */
|
||||||
|
.panel-main-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
position: static;
|
||||||
|
height: 85vh;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapa-column,
|
||||||
|
.resultados-column {
|
||||||
|
position: static;
|
||||||
|
/* Desactivamos el posicionamiento absoluto */
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto;
|
||||||
|
flex: 3;
|
||||||
|
overflow-y: auto;
|
||||||
|
/* Permitimos que la columna de resultados tenga su propio scroll */
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultados-column {
|
||||||
|
flex: 2;
|
||||||
|
min-width: 300px;
|
||||||
|
/* Un mínimo para que no se comprima */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Ocultamos los botones de cambio de vista móvil, ya que ambas se ven */
|
||||||
|
.mobile-view-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Mostramos de nuevo el botón lateral para colapsar el panel de resultados */
|
||||||
|
.panel-toggle-btn {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import { Breadcrumbs } from './components/Breadcrumbs';
|
|||||||
import './PanelNacional.css';
|
import './PanelNacional.css';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import type { PanelElectoralDto } from '../../../types/types';
|
import type { PanelElectoralDto } from '../../../types/types';
|
||||||
|
import { FiMap, FiList } from 'react-icons/fi';
|
||||||
|
import { useMediaQuery } from './hooks/useMediaQuery';
|
||||||
|
|
||||||
interface PanelNacionalWidgetProps {
|
interface PanelNacionalWidgetProps {
|
||||||
eleccionId: number;
|
eleccionId: number;
|
||||||
@@ -38,6 +40,9 @@ export const PanelNacionalWidget = ({ eleccionId }: PanelNacionalWidgetProps) =>
|
|||||||
const [ambitoActual, setAmbitoActual] = useState<AmbitoState>({ id: null, nivel: 'pais', nombre: 'Argentina', provinciaDistritoId: null });
|
const [ambitoActual, setAmbitoActual] = useState<AmbitoState>({ id: null, nivel: 'pais', nombre: 'Argentina', provinciaDistritoId: null });
|
||||||
const [categoriaId, setCategoriaId] = useState<number>(2);
|
const [categoriaId, setCategoriaId] = useState<number>(2);
|
||||||
const [isPanelOpen, setIsPanelOpen] = useState(true);
|
const [isPanelOpen, setIsPanelOpen] = useState(true);
|
||||||
|
const [mobileView, setMobileView] = useState<'mapa' | 'resultados'>('mapa');
|
||||||
|
// --- DETECCIÓN DE VISTA MÓVIL ---
|
||||||
|
const isMobile = useMediaQuery('(max-width: 800px)');
|
||||||
|
|
||||||
const handleAmbitoSelect = (nuevoAmbitoId: string, nuevoNivel: 'provincia' | 'municipio', nuevoNombre: string) => {
|
const handleAmbitoSelect = (nuevoAmbitoId: string, nuevoNivel: 'provincia' | 'municipio', nuevoNombre: string) => {
|
||||||
setAmbitoActual(prev => ({
|
setAmbitoActual(prev => ({
|
||||||
@@ -76,12 +81,14 @@ export const PanelNacionalWidget = ({ eleccionId }: PanelNacionalWidgetProps) =>
|
|||||||
<div className="panel-nacional-container">
|
<div className="panel-nacional-container">
|
||||||
<header className="panel-header">
|
<header className="panel-header">
|
||||||
<div className="header-top-row">
|
<div className="header-top-row">
|
||||||
<h1>Resultados elecciones {ambitoActual.nombre}</h1>
|
<h1>Legislativas Argentina 2025</h1>
|
||||||
<Select
|
<Select
|
||||||
options={CATEGORIAS_NACIONALES}
|
options={CATEGORIAS_NACIONALES}
|
||||||
value={selectedCategoria}
|
value={selectedCategoria}
|
||||||
onChange={(option) => option && setCategoriaId(option.value)}
|
onChange={(option) => option && setCategoriaId(option.value)}
|
||||||
className="categoria-selector"
|
className="categoria-selector"
|
||||||
|
classNamePrefix="categoria-selector"
|
||||||
|
isSearchable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
@@ -92,7 +99,7 @@ export const PanelNacionalWidget = ({ eleccionId }: PanelNacionalWidgetProps) =>
|
|||||||
onVolverProvincia={handleVolverAProvincia}
|
onVolverProvincia={handleVolverAProvincia}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
<main className={`panel-main-content ${!isPanelOpen ? 'panel-collapsed' : ''}`}>
|
<main className={`panel-main-content ${!isPanelOpen ? 'panel-collapsed' : ''} ${isMobile ? `mobile-view-${mobileView}` : ''}`}>
|
||||||
<div className="mapa-column">
|
<div className="mapa-column">
|
||||||
<button className="panel-toggle-btn" onClick={() => setIsPanelOpen(!isPanelOpen)} title={isPanelOpen ? "Ocultar panel" : "Mostrar panel"}>
|
<button className="panel-toggle-btn" onClick={() => setIsPanelOpen(!isPanelOpen)} title={isPanelOpen ? "Ocultar panel" : "Mostrar panel"}>
|
||||||
{isPanelOpen ? '›' : '‹'}
|
{isPanelOpen ? '›' : '‹'}
|
||||||
@@ -107,6 +114,7 @@ export const PanelNacionalWidget = ({ eleccionId }: PanelNacionalWidgetProps) =>
|
|||||||
provinciaDistritoId={ambitoActual.provinciaDistritoId ?? null}
|
provinciaDistritoId={ambitoActual.provinciaDistritoId ?? null}
|
||||||
onAmbitoSelect={handleAmbitoSelect}
|
onAmbitoSelect={handleAmbitoSelect}
|
||||||
onVolver={ambitoActual.nivel === 'municipio' ? handleVolverAProvincia : handleResetToPais}
|
onVolver={ambitoActual.nivel === 'municipio' ? handleVolverAProvincia : handleResetToPais}
|
||||||
|
isMobileView={isMobile}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,6 +128,24 @@ export const PanelNacionalWidget = ({ eleccionId }: PanelNacionalWidgetProps) =>
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* --- NUEVO CONTROLADOR DE VISTA PARA MÓVIL --- */}
|
||||||
|
<div className="mobile-view-toggle">
|
||||||
|
<button
|
||||||
|
className={`toggle-btn ${mobileView === 'mapa' ? 'active' : ''}`}
|
||||||
|
onClick={() => setMobileView('mapa')}
|
||||||
|
>
|
||||||
|
<FiMap />
|
||||||
|
<span>Mapa</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`toggle-btn ${mobileView === 'resultados' ? 'active' : ''}`}
|
||||||
|
onClick={() => setMobileView('resultados')}
|
||||||
|
>
|
||||||
|
<FiList />
|
||||||
|
<span>Resultados</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -9,6 +9,7 @@ import { API_BASE_URL, assetBaseUrl } from '../../../../apiService';
|
|||||||
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
|
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
|
||||||
import { MapaProvincial } from './MapaProvincial';
|
import { MapaProvincial } from './MapaProvincial';
|
||||||
import { CabaLupa } from './CabaLupa';
|
import { CabaLupa } from './CabaLupa';
|
||||||
|
import { BiZoomIn, BiZoomOut } from "react-icons/bi";
|
||||||
|
|
||||||
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||||
const FADED_BACKGROUND_COLOR = '#F0F0F0';
|
const FADED_BACKGROUND_COLOR = '#F0F0F0';
|
||||||
@@ -17,7 +18,7 @@ const normalizarTexto = (texto: string = '') => texto.trim().toUpperCase().norma
|
|||||||
type PointTuple = [number, number];
|
type PointTuple = [number, number];
|
||||||
|
|
||||||
const PROVINCE_VIEW_CONFIG: Record<string, { center: PointTuple; zoom: number }> = {
|
const PROVINCE_VIEW_CONFIG: Record<string, { center: PointTuple; zoom: number }> = {
|
||||||
"BUENOS AIRES": { center: [-60.5, -37.3], zoom: 5.5 },
|
"BUENOS AIRES": { center: [-60.5, -37.3], zoom: 5 },
|
||||||
"SANTA CRUZ": { center: [-69.5, -48.8], zoom: 5 },
|
"SANTA CRUZ": { center: [-69.5, -48.8], zoom: 5 },
|
||||||
"CIUDAD AUTONOMA DE BUENOS AIRES": { center: [-58.45, -34.6], zoom: 85 },
|
"CIUDAD AUTONOMA DE BUENOS AIRES": { center: [-58.45, -34.6], zoom: 85 },
|
||||||
"CHUBUT": { center: [-68.5, -44.5], zoom: 5.5 },
|
"CHUBUT": { center: [-68.5, -44.5], zoom: 5.5 },
|
||||||
@@ -31,7 +32,6 @@ const LUPA_SIZE_RATIO = 0.2;
|
|||||||
const MIN_LUPA_SIZE_PX = 100;
|
const MIN_LUPA_SIZE_PX = 100;
|
||||||
const MAX_LUPA_SIZE_PX = 180;
|
const MAX_LUPA_SIZE_PX = 180;
|
||||||
|
|
||||||
|
|
||||||
interface MapaNacionalProps {
|
interface MapaNacionalProps {
|
||||||
eleccionId: number;
|
eleccionId: number;
|
||||||
categoriaId: number;
|
categoriaId: number;
|
||||||
@@ -41,10 +41,19 @@ interface MapaNacionalProps {
|
|||||||
provinciaDistritoId: string | null;
|
provinciaDistritoId: string | null;
|
||||||
onAmbitoSelect: (ambitoId: string, nivel: 'provincia' | 'municipio', nombre: string) => void;
|
onAmbitoSelect: (ambitoId: string, nivel: 'provincia' | 'municipio', nombre: string) => void;
|
||||||
onVolver: () => void;
|
onVolver: () => void;
|
||||||
|
isMobileView: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver }: MapaNacionalProps) => {
|
// --- CONFIGURACIONES DEL MAPA ---
|
||||||
const [position, setPosition] = useState({ zoom: 1, center: [-65, -40] as PointTuple });
|
const desktopProjectionConfig = { scale: 700, center: [-65, -40] as [number, number] };
|
||||||
|
const mobileProjectionConfig = { scale: 1100, center: [-64, -41] as [number, number] };
|
||||||
|
|
||||||
|
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver, isMobileView }: MapaNacionalProps) => {
|
||||||
|
const [position, setPosition] = useState({
|
||||||
|
zoom: isMobileView ? 1.5 : 1.05, // 1.5 para móvil, 1.05 para desktop
|
||||||
|
center: [-65, -40] as PointTuple
|
||||||
|
});
|
||||||
|
const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null);
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const lupaRef = useRef<HTMLDivElement | null>(null);
|
const lupaRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -70,21 +79,33 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nivel === 'pais') {
|
if (nivel === 'pais') {
|
||||||
setPosition({ zoom: 1, center: [-65, -40] });
|
setPosition({
|
||||||
|
zoom: isMobileView ? 1.4 : 1.05, // 1.5 para móvil, 1.05 para desktop
|
||||||
|
center: [-65, -40]
|
||||||
|
});
|
||||||
|
// Reseteamos el ref
|
||||||
|
initialProvincePositionRef.current = null;
|
||||||
} else if (nivel === 'provincia') {
|
} else if (nivel === 'provincia') {
|
||||||
const nombreNormalizado = normalizarTexto(nombreAmbito);
|
const nombreNormalizado = normalizarTexto(nombreAmbito);
|
||||||
const manualConfig = PROVINCE_VIEW_CONFIG[nombreNormalizado];
|
const manualConfig = PROVINCE_VIEW_CONFIG[nombreNormalizado];
|
||||||
|
|
||||||
|
let provinceConfig = { zoom: 7, center: [-65, -40] as PointTuple };
|
||||||
|
|
||||||
if (manualConfig) {
|
if (manualConfig) {
|
||||||
setPosition(manualConfig);
|
provinceConfig = manualConfig;
|
||||||
} else {
|
} else {
|
||||||
const provinciaGeo = geoDataNacional.objects.provincias.geometries.find((g: any) => normalizarTexto(g.properties.nombre) === nombreNormalizado);
|
const provinciaGeo = geoDataNacional.objects.provincias.geometries.find((g: any) => normalizarTexto(g.properties.nombre) === nombreNormalizado);
|
||||||
if (provinciaGeo) {
|
if (provinciaGeo) {
|
||||||
const provinciaFeature = feature(geoDataNacional, provinciaGeo);
|
const provinciaFeature = feature(geoDataNacional, provinciaGeo);
|
||||||
const centroid = geoCentroid(provinciaFeature);
|
const centroid = geoCentroid(provinciaFeature);
|
||||||
setPosition({ zoom: 7, center: centroid as PointTuple });
|
provinceConfig = { zoom: 7, center: centroid as PointTuple };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPosition(provinceConfig);
|
||||||
|
|
||||||
|
// --- Guardar el objeto de posición completo en el ref ---
|
||||||
|
initialProvincePositionRef.current = provinceConfig;
|
||||||
}
|
}
|
||||||
}, [nivel, nombreAmbito, geoDataNacional]);
|
}, [nivel, nombreAmbito, geoDataNacional]);
|
||||||
|
|
||||||
@@ -104,7 +125,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
|
|||||||
|
|
||||||
const calculatedSize = containerRect.width * LUPA_SIZE_RATIO;
|
const calculatedSize = containerRect.width * LUPA_SIZE_RATIO;
|
||||||
const newLupaSize = Math.max(MIN_LUPA_SIZE_PX, Math.min(calculatedSize, MAX_LUPA_SIZE_PX));
|
const newLupaSize = Math.max(MIN_LUPA_SIZE_PX, Math.min(calculatedSize, MAX_LUPA_SIZE_PX));
|
||||||
|
|
||||||
const horizontalOffset = newLupaSize * 0.5;
|
const horizontalOffset = newLupaSize * 0.5;
|
||||||
const verticalOffset = newLupaSize * 0.2;
|
const verticalOffset = newLupaSize * 0.2;
|
||||||
|
|
||||||
@@ -132,7 +153,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
|
|||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
resizeObserver.observe(containerRef.current);
|
resizeObserver.observe(containerRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timerId: NodeJS.Timeout;
|
let timerId: NodeJS.Timeout;
|
||||||
|
|
||||||
if (initialLoadRef.current && nivel === 'pais') {
|
if (initialLoadRef.current && nivel === 'pais') {
|
||||||
@@ -159,20 +180,93 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
|
|||||||
};
|
};
|
||||||
}, [position, nivel]);
|
}, [position, nivel]);
|
||||||
|
|
||||||
|
// --- HANDLERS PARA EL ZOOM ---
|
||||||
|
const handleZoomIn = () => {
|
||||||
|
setPosition(prev => ({
|
||||||
|
...prev,
|
||||||
|
zoom: Math.min(prev.zoom * 1.8, 100) // Multiplica el zoom actual, con un límite
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Lógica de reseteo en handleZoomOut ---
|
||||||
|
const handleZoomOut = () => {
|
||||||
|
setPosition(prev => {
|
||||||
|
const newZoom = Math.max(prev.zoom / 1.8, 1);
|
||||||
|
const initialPos = initialProvincePositionRef.current;
|
||||||
|
|
||||||
|
// Si estamos en una provincia Y el nuevo zoom es igual o menor que el inicial...
|
||||||
|
if (initialPos && newZoom <= initialPos.zoom) {
|
||||||
|
// ...reseteamos a la posición inicial guardada (zoom Y centro).
|
||||||
|
return initialPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si no, solo actualizamos el zoom.
|
||||||
|
return { ...prev, zoom: newZoom };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMoveEnd = (newPosition: { coordinates: PointTuple, zoom: number }) => {
|
||||||
|
// Solo actualizamos el centro (coordenadas), no el zoom, al arrastrar
|
||||||
|
setPosition(prev => ({ ...prev, center: newPosition.coordinates }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const panEnabled =
|
||||||
|
//isMobileView &&
|
||||||
|
nivel === 'provincia' &&
|
||||||
|
initialProvincePositionRef.current !== null &&
|
||||||
|
position.zoom > initialProvincePositionRef.current.zoom &&
|
||||||
|
!nombreMunicipioSeleccionado;
|
||||||
|
|
||||||
|
const showZoomControls = nivel === 'provincia';
|
||||||
|
|
||||||
|
// --- FUNCIÓN DE FILTRO ---
|
||||||
|
const filterInteractionEvents = (event: any) => {
|
||||||
|
// La librería pasa un objeto de evento que contiene el evento original del navegador.
|
||||||
|
// Si el evento original es de la rueda del ratón ('wheel'), siempre lo bloqueamos.
|
||||||
|
if (event.sourceEvent && event.sourceEvent.type === 'wheel') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Para cualquier otro evento (arrastre, etc.), la decisión depende de nuestra lógica `panEnabled`.
|
||||||
|
return panEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- LÓGICA PARA DESHABILITAR EL BOTÓN ---
|
||||||
|
const isZoomOutDisabled =
|
||||||
|
(nivel === 'provincia' && initialProvincePositionRef.current && position.zoom <= initialProvincePositionRef.current.zoom) ||
|
||||||
|
(nivel === 'pais' && position.zoom <= (isMobileView ? 1.4 : 1.05));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mapa-componente-container" ref={containerRef}>
|
<div className="mapa-componente-container" ref={containerRef}>
|
||||||
|
{showZoomControls && (
|
||||||
|
<div className="zoom-controls-container">
|
||||||
|
<button onClick={handleZoomIn} className="zoom-btn" title="Acercar">
|
||||||
|
<span className="zoom-icon-wrapper"><BiZoomIn /></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleZoomOut}
|
||||||
|
className={`zoom-btn ${isZoomOutDisabled ? 'disabled' : ''}`}
|
||||||
|
title="Alejar"
|
||||||
|
disabled={isZoomOutDisabled}
|
||||||
|
>
|
||||||
|
<span className="zoom-icon-wrapper"><BiZoomOut /></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{nivel !== 'pais' && <button onClick={onVolver} className="mapa-volver-btn">← Volver</button>}
|
{nivel !== 'pais' && <button onClick={onVolver} className="mapa-volver-btn">← Volver</button>}
|
||||||
|
|
||||||
<div className="mapa-render-area">
|
<div className="mapa-render-area">
|
||||||
<ComposableMap
|
<ComposableMap
|
||||||
projection="geoMercator"
|
projection="geoMercator"
|
||||||
projectionConfig={{ scale: 700, center: [-65, -40] }}
|
projectionConfig={isMobileView ? mobileProjectionConfig : desktopProjectionConfig}
|
||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
>
|
>
|
||||||
<ZoomableGroup
|
<ZoomableGroup
|
||||||
center={position.center}
|
center={position.center}
|
||||||
zoom={position.zoom}
|
zoom={position.zoom}
|
||||||
filterZoomEvent={() => false}
|
onMoveEnd={handleMoveEnd}
|
||||||
|
filterZoomEvent={filterInteractionEvents}
|
||||||
>
|
>
|
||||||
<Geographies geography={geoDataNacional}>
|
<Geographies geography={geoDataNacional}>
|
||||||
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
|
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
|
||||||
@@ -233,7 +327,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip id="mapa-tooltip" />
|
<Tooltip id="mapa-tooltip" key={nivel} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// src/hooks/useMediaQuery.ts
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useMediaQuery = (query: string): boolean => {
|
||||||
|
const [matches, setMatches] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const media = window.matchMedia(query);
|
||||||
|
if (media.matches !== matches) {
|
||||||
|
setMatches(media.matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = () => setMatches(media.matches);
|
||||||
|
// Deprecated 'addListener' for broader browser support, 'addEventListener' is preferred.
|
||||||
|
if (media.addEventListener) {
|
||||||
|
media.addEventListener('change', listener);
|
||||||
|
} else {
|
||||||
|
media.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (media.removeEventListener) {
|
||||||
|
media.removeEventListener('change', listener);
|
||||||
|
} else {
|
||||||
|
media.removeListener(listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [matches, query]);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
};
|
||||||
@@ -14,7 +14,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3a8f64bf854b2bdf66d83e503aaacc7dca77138e")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7d2922aaeb546ad280af958d81394ab1715a3267")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
Reference in New Issue
Block a user