Compare commits

...

22 Commits

Author SHA1 Message Date
ifiguero 5af531837f fix 2024-08-05 10:36:55 -04:00
ifiguero ad2f83cf3b none 2024-08-05 10:13:19 -04:00
ifiguero 9a69c66b13 test 2024-08-05 10:10:51 -04:00
ifiguero 2f51d94ea2 test 2024-08-05 10:06:30 -04:00
ifiguero c24acb7a75 probando 2024-08-05 09:37:11 -04:00
ifiguero a334053420 ,,, 2024-08-05 09:36:11 -04:00
ifiguero 3407f855c1 test 2024-08-05 09:32:47 -04:00
ifiguero 7abf1b3ab4 trry 2024-08-05 09:28:42 -04:00
ifiguero 9c1d9bf372 test 2024-08-05 09:21:51 -04:00
Ronald Morales a2687861a2 Agrega Graficos 2024-04-22 22:50:48 -04:00
Ronald Morales cc109373b3 Corrige Id Shape Numerico a Varchar 2024-04-12 23:36:56 -04:00
Ronald Morales 78956d4e01 Merge branch 'develop/Ronald' of https://gitlab.com/m3f_usm/admin_transporte/frontend 2024-03-25 01:23:17 -03:00
Ronald Morales 181806faf2 Agrega Validaciones a Carga GTFS y define rutas de informes 2024-03-25 01:21:47 -03:00
Ronald Morales 9486b40ecc Merge branch 'develop/Ronald' of https://gitlab.com/m3f_usm/admin_transporte/frontend 2024-03-24 10:51:27 -03:00
Ronald Morales 8e7168fbe4 Merge branch 'master' of https://gitlab.com/m3f_usm/admin_transporte/frontend into develop/Ronald 2024-03-24 10:47:48 -03:00
Ronald Morales 4e2d3c10c9 Modifica mapa, operador, fecha gtfs 2024-03-24 02:05:19 -03:00
Francisco Sandoval 07031c2a9e Merge branch 'develop/Ronald' into 'master'
Develop/ronald

See merge request m3f_usm/admin_transporte/frontend!3
2024-03-17 22:53:57 +00:00
Ronald Morales d379ff956a Agrega Reportes, valida borrado Rol, agrega texto llegada a Api y valida gtfs 2024-03-17 02:56:44 -03:00
Francisco Sandoval e947951f1c Merge remote-tracking branch 'origin/develop/Ronald' 2024-03-10 12:48:29 -03:00
Ronald Morales fac33c2d8a Merge branch 'master' of https://gitlab.com/m3f_usm/admin_transporte/frontend into develop/Ronald 2024-03-06 15:35:57 -03:00
Ronald Morales 76a28aa8fa Crea rol basado en Operador y App Tipo Cargo V2 2024-03-06 15:33:20 -03:00
Francisco Sandoval 08ce21218e cambios sabado 2 de marzo 2024 2024-03-02 18:10:40 -03:00
26 changed files with 987 additions and 117 deletions

35
package-lock.json generated
View File

@ -11,6 +11,8 @@
"@adminkit/core": "^3.4.0",
"bootstrap": "^5.3.0",
"bootstrap-icons": "^1.10.5",
"chart.js": "^3.9.1",
"chartjs-plugin-datalabels": "^2.2.0",
"history": "^5.3.0",
"svelte-navigator": "^3.2.2",
"svelte-pagination": "^0.0.1",
@ -37,6 +39,15 @@
"simplebar": "5.3.9"
}
},
"node_modules/@adminkit/core/node_modules/chart.js": {
"version": "2.9.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==",
"dependencies": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"node_modules/@babel/polyfill": {
"version": "7.12.1",
"resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz",
@ -508,13 +519,9 @@
"integrity": "sha512-ceOhN1DL7Y4O6M0j9ICgmTYziV89WMd96SvSl0REd8PMgrY0B/WBOPoed5S1KUmJqXgUXh8gzSe6E3ae27upsQ=="
},
"node_modules/chart.js": {
"version": "2.9.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==",
"dependencies": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
},
"node_modules/chartjs-color": {
"version": "2.4.1",
@ -533,6 +540,14 @@
"color-name": "^1.0.0"
}
},
"node_modules/chartjs-plugin-datalabels": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz",
"integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==",
"peerDependencies": {
"chart.js": ">=3.0.0"
}
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
@ -727,9 +742,9 @@
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}

View File

@ -17,6 +17,8 @@
"@adminkit/core": "^3.4.0",
"bootstrap": "^5.3.0",
"bootstrap-icons": "^1.10.5",
"chart.js": "^3.9.1",
"chartjs-plugin-datalabels": "^2.2.0",
"history": "^5.3.0",
"svelte-navigator": "^3.2.2",
"svelte-pagination": "^0.0.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -3,16 +3,16 @@
<div class="row text-muted">
<div class="col-6 text-start">
<p class="mb-0">
<a class="text-muted" href="https://www.empresa.com/"
<a class="text-muted" href="https://tdt-dev.ilab.cl/"
target="_blank" rel="noreferrer">
<strong>.</strong>
</a> &copy;
<strong></strong>
</a>
</p>
</div>
<div class="col-6 text-end">
<ul class="list-inline">
<li class="list-inline-item">
<a class="text-muted" href="https://www.empresa.com/support/" target="_blank" rel="noreferrer">Soporte</a>
<a class="text-muted" href="https://tdt-dev.ilab.cl/" target="_blank" rel="noreferrer">Transformación Digital del Transporte 2024</a>
</li>
</ul>
</div>

