Merge branch 'develop/Ronald' into 'master'

Develop/ronald

See merge request m3f_usm/admin_transporte/frontend!3
develop/Ronald
Francisco Sandoval 2024-03-17 22:53:57 +00:00
commit 07031c2a9e
13 changed files with 273 additions and 32 deletions

View File

@ -20,6 +20,7 @@
"history": "^5.3.0",
"svelte-navigator": "^3.2.2",
"svelte-pagination": "^0.0.1",
"svelte-qrcode": "^1.0.0"
"svelte-qrcode": "^1.0.0",
"chart.js": "^3.7.0"
}
}

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

@ -123,6 +123,12 @@
{#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 === '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('vigente')}>Vigente</a>
{#if ordering === 'vigente'}<i class="bi bi-caret-up-fill"></i>{/if}
@ -144,6 +150,7 @@
</td>
<td>{app.created}</td>
<td>{app.status}</td>
<td>{app.valid_from}</td>
<td>{app.vigente ? '✅':'🚫'}</td>
</tr>
{/each}

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

@ -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>

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

@ -15,7 +15,7 @@
<PageTitle
loading={loading1 || loading2 || loading3 || loading4 || loading5 }
><strong>Análisis</strong> de datos</PageTitle>
><strong>Resumen</strong> de datos</PageTitle>
<div class="row">
<div class="col-xs-12 col-md-7">

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

@ -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();
}