Merge branch 'develop/Ronald' into 'master'
Develop/ronald See merge request m3f_usm/admin_transporte/frontend!3develop/Ronald
commit
07031c2a9e
|
@ -20,6 +20,7 @@
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"svelte-navigator": "^3.2.2",
|
"svelte-navigator": "^3.2.2",
|
||||||
"svelte-pagination": "^0.0.1",
|
"svelte-pagination": "^0.0.1",
|
||||||
"svelte-qrcode": "^1.0.0"
|
"svelte-qrcode": "^1.0.0",
|
||||||
|
"chart.js": "^3.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
<div class="row text-muted">
|
<div class="row text-muted">
|
||||||
<div class="col-6 text-start">
|
<div class="col-6 text-start">
|
||||||
<p class="mb-0">
|
<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">
|
target="_blank" rel="noreferrer">
|
||||||
<strong>.</strong>
|
<strong></strong>
|
||||||
</a> ©
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 text-end">
|
<div class="col-6 text-end">
|
||||||
<ul class="list-inline">
|
<ul class="list-inline">
|
||||||
<li class="list-inline-item">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -49,6 +49,13 @@
|
||||||
<span class="align-middle">Listado Archivos GTFS</span>
|
<span class="align-middle">Listado Archivos GTFS</span>
|
||||||
</SideLink>
|
</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>
|
<li class="sidebar-header">Mantenedores</li>
|
||||||
|
|
||||||
<SideLink to="/paraderos">
|
<SideLink to="/paraderos">
|
||||||
|
|
|
@ -123,6 +123,12 @@
|
||||||
{#if ordering === '-status'}<i class="bi bi-caret-down-fill"></i>{/if}
|
{#if ordering === '-status'}<i class="bi bi-caret-down-fill"></i>{/if}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
<a href={"#"} on:click|preventDefault={() => onOrderBy('valid_from')}>Inico Vigencia</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>
|
<th>
|
||||||
<a href={"#"} on:click|preventDefault={() => onOrderBy('vigente')}>Vigente</a>
|
<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-up-fill"></i>{/if}
|
||||||
|
@ -144,6 +150,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{app.created}</td>
|
<td>{app.created}</td>
|
||||||
<td>{app.status}</td>
|
<td>{app.status}</td>
|
||||||
|
<td>{app.valid_from}</td>
|
||||||
<td>{app.vigente ? '✅':'🚫'}</td>
|
<td>{app.vigente ? '✅':'🚫'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -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,' ')} {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>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script>
|
||||||
|
import { getOperadores } from "$/services/operadores";
|
||||||
|
|
||||||
|
export let id_operador;
|
||||||
|
export let loading = false;
|
||||||
|
|
||||||
|
let operadores = [];
|
||||||
|
|
||||||
|
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 = `${window.location.href}/reporte_itinerarios_${id_operador}.pdf`;
|
||||||
|
window.open(url, '_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`;
|
||||||
|
window.open(url, '_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>
|
|
@ -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>
|
|
@ -74,7 +74,7 @@
|
||||||
|
|
||||||
<div class="card flex-fill w-100">
|
<div class="card flex-fill w-100">
|
||||||
<div class="card-header">
|
<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>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col mt-0">
|
<div class="col mt-0">
|
||||||
<h5 class="card-title">Cantidad de Líneas</h5>
|
<h5 class="card-title">Cantidad de Rutas</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
|
|
@ -1,31 +1,46 @@
|
||||||
|
<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">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<h5 class="card-title">Paraderos por Comuna </h5>
|
||||||
<div class="col mt-0">
|
<div class="col-auto">
|
||||||
<h5 class="card-title">Paraderos por comuna</h5>
|
<div class="stat text-primary">
|
||||||
</div>
|
<i class="bi bi-map"></i>
|
||||||
|
|
||||||
<div class="col-auto">
|
|
||||||
<div class="stat text-primary">
|
|
||||||
<i class="bi bi-geo-alt fs-4"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mt-1 mb-3">2.382</h1>
|
<table class="table table-striped table-hover table-responsive">
|
||||||
<table class="table mb-0">
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Comuna</th>
|
||||||
|
<th>Total Paraderos</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{#each comunasCounts as { id_comuna__nombre_comuna, total }}
|
||||||
<td>Chrome</td>
|
<tr>
|
||||||
<td class="text-end">4306</td>
|
<td>{id_comuna__nombre_comuna}</td>
|
||||||
</tr>
|
<td>{total}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td>Firefox</td>
|
{/each}
|
||||||
<td class="text-end">3801</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>IE</td>
|
|
||||||
<td class="text-end">1689</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<PageTitle
|
<PageTitle
|
||||||
loading={loading1 || loading2 || loading3 || loading4 || loading5 }
|
loading={loading1 || loading2 || loading3 || loading4 || loading5 }
|
||||||
><strong>Análisis</strong> de datos</PageTitle>
|
><strong>Resumen</strong> de datos</PageTitle>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-7">
|
<div class="col-xs-12 col-md-7">
|
||||||
|
|
|
@ -21,6 +21,7 @@ import PageRutas from "$/pages/rutas/Home.svelte";
|
||||||
import { getPermisosApp } from '$/services/usuarios'
|
import { getPermisosApp } from '$/services/usuarios'
|
||||||
import { storePermisos } from '$/stores/global'
|
import { storePermisos } from '$/stores/global'
|
||||||
import PageTipoCargo from '$/pages/tipo_cargo/Admin.svelte'
|
import PageTipoCargo from '$/pages/tipo_cargo/Admin.svelte'
|
||||||
|
import PageReporteItinerario from '$/pages/reportes/Itinerario.svelte'
|
||||||
|
|
||||||
export const routes_base = [
|
export const routes_base = [
|
||||||
{ path: '/', component: PageHome, public: true },
|
{ path: '/', component: PageHome, public: true },
|
||||||
|
@ -43,6 +44,7 @@ export const routes_base = [
|
||||||
{ path: '/paraderos', component: PageParaderos },
|
{ path: '/paraderos', component: PageParaderos },
|
||||||
{ path: '/rutas', component: PageRutas },
|
{ path: '/rutas', component: PageRutas },
|
||||||
{ path: '/tipo-cargo', component: PageTipoCargo },
|
{ path: '/tipo-cargo', component: PageTipoCargo },
|
||||||
|
{ path: '/reporte/itinerario', component: PageReporteItinerario },
|
||||||
{ path: '*', component: PageError, public: true },
|
{ path: '*', component: PageError, public: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -133,3 +133,13 @@ export async function getCount(params) {
|
||||||
if (!res.ok) throw await res.text()
|
if (!res.ok) throw await res.text()
|
||||||
return res.json()
|
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();
|
||||||
|
}
|
Loading…
Reference in New Issue