View File

@ -48,6 +48,13 @@
<i class="align-middle bi bi-card-list fs-4" />
<span class="align-middle">Listado Archivos GTFS</span>
</SideLink>
<li class="sidebar-header">Reportes</li>
<SideLink to='/reporte/itinerario'>
<i class="align-middle bi bi-card-list fs-4" />
<span class="align-middle">Itinerario</span>
</SideLink>
<li class="sidebar-header">Mantenedores</li>

View File

@ -0,0 +1,131 @@
<script>
import Paginate from "$/components/Paginate.svelte";
import { getOperadores } from "$/services/operadores";
import PageTitle from "$/components/PageTitle.svelte";
import ModalOperador from "./ModalOperador.svelte";
import { useLocation } from "svelte-navigator";
import { getPermisosPath } from "$/services/usuarios";
const limit = 15;
let page = 1;
let offset = 0;
let count = 0;
let ordering = 'id_operador'
let operadores = []
let operador = null
let verLineas =null
let loading = false;
let escritura = false;
let location = useLocation()
getPermisosPath($location.pathname)
.then(data => escritura = data.escritura)
.catch(error => console.log({ error }))
$: onPage(page)
async function onPage(p) {
try {
loading = true
offset = (p - 1) * limit;
const data = await getOperadores({ offset, limit, ordering })
operadores = data.results;
count = data.count;
} catch (error) {
globalThis.toast.error(error)
} finally {
loading = false;
}
}
function onEdita(item) {
operador = item;
}
function onVerLineas(item){
verLineas = item;
operador = item;
}
function onNuevo() {
operador = {}
}
function onOrderBy(field) {
ordering = ordering === field ? '-' + field : field;
onPage(page)
}
</script>
<PageTitle {loading}>Operadores</PageTitle>
<div class="card">
<div class="card-header">
{#if escritura}
<button class="btn btn-primary" on:click|preventDefault={onNuevo}>
<i class="bi bi-plus-lg"></i> Nuevo
</button>
{/if}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr class="table-light">
<th style="width:5%">Nro</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('id_operador')}>ID</a>
{#if ordering === 'id_operador'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-id_operador'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('nombre_operador')}>Nombre</a>
{#if ordering === 'nombre_operador'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-nombre_operador'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('vigente')}>Vigente</a>
{#if ordering === 'vigente'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-vigente'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
</tr>
</thead>
<tbody>
{#each operadores as app, index}
<tr key={index}>
<td class="table-light">{offset + index + 1}</td>
<td>{app.id_operador}</td>
<td><a href={"#"} on:click|preventDefault={() => onEdita(app)}>{app.nombre_operador}</a></td>
<td>{app.vigente ? '✅':'🚫'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
<div class="card-footer d-flex">
<a href={"#"} class="btn btn-outline-secondary me-3" on:click|preventDefault={() => onPage(page)}>
<i class="bi bi-arrow-repeat"></i>
</a>
<Paginate
{limit}
{count}
on:page={ev => page = ev.detail}
/>
</div>
</div>
{#if operador}
<ModalOperador
{operador}
{escritura}
on:close={() => operador = null}
on:refresh={() => onPage(page)}
/>
{/if}
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -1,26 +1,29 @@
<script>
import Paginate from "$/components/Paginate.svelte";
import { downloadGtfsArchivo, getGtfsArchivo,getGtfsArchivoId,getGtfsBase } from "$/services/gtfs_archivo";
import PageTitle from "$/components/PageTitle.svelte";
import PageTitle from "$/components/PageTitle.svelte";
import { useLocation } from "svelte-navigator";
import { getPermisosPath } from "$/services/usuarios";
import ModalgtfsUpload from "./ModalgtfsUpload.svelte";
import { getRedTransporte } from "$/services/red_transporte";
import { space } from "svelte/internal";
let id_red;
let id_red;
let escritura = false;
const limit = 15;
let page = 1;
let offset = 0;
let count = 0;
let ordering = 'id_archivo'
let ordering = '-id_gtfs'
let redes = []
let lista_gtfs = []
let location = useLocation()
let loading = false;
let showUpload = null;
let baseUrlInforme = window.location.origin;
getPermisosPath($location.pathname)
.then(data => escritura = data.escritura)
.catch(error => console.log({ error }))
@ -33,12 +36,12 @@
async function onPage(p) {
try {
if (!id_red) {
if (!id_red) {
return;
}
loading = true
offset = (p - 1) * limit;
const data = await getGtfsArchivo({id_red, offset, limit, ordering })
const data = await getGtfsArchivo({id_red, offset, limit, ordering:'-id_gtfs'})
lista_gtfs = data.results;
count = data.count;
} catch (error) {
@ -69,7 +72,36 @@
.finally(() => loading = false)
}
function reporteErroresCargaGTFS (id_red, id_gtfs){
const url = new URL(`reporte/gtfs/reporte_${id_red}_${id_gtfs}.pdf`, baseUrlInforme);
window.open(url.href, '_blank');
}
function adjustDateTimeByOffset(isoString) {
// Extraer la fecha, la hora y el desplazamiento de zona horaria
const [datePart, timePart] = isoString.split('T');
const [time, offset] = timePart.split(/(?=[-+])/); // Usa una expresión regular para dividir por el signo más cercano (+ o -)
// Convertir a fecha/hora UTC
const dateTime = new Date(`${datePart}T${time}Z`);
// Extraer las horas y minutos del desplazamiento
const offsetSign = offset[0] === '+' ? 1 : -1; // Determinar si el desplazamiento es positivo o negativo
const [offsetHours, offsetMinutes] = offset.slice(1).split(':').map(Number);
// Ajustar la fecha/hora basándose en el desplazamiento
dateTime.setUTCHours(dateTime.getUTCHours() + offsetSign * offsetHours);
dateTime.setUTCMinutes(dateTime.getUTCMinutes() + offsetSign * offsetMinutes);
// Formatear y retornar la nueva fecha/hora ajustada
const adjustedYear = dateTime.getUTCFullYear();
const adjustedMonth = String(dateTime.getUTCMonth() + 1).padStart(2, '0'); // getUTCMonth() es 0-indexado
const adjustedDate = String(dateTime.getUTCDate()).padStart(2, '0');
const adjustedHours = String(dateTime.getUTCHours()).padStart(2, '0');
const adjustedMinutes = String(dateTime.getUTCMinutes()).padStart(2, '0');
return `${adjustedDate}-${adjustedMonth}-${adjustedYear} ${adjustedHours}:${adjustedMinutes}`;
}
</script>
@ -106,27 +138,39 @@
{#if ordering === '-id_gtfs'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>-->
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('nombre_red')}>Archivo</a>
{#if ordering === 'archivo'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-archivo'}<i class="bi bi-caret-down-fill"></i>{/if}
<a href={"#"} on:click|preventDefault={() => onOrderBy('id_gtfs')}>ID</a>
<!-- {#if ordering === 'id_gtfs'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-id_gtfs'}<i class="bi bi-caret-down-fill"></i>{/if}-->
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('created')}>Creado el</a>
{#if ordering === 'created'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-created'}<i class="bi bi-caret-down-fill"></i>{/if}
<a href={"#"} on:click|preventDefault={() => onOrderBy('archivo')}>Archivo</a>
<!-- {#if ordering === 'archivo'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-archivo'}<i class="bi bi-caret-down-fill"></i>{/if}-->
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('status')}>Estado</a>
{#if ordering === 'status'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-status'}<i class="bi bi-caret-down-fill"></i>{/if}
<a href={"#"} on:click|preventDefault={() => onOrderBy('created')}>Creado el</a>
<!-- {#if ordering === 'created'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-created'}<i class="bi bi-caret-down-fill"></i>{/if}-->
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('status')}>Estado A</a>
<!-- {#if ordering === 'status'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-status'}<i class="bi bi-caret-down-fill"></i>{/if}-->
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('valid_from')}>Inico Vigencia</a>
<!-- {#if ordering === 'valid_from'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-valid_from'}<i class="bi bi-caret-down-fill"></i>{/if}-->
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('vigente')}>Vigente</a>
{#if ordering === 'vigente'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-vigente'}<i class="bi bi-caret-down-fill"></i>{/if}
<!-- {#if ordering === 'vigente'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-vigente'}<i class="bi bi-caret-down-fill"></i>{/if} -->
</th>
</tr>
</thead>
@ -134,7 +178,7 @@
{#each lista_gtfs as app, index}
<tr>
<td class="table-light">{offset + index + 1}</td>
<!--<td>{app.id_gtfs}</td>-->
<td>{app.id_gtfs}</td>
<td>
{#if !loading}
<a href={"#"} on:click|preventDefault={() => onDownload(app)}>{app.archivo}</a>
@ -142,8 +186,21 @@
<span>{app.archivo}</span>
{/if}
</td>
<td>{app.created}</td>
<td>{app.status}</td>
<td>{adjustDateTimeByOffset(app.created)}</td>
<td >
{#if app.status.toUpperCase().includes('PREP')}
<!-- Elemento interactivo para usuarios con 'CON REPAROS' en el estado -->
<a href={"#"} on:click|preventDefault={() => reporteErroresCargaGTFS(id_red, app.id_gtfs)}>{app.status}</a>
{:else}
<!-- Solo texto para estados sin 'CON REPAROS' -->
<a href={"#"} on:click|preventDefault={() => reporteErroresCargaGTFS(id_red, app.id_gtfs)}>{app.status}</a>
{/if}
</td>
<td>{app.valid_from}</td>
<td>{app.vigente ? '✅':'🚫'}</td>
</tr>
{/each}
@ -166,11 +223,11 @@
{#if showUpload}
<ModalgtfsUpload on:close={() => showUpload = false} {id_red} on:refresh={() => onPage(page)} />
{/if}
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>
</style>

View File

@ -0,0 +1,104 @@
<script>
import { getLineas } from "$/services/lineas";
import { getOperadores } from "$/services/operadores";
import { onMount } from 'svelte';
export let id_operador;
export let id_linea;
export let codigo;
export let ver_buses;
export let ver_paraderos;
export let loading = false;
let operadores = []
let lineas = []
let lineas_operador = []
onMount(() => {
getOperadores({ vigente: 1 })
.then(data => data.sort((a,b) => a.nombre_operador < b.nombre_operador? -1 : 1))
//.then(data => operadores = data)
.then(data => {
operadores = data;
if (operadores.length > 0) {
id_operador = operadores[0].id_operador;
onChangeOperador();
}
})
.catch(error => globalThis.toast.error(error))
getLineas({ vigente: 1 })
.then(data => data.sort((a,b) => a.nombre_linea < b.nombre_linea? -1 : 1))
.then(data => lineas = data)
.catch(error => globalThis.toast.error(error))
});
function onChangeOperador() {
id_linea = ''
ver_paraderos = false
ver_buses = false
if (!id_operador) {
lineas_operador = []
} else {
const lineas_filtradas = lineas.filter(el => el.id_operador === id_operador);
lineas_operador = lineas_filtradas.sort((a,b) => a.route_short_name < b.route_short_name ? -1 : 1);
if (lineas_operador.length > 0) {
id_linea = lineas_operador[0].id_linea;
onChangeLinea();
}
}
}
function onChangeLinea() {
codigo = lineas.find(el => el.id_linea === id_linea)?.route_short_name || null
ver_paraderos = false
ver_buses = false
}
</script>
<div class="row">
<div class="col-md">
<div class="input-group mb-3">
<div class="input-group-text">Operador</div>
<select bind:value={id_operador} class="form-select" on:change={onChangeOperador}>
<option value=""></option>
{#each operadores as operador}
<option value={operador.id_operador}>{operador.nombre_operador}</option>
{/each}
</select>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Linea</div>
<select bind:value={id_linea} class="form-select" style="font-family: monospace;" on:change={onChangeLinea}>
<option value=""></option>
{#each lineas_operador as linea}
<option value={linea.id_linea}>{@html linea.route_short_name.padEnd(6).replace(/ /g,'&nbsp;')} {linea.route_long_name}</option>
{/each}
</select>
</div>
</div>
<div class="col-md-auto">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="check-ver-buses-recorrido"
disabled={!id_linea}
bind:checked={ver_buses}>
<label class="form-check-label" for="check-ver-buses-recorrido">Ver Buses del Recorrido</label>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="check-ver-paraderos"
disabled={!id_linea}
bind:checked={ver_paraderos}>
<label class="form-check-label" for="check-ver-paraderos">Ver Paraderos</label>
</div>
{#if loading}
<div class="spinner-grow spinner-grow-sm text-danger" role="status">
<span class="visually-hidden">Loading...</span>
</div>
{/if}
</div>
</div>

View File

@ -7,12 +7,15 @@
// services
import { getRegiones } from "$/services/regiones"
import { getComunas } from "$/services/comunas"
import { getMarcasParaderos } from "$/services/mapas"
import { getMarcasParaderos, getCoordenadasIniciales } from "$/services/mapas"
// libs
import { storeParaderos } from "$/stores/global"
import imagenParada from '$/assets/parada.png'
import imagenParadaAzul from '$/assets/paradaAzul.png'
import { onMount } from "svelte";
import { getDispositivos } from "../../services/dispositivos";
let myMap = null
let elMap = null
@ -22,6 +25,7 @@
let comunas_x_region = []
let L = null
let iconParada = null
let iconParadaAzul = null
let markers = []
let form = {}
let parada = null
@ -30,10 +34,22 @@
$: myMap && crear_marcadores_por_criterio()
let center = { lat: 0, lng: 0 };
let dispositivosConTotem
//dispositivosConTotem = totemAsignado();
onMount(() => {
if(globalThis.L) create_map();
cargar_paraderos_todos($storeParaderos)
})
obtieneCorrdenadas();
// No es necesario llamar a create_map() , la lógica se maneja a través de la reactividad de center
});
// Reacciona a los cambios en center
$: if(center && globalThis.L) {
create_map();
cargar_paraderos_todos($storeParaderos);
};
function create_map() {
if (!elMap || !globalThis.L) return;
@ -46,6 +62,14 @@
popupAnchor: [0, -16]
})
}
if (!iconParadaAzul) {
iconParadaAzul = L.icon({
iconUrl: imagenParadaAzul,
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -16]
})
}
if (!myMap) {
myMap = L.map(elMap)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
@ -55,13 +79,19 @@
// obtener coordenadas actuales
// centrar mapa en coordenadas del navegador
navigator.geolocation.getCurrentPosition(
/* navigator.geolocation.getCurrentPosition(
({ coords }) => {
const { latitude, longitude } = coords;
myMap.setView([latitude, longitude], 16)
},
(error) => console.log({ error })
)
)*/
// Configurar la vista del mapa en la ubicación de Concepción con un nivel de zoom, por ejemplo, 16
myMap.setView(center, 15);
}
@ -74,6 +104,20 @@
}
}
async function obtieneCorrdenadas () {
try {
const data = await getCoordenadasIniciales('?');
center.lat = parseFloat(data.initialLat);
center.lng = parseFloat(data.initialLng);
console.log("Coordenadas de inicio:", data);
}
catch (error) {
console.error('Error al obtener las coordenadas iniciales :', error);
}
}
async function cargar_paraderos_todos(data_default) {
try {
loading = true
@ -118,11 +162,18 @@
// filtro coincide a criterio
return true
})
// 3. crear marcadores
for (let mark of paraderos) {
const tieneTotem = false // dispositivosConTotem.some(dispositivo => dispositivo.id_paradero === mark.id_paradero);
const { lat, lng } = mark.position
const marker = L.marker([lat, lng], { icon: iconParada }).addTo(myMap)
const marker = L.marker([lat, lng], {icon: tieneTotem ? iconParadaAzul : iconParada }).addTo(myMap)
const { title, location } = mark;
const html = `${title}<br>${location}`
@ -149,6 +200,22 @@
form.time_search && clearTimeout(form.time_search)
form.time_search = setTimeout(() => crear_marcadores_por_criterio(), 1000)
}
async function totemAsignado( ) {
try {
const dispTotem = await getDispositivos({ id_tipo_dispositivo: 1 });
return dispTotem
} catch (error) {
console.log({ error });
return [];
}
}
</script>
<svelte:head>

View File

@ -9,7 +9,7 @@
import { onMount } from "svelte";
// servicios
import { getRutas } from "$/services/mapas";
import { getRutas,getCoordenadasIniciales } from "$/services/mapas";
import ModalLetreroLUR from "./ModalLetreroLUR.svelte";
import FiltroRutas from "./FiltroRutas.svelte";
import { getBusesLinea, getParaderosLinea } from "$/services/lineas";
@ -39,15 +39,22 @@
let timeInterval = null
let fileproto = null
let loading_proto = false
let center = { lat: 0, lng: 0 };
onMount(() => {
create_map()
//create_map()
obtieneCorrdenadas();
return () => {
timeInterval && globalThis.clearInterval(timeInterval)
}
})
// Reacciona a los cambios en center
$: if(center && globalThis.L) {
create_map();
};
function create_map() {
if (!elMap || !globalThis.L) return;
if (!L) L = globalThis.L;
@ -85,22 +92,38 @@
}
if (!myMap) {
myMap = L.map(elMap)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{ attribution: '&copy; <a target="_blank" href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }
L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
maxZoom: 19,
attribution: '&copy; <a target="_blank" href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
).addTo(myMap);
}
// obtener coordenadas actuales
// centrar mapa en coordenadas del navegador
navigator.geolocation.getCurrentPosition(
/*navigator.geolocation.getCurrentPosition(
({ coords }) => {
const { latitude, longitude } = coords;
myMap.setView([latitude, longitude], 16);
},
(error) => console.log({ error })
)
}
)*/
myMap.setView(center, 14);
}
async function obtieneCorrdenadas () {
try {
const data = await getCoordenadasIniciales('?');
center.lat = parseFloat(data.initialLat);
center.lng = parseFloat(data.initialLng);
console.log("Coordenadas de inicio:", data);
}
catch (error) {
console.error('Error al obtener las coordenadas iniciales :', error);
}
}
async function onMostrarRuta(id_operador, id_linea) {
try {
loading = true
@ -114,7 +137,11 @@
const coordenadas = data.positions.map(({ shape_pt_lat, shape_pt_lon}) => [shape_pt_lat, shape_pt_lon])
polyline = L.polyline(coordenadas, { color: 'red' }).addTo(myMap)
myMap.fitBounds(polyline.getBounds());
const bound = polyline.getBounds()
if (bound._northEast && bound._southWset) {
myMap.fitBounds(polyline.getBounds());
}
if (coordenadas) {
marker1 = L.marker(coordenadas[0], { icon: iconPartida }).addTo(myMap)
@ -124,6 +151,7 @@
}
} catch (error) {
console.log({ error })
globalThis.toast.error(error)
} finally {
loading = false;

View File

@ -71,19 +71,20 @@
form = await updateOperador(form);
} else {
formRol.nombre_rol=form.nombre_operador;
formRol = await createRol(formRol);
form.id_rol = formRol.id_rol
/*formRol.nombre_rol=form.nombre_operador;
formRol = await createRol(formRol);
form.id_rol = formRol.id_rol */
form = await createOperador(form);
formRol= await createRolOperador(form);
/*formRol= await createRolOperador(form);*/
formRol={};
/*formRol={};
formRol.id_rol = form.id_rol;
formRol.id_aplicacion=1;
formRol = await createRolyaplicacion(formRol);
formRol.id_aplicacion=2;
formRol = await createRolyaplicacion(formRol);
formRol = await createRolyaplicacion(formRol);*/
}

View File

@ -0,0 +1,64 @@
<script>
import { getOperadores } from "$/services/operadores";
export let id_operador;
export let loading = false;
let operadores = [];
let baseUrlInforme = window.location.origin;
getOperadores({ vigente: 1 })
.then(data => data.sort((a, b) => a.nombre_operador < b.nombre_operador ? -1 : 1))
.then(data => operadores = data)
.catch(error => globalThis.toast.error(error));
function validarOperadorSeleccionado() {
if (!id_operador) {
globalThis.toast.error('Debe seleccionar un operador');
return false;
}
return true;
}
function abrirReporteItinerario() {
if (!validarOperadorSeleccionado()) return;
const url = new URL(`reporte/itinerarios/${id_operador}.pdf`, baseUrlInforme);
window.open(url.href, '_blank');
}
function abrirReporteExpediciones() {
if (!validarOperadorSeleccionado()) return;
const fecha = new Date().toISOString().slice(0, 10).replace(/-/g, '');
//const url = `${window.location.href}reporte/expediciones/${id_operador}_${fecha}.pdf`;
const url = new URL(`reporte/expediciones/${id_operador}_${fecha}.pdf`, baseUrlInforme);
window.open(url.href, '_blank');
}
</script>
<div class="row">
<div class="col-md">
<div class="input-group mb-3">
<div class="input-group-text">Operador</div>
<select bind:value={id_operador} class="form-select">
{#each operadores as operador}
<option value={operador.id_operador}>{operador.nombre_operador}</option>
{/each}
</select>
</div>
</div>
<div class="col-md-auto">
{#if loading}
<div class="spinner-grow spinner-grow-sm text-danger" role="status">
<span class="visually-hidden">Loading...</span>
</div>
{/if}
</div>
</div>
<div class="btn-group" role="group">
<button on:click={abrirReporteItinerario} class="btn btn-primary">Reporte Itinerario</button>
</div>
<div class="btn-group" role="group">
<button on:click={abrirReporteExpediciones} class="btn btn-secondary">Reporte Expediciones</button>
</div>

View File

@ -0,0 +1,32 @@
<script>
import { onMount } from 'svelte';
import Chart from 'chart.js/auto';
export let comunasCounts = [];
onMount(() => {
const ctx = document.getElementById('comunasChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: comunasCounts.map(item => item.id_comuna__nombre_comuna),
datasets: [{
label: '# de Paraderos',
data: comunasCounts.map(item => item.total),
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
</script>
<canvas id="comunasChart" width="400" height="400"></canvas>

View File

@ -74,7 +74,7 @@
<div class="card flex-fill w-100">
<div class="card-header">
<h5 class="card-title mb-0">Buses en recorrido por línea</h5>
<h5 class="card-title mb-0">Buses en recorrido por Ruta</h5>
</div>
<div class="card-body">
<div class="row">

View File

@ -18,7 +18,7 @@
<div class="card-body">
<div class="row">
<div class="col mt-0">
<h5 class="card-title">Cantidad de Líneas</h5>
<h5 class="card-title">Cantidad de Rutas</h5>
</div>
<div class="col-auto">

View File

@ -1,32 +1,47 @@
<script>
import { createEventDispatcher, onMount } from "svelte";
import { getCountByComuna } from "$/services/paraderos";
const dispatch = createEventDispatcher();
let comunasCounts = [];
onMount(() => {
dispatch('loading', true);
getCountByComuna({ vigente: 1 })
.then(data => {
comunasCounts = data.count_by_comuna.sort((a, b) => {
return a.id_comuna__nombre_comuna.localeCompare(b.id_comuna__nombre_comuna);
});
})
.catch(error => console.error("Failed to load comunas counts:", error))
.finally(() => dispatch('loading', false));
});
</script>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col mt-0">
<h5 class="card-title">Paraderos por comuna</h5>
</div>
<div class="col-auto">
<div class="stat text-primary">
<i class="bi bi-geo-alt fs-4"></i>
</div>
<h5 class="card-title">Paraderos por Comuna </h5>
<div class="col-auto">
<div class="stat text-primary">
<i class="bi bi-map"></i>
</div>
</div>
<h1 class="mt-1 mb-3">2.382</h1>
<table class="table mb-0">
<table class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>Comuna</th>
<th>Total Paraderos</th>
</tr>
</thead>
<tbody>
<tr>
<td>Chrome</td>
<td class="text-end">4306</td>
</tr>
<tr>
<td>Firefox</td>
<td class="text-end">3801</td>
</tr>
<tr>
<td>IE</td>
<td class="text-end">1689</td>
</tr>
{#each comunasCounts as { id_comuna__nombre_comuna, total }}
<tr>
<td>{id_comuna__nombre_comuna}</td>
<td>{total}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,113 @@
<script>
import { onMount } from 'svelte';
import { getCountByComuna } from '$/services/paraderos';
import Chart from 'chart.js/auto';
import ChartDataLabels from 'chartjs-plugin-datalabels';
let comunasCounts = [];
let chart = null; // Referencia al gráfico
onMount(() => {
getCountByComuna({ vigente: 1 })
.then(data => {
comunasCounts = data.count_by_comuna.sort((a, b) => a.id_comuna__nombre_comuna.localeCompare(b.id_comuna__nombre_comuna));
updateChart(); // Actualizar el gráfico cuando los datos estén listos
})
.catch(error => console.error("Failed to load comunas counts:", error));
});
function updateChart() {
const ctx = document.getElementById('comunasChart').getContext('2d');
if (chart) {
chart.destroy(); // Destruir el gráfico existente antes de crear uno nuevo
}
chart = new Chart(ctx, {
type: 'pie',
data: {
labels: comunasCounts.map(c => c.id_comuna__nombre_comuna),
datasets: [{
label: 'Total Paraderos',
data: comunasCounts.map(c => c.total),
backgroundColor: comunasCounts.map(() => `hsla(${Math.random() * 360}, 100%, 75%, 0.8)`), // Colores aleatorios con opacidad
hoverOffset: 4
}]
},
plugins: [ChartDataLabels],
options: {
responsive: true,
plugins: {
datalabels: {
color: '#ffffff',
anchor: 'end',
align: 'start',
font: {
size: 14,
weight: 'bold',
family: 'Arial'
},
formatter: (value, ctx) => {
let sum = ctx.dataset.data.reduce((acc, curr) => acc + curr, 0);
let percentage = (value * 100 / sum).toFixed(2) + "%";
return ` (${percentage})`;
}
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
let label = tooltipItem.label || '';
let value = tooltipItem.raw;
let total = tooltipItem.dataset.data.reduce((acc, curr) => acc + curr, 0);
let percentage = ((value / total) * 100).toFixed(2) + '%';
return `${label}: (${percentage})`;
}
}
},
legend: {
position: 'top',
}
}
}
});
}
</script>
<table class="table table-striped table-hover table-responsive">
<tr>
<th>
<div class="card">
<div class="card-body">
<h5 class="card-title">Paraderos por Comuna </h5>
<div class="col-auto">
<div class="stat text-primary">
<i class="bi bi-map"></i>
</div>
</div>
<table class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>Comuna</th>
<th>Total Paraderos</th>
</tr>
</thead>
<tbody>
{#each comunasCounts as { id_comuna__nombre_comuna, total }}
<tr>
<td>{id_comuna__nombre_comuna}</td>
<td>{total}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</th>
<th>
<div class="card">
<div class="card-body">
<canvas id="comunasChart"></canvas>
</div>
</div>
</th>
</tr>
</table>

View File

@ -0,0 +1,71 @@
<script>
import { onMount, createEventDispatcher } from "svelte";
import Chart from 'chart.js/auto';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { getCountRutasBuses } from "$/services/lineas";
import { getCount } from "$/services/lineas";
const dispatch = createEventDispatcher();
let rutasConGPS = [];
let rutasSinGPS = 0;
let chart = null;
onMount(() => {
dispatch('loading', true);
Promise.all([
getCountRutasBuses(),
getCount({ vigente: 1 })
]).then(([dataRutasConGPS, dataTotalRutas]) => {
rutasConGPS = dataRutasConGPS;
rutasSinGPS = parseInt (dataTotalRutas.count) - parseInt (dataRutasConGPS.count);
updateChart();
}).catch(error => {
console.error('Error fetching counts:', error);
}).finally(() => {
dispatch('loading', false);
});
});
function updateChart() {
const ctx = document.getElementById('barChartR').getContext('2d');
if (chart) {
chart.destroy();
}
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Rutas con información', 'Rutas sin información'],
datasets: [{
label: 'Posicionamiento',
data: [rutasConGPS.count, rutasSinGPS],
backgroundColor: ['rgba(54, 162, 235, 0.8)', 'rgba(255, 99, 132, 0.8)']
}]
},
plugins: [ChartDataLabels],
options: {
plugins: {
legend: {
display: true
},
tooltip: {
enabled: true
}
},
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
</script>
<div class="card">
<div class="card-body">
<h5 class="card-title">Rutas con Informacion de Posicionamiento</h5>
<canvas id="barChartR"></canvas>
</div>
</div>

View File

@ -3,42 +3,29 @@
import CantidadParaderos from "./CantidadParaderos.svelte";
import CantidadLineas from "./CantidadLineas.svelte";
import CantidadParaderosComuna from "./CantidadParaderosComuna.svelte";
import CantidadParaderosComunaGrafico from "./CantidadParaderosComunaGrafico.svelte";
import CantidadBusesRecorrido from "./CantidadBusesRecorrido.svelte";
import CantidadBusesLinea from "./CantidadBusesLinea.svelte";
import RutasCorrectas from "./rutasCorrectas.svelte";
import CantidadRutasBuses from "./CantidadRutaBuses.svelte";
let loading1 = false
let loading2 = false
let loading3 = false
let loading4 = false
let loading5 = false
let loading6 = false
</script>
<PageTitle
loading={loading1 || loading2 || loading3 || loading4 || loading5 }
><strong>Análisis</strong> de datos</PageTitle>
<PageTitle loading={loading1 || loading2 || loading3 || loading4 || loading5 }><strong>Resumen</strong> de datos</PageTitle>
<div class="row">
<div class="col-xs-12 col-md-7">
<div class="col-12">
<div class="row">
<div class="col">
<CantidadLineas on:loading={ev => loading1 = ev.detail} />
</div>
<div class="col">
<CantidadParaderos on:loading={ev => loading2 = ev.detail} />
</div>
<div class="col"><CantidadParaderos on:loading={ev => loading2 = ev.detail} /></div>
</div>
<div class="row">
<div class="col">
<CantidadParaderosComuna on:loading={ev => loading3 = ev.detail} />
</div>
<div class="col">
<CantidadBusesRecorrido on:loading={ev => loading4 = ev.detail} />
</div>
<div class="col"><CantidadParaderosComunaGrafico on:loading={ev => loading3 = ev.detail} /> </div>
</div>
</div>
<div class="col-xs-12 col-md-5">
<CantidadBusesLinea on:loading={ev => loading5 = ev.detail} />
</div>
</div>

View File

@ -0,0 +1,69 @@
<script>
import { onMount } from "svelte";
import Chart from 'chart.js/auto';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { getCountLineasCorrectas } from "$/services/lineas";
let rutas = [];
let chart = null;
onMount(() => {
getCountLineasCorrectas().then(data => {
rutas = data;
updateChart();
}).catch(error => console.error('Error fetching line counts:', error));
});
function updateChart() {
const ctx = document.getElementById('barChart').getContext('2d');
if (chart) {
chart.destroy();
}
// Generando colores aleatorios para cada barra
const backgroundColors = rutas.map(() => `hsla(${Math.random() * 360}, 70%, 70%, 0.8)`);
const borderColors = rutas.map(() => `hsla(${Math.random() * 360}, 70%, 50%, 1)`);
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: rutas.map(ruta => ruta.titulo),
datasets: [{
label: 'Rutas',
data: rutas.map(ruta => ruta.cantidad),
backgroundColor: backgroundColors,
borderColor: borderColors,
borderWidth: 1
}]
},
plugins: [ChartDataLabels],
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
datalabels: {
color: '#444',
font: {
weight: 'bold'
},
formatter: (value, ctx) => {
let sum = ctx.dataset.data.reduce((a, b) => a + b, 0);
let percentage = (value * 100 / sum).toFixed(2) + "%";
return value + " (" + percentage + ")";
}
}
}
}
});
}
</script>
<div class="card">
<div class="card-body">
<h5 class="card-title">Rutas Definidas</h5>
<canvas id="barChart"></canvas>
</div>
</div>

View File

@ -16,8 +16,8 @@
<img class="img-fluid" width="100%" alt="" src={avatar} />
<section class="section">
<div class="section-body">
<p>Twitter del perfil: <br />
@twitter
<p> <br />
</p>
</div>
</section>
@ -34,13 +34,10 @@
</div>
<div class="section-body">
<p>
Vestibulum
volutpat lacus ac magna ullamcorper, id semper sem aliquam. Donec
vestibulum turpis mi, sed ullamcorper lorem feugiat sed. Praesent
ut fringilla dolor. Sed viverra posuere felis eu ullamcorper.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
</div>
@ -53,12 +50,13 @@
<div class="section-header">
<h6 class="pb-2 border-bottom border-accent">Información de contacto:</h6>
</div>
<div class="section-body">
<!-- <div class="section-body">
<p class="mb-1">Sitio Web: <a href="http://wwww.example.com">http://wwww.example.com</a></p>
<p class="mb-1">Twitter: @twitter</p>
<p class="mb-2">Facebook: facebook/ejemplo</p><a class="btn btn-primary btn-block" href={"#"}>Ir al
sitio</a>
</div>
-->
</section>
</div>
</div>

View File

@ -21,6 +21,7 @@ import PageRutas from "$/pages/rutas/Home.svelte";
import { getPermisosApp } from '$/services/usuarios'
import { storePermisos } from '$/stores/global'
import PageTipoCargo from '$/pages/tipo_cargo/Admin.svelte'
import PageReporteItinerario from '$/pages/reportes/Itinerario.svelte'
export const routes_base = [
{ path: '/', component: PageHome, public: true },
@ -43,6 +44,7 @@ export const routes_base = [
{ path: '/paraderos', component: PageParaderos },
{ path: '/rutas', component: PageRutas },
{ path: '/tipo-cargo', component: PageTipoCargo },
{ path: '/reporte/itinerario', component: PageReporteItinerario },
{ path: '*', component: PageError, public: true },
];

View File

@ -0,0 +1,68 @@
import { base, getToken } from './_config'
export async function getUsuarios(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/usuarios/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getUsuario(id) {
const res = await fetch(`${base}/usuarios/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getPermisosPath(path) {
const res = await fetch(`${base}/usuarios/permisos/`, {
method: 'POST',
body: JSON.stringify({ path }),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getPermisosApp() {
const res = await fetch(`${base}/usuarios/permisos/`, {
method: 'POST',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createUsuario(data) {
const res = await fetch(`${base}/usuarios/`, {
method: 'POST',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function updateUsuario({ login: id = null, ...data }) {
const res = await fetch(`${base}/usuarios/${id}/`, {
method: 'PATCH',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function deleteUsuario(id) {
const res = await fetch(`${base}/usuarios/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.text()
}

View File

@ -86,6 +86,16 @@ export async function getCountBuses(params) {
}
export async function getCountLineasCorrectas(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/lineas/count_lineas_correctas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getCountBusesRecorridos(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/lineas/count_buses_recorridos/${query}`, {
@ -93,4 +103,14 @@ export async function getCountBusesRecorridos(params) {
})
if (!res.ok) throw await res.text()
return res.json()
}
}
export async function getCountRutasBuses(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/lineas/count_rutas_buses/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -17,4 +17,13 @@ export async function getRutas(params) {
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getCoordenadasIniciales(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/mapas/coordenadas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text( )
return res.json()
}

View File

@ -132,4 +132,14 @@ export async function getCount(params) {
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getCountByComuna(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/paraderos/count_by_comuna/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}