189 lines
7.5 KiB
TypeScript
189 lines
7.5 KiB
TypeScript
// src/components/TelegramaWidget.tsx
|
|
import { useState, useEffect, useMemo } from 'react';
|
|
import Select, { type FilterOptionOption } from 'react-select'; // <-- Importar react-select
|
|
import {
|
|
getSecciones,
|
|
getMunicipiosPorSeccion,
|
|
getEstablecimientosPorMunicipio, // <-- Nueva función
|
|
getMesasPorEstablecimiento,
|
|
getTelegramaPorId,
|
|
assetBaseUrl
|
|
} from '../apiService';
|
|
import type { TelegramaData, CatalogoItem } from '../types/types';
|
|
import './TelegramaWidget.css';
|
|
|
|
import { pdfjs, Document, Page } from 'react-pdf';
|
|
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
|
import 'react-pdf/dist/Page/TextLayer.css';
|
|
|
|
pdfjs.GlobalWorkerOptions.workerSrc = `${assetBaseUrl}/pdf.worker.min.mjs`;
|
|
|
|
// Estilos para los selectores
|
|
const customSelectStyles = {
|
|
control: (base: any) => ({ ...base, marginBottom: '1rem' }),
|
|
menu: (base: any) => ({ ...base, zIndex: 10 }),
|
|
};
|
|
|
|
// --- FUNCIÓN DE FILTRO "SMART SEARCH" ---
|
|
const smartSearchFilter = (
|
|
option: FilterOptionOption<{ label: string; value: string }>,
|
|
inputValue: string
|
|
) => {
|
|
// 1. Si no hay entrada de búsqueda, muestra todas las opciones.
|
|
if (!inputValue) {
|
|
return true;
|
|
}
|
|
|
|
// 2. Normalizamos tanto la etiqueta de la opción como la entrada del usuario:
|
|
// - a minúsculas
|
|
// - quitamos los acentos (si fuera necesario, aunque aquí no tanto)
|
|
const normalizedLabel = option.label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
const normalizedInput = inputValue.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
|
|
// 3. Dividimos la entrada del usuario en palabras individuales.
|
|
const searchTerms = normalizedInput.split(' ').filter(term => term.length > 0);
|
|
|
|
// 4. La opción es válida si CADA TÉRMINO de búsqueda está incluido en la etiqueta.
|
|
return searchTerms.every(term => normalizedLabel.includes(term));
|
|
};
|
|
|
|
export const TelegramaWidget = () => {
|
|
// Estados para los datos de los dropdowns
|
|
const [secciones, setSecciones] = useState<CatalogoItem[]>([]);
|
|
const [municipios, setMunicipios] = useState<CatalogoItem[]>([]);
|
|
const [establecimientos, setEstablecimientos] = useState<CatalogoItem[]>([]);
|
|
const [mesas, setMesas] = useState<CatalogoItem[]>([]);
|
|
|
|
// Estados para los valores seleccionados (adaptados para react-select)
|
|
const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null);
|
|
const [selectedMunicipio, setSelectedMunicipio] = useState<{ value: string; label: string } | null>(null);
|
|
const [selectedEstablecimiento, setSelectedEstablecimiento] = useState<{ value: string; label: string } | null>(null);
|
|
const [selectedMesa, setSelectedMesa] = useState<{ value: string; label: string } | null>(null);
|
|
|
|
// Estados para la visualización del telegrama (sin cambios)
|
|
const [telegrama, setTelegrama] = useState<TelegramaData | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Cargar secciones iniciales y aplicar orden
|
|
useEffect(() => {
|
|
getSecciones().then(seccionesData => {
|
|
const orden = new Map([
|
|
['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3],
|
|
['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7]
|
|
]);
|
|
const getOrden = (nombre: string) => {
|
|
const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/);
|
|
return match ? orden.get(match[0]) ?? 99 : 99;
|
|
};
|
|
seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre));
|
|
setSecciones(seccionesData);
|
|
});
|
|
}, []);
|
|
|
|
// Cargar municipios cuando cambia la sección
|
|
useEffect(() => {
|
|
if (selectedSeccion) {
|
|
setMunicipios([]); setEstablecimientos([]); setMesas([]);
|
|
setSelectedMunicipio(null); setSelectedEstablecimiento(null); setSelectedMesa(null);
|
|
getMunicipiosPorSeccion(selectedSeccion.value).then(setMunicipios);
|
|
}
|
|
}, [selectedSeccion]);
|
|
|
|
// Cargar establecimientos cuando cambia el municipio (SIN pasar por circuito)
|
|
useEffect(() => {
|
|
if (selectedMunicipio) {
|
|
setEstablecimientos([]); setMesas([]);
|
|
setSelectedEstablecimiento(null); setSelectedMesa(null);
|
|
getEstablecimientosPorMunicipio(selectedMunicipio.value).then(setEstablecimientos);
|
|
}
|
|
}, [selectedMunicipio]);
|
|
|
|
// Cargar mesas cuando cambia el establecimiento
|
|
useEffect(() => {
|
|
if (selectedEstablecimiento) {
|
|
setMesas([]);
|
|
setSelectedMesa(null);
|
|
getMesasPorEstablecimiento(selectedEstablecimiento.value).then(setMesas);
|
|
}
|
|
}, [selectedEstablecimiento]);
|
|
|
|
// Buscar el telegrama cuando se selecciona una mesa
|
|
useEffect(() => {
|
|
if (selectedMesa) {
|
|
setLoading(true); setError(null); setTelegrama(null);
|
|
getTelegramaPorId(selectedMesa.value)
|
|
.then(setTelegrama)
|
|
.catch(() => setError(`El telegrama para la mesa seleccionada, aún no se cargó en el sistema.`))
|
|
.finally(() => setLoading(false));
|
|
}
|
|
}, [selectedMesa]);
|
|
|
|
// Formateo de opciones para react-select
|
|
const seccionOptions = useMemo(() => secciones.map(s => ({ value: s.id, label: s.nombre })), [secciones]);
|
|
const municipioOptions = useMemo(() => municipios.map(m => ({ value: m.id, label: m.nombre })), [municipios]);
|
|
const establecimientoOptions = useMemo(() => establecimientos.map(e => ({ value: e.id, label: e.nombre })), [establecimientos]);
|
|
const mesaOptions = useMemo(() => mesas.map(m => ({ value: m.id, label: m.nombre })), [mesas]);
|
|
|
|
return (
|
|
<div className="telegrama-container">
|
|
<h4>Consulta de Telegramas por Ubicación</h4>
|
|
<div className="filters-grid">
|
|
<Select
|
|
options={seccionOptions}
|
|
value={selectedSeccion}
|
|
onChange={setSelectedSeccion}
|
|
placeholder="1. Sección..."
|
|
styles={customSelectStyles}
|
|
/>
|
|
<Select
|
|
options={municipioOptions}
|
|
value={selectedMunicipio}
|
|
onChange={setSelectedMunicipio}
|
|
placeholder="2. Municipio..."
|
|
isDisabled={!selectedSeccion}
|
|
styles={customSelectStyles}
|
|
/>
|
|
<Select
|
|
options={establecimientoOptions}
|
|
value={selectedEstablecimiento}
|
|
onChange={setSelectedEstablecimiento}
|
|
placeholder="3. Establecimiento..."
|
|
isDisabled={!selectedMunicipio}
|
|
styles={customSelectStyles}
|
|
filterOption={smartSearchFilter}
|
|
isSearchable // Habilitamos la búsqueda
|
|
/>
|
|
<Select
|
|
options={mesaOptions}
|
|
value={selectedMesa}
|
|
onChange={setSelectedMesa}
|
|
placeholder="4. Mesa..."
|
|
isDisabled={!selectedEstablecimiento}
|
|
styles={customSelectStyles}
|
|
/>
|
|
</div>
|
|
|
|
<div className="telegrama-viewer">
|
|
{loading && <div className="spinner"></div>}
|
|
{error && <p className="message error">{error}</p>}
|
|
|
|
{telegrama && (
|
|
<div className="telegrama-content">
|
|
<div className="telegrama-pdf-viewer">
|
|
<Document
|
|
file={`data:application/pdf;base64,${telegrama.contenidoBase64}`}
|
|
onLoadError={(error) => setError(`Error al cargar el PDF: ${error.message}`)}
|
|
loading={<div className="spinner"></div>}
|
|
>
|
|
<Page pageNumber={1} renderTextLayer={false} renderAnnotationLayer={false} />
|
|
</Document>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!loading && !telegrama && !error && <p className="message">Seleccione una mesa para visualizar el telegrama.</p>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |