se agrega mantenedor de rutas y paradero-servicios en mapa

develop/frontend
Francisco Sandoval 2023-11-01 09:53:29 -03:00
parent 0865f41f1a
commit 3934519a77
15 changed files with 515 additions and 51 deletions

View File

@ -49,9 +49,9 @@
<span class="align-middle">Aplicaciones</span>
</SideLink>
<SideLink to="/lineas">
<i class="align-middle bi bi-terminal fs-4" />
<span class="align-middle">Rutas de Buses</span>
<SideLink to="/rutas">
<i class="align-middle bi bi-sign-turn-right fs-4"></i>
<span class="align-middle">Servicios de Buses</span>
</SideLink>
<SideLink to="/comunas">

View File

@ -1,35 +0,0 @@
<script>
import PageTitle from "../../components/PageTitle.svelte";
import TableResponsive from "../../components/TableResponsive.svelte";
let loading = false;
</script>
<PageTitle {loading}>Rutas de Buses</PageTitle>
<div class="card">
<div class="card-header">
<button class="btn btn-outline-secondary"><i class="bi bi-plus-lg"></i> Nuevo</button>
</div>
<div class="card-body">
<TableResponsive>
<thead>
<tr>
<th>ID</th>
<th>Operador</th>
<th>LUR</th>
<th>Dirección</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</TableResponsive>
</div>
<div class="card-footer"></div>
</div>

View File

@ -1,6 +1,4 @@
<script>
// imagenes
import IconParada from "$/assets/parada-de-autobus.png";
// servicios
import {
getParadero,

View File

@ -24,7 +24,7 @@
</table>
<div class="text-center mb-3">
<img loading="lazy" width="300" height="300" src="https://gravatar.com/avatar/2db5ce9c15a9178854325b844485702b?s=400&d=wavatar&r=x" alt="">
<img loading="lazy" width="300" height="300" src="https://play-lh.googleusercontent.com/lomBq_jOClZ5skh0ELcMx4HMHAMW802kp9Z02_A84JevajkqD87P48--is1rEVPfzGVf=w240-h480-rw" alt="">
</div>
<div class="text-center">
<button class="btn btn-secondary">Imprimir QR</button>

View File

@ -1,16 +1,101 @@
<script>
import IconLoading from "../../components/IconLoading.svelte";
import { getLinea } from "../../services/lineas";
import { getOperador } from "../../services/operadores";
import { createLineaParadero, deleteLineaParadero, getLineasParadero } from "../../services/paraderos";
import ModalRutas from "../rutas/ModalRutas.svelte";
export let parada = null
let showModalRutas = false
let servicios = []
let loading = false
async function fetchData({id_paradero = null}) {
try {
if (!id_paradero) return;
loading = true
const resultado = await getLineasParadero(id_paradero)
const lineas = await Promise.all(
resultado.map(({id_linea}) => getLinea(id_linea))
)
const id_operadores = [ ...new Set(lineas.map(linea => linea.id_operador)) ]
const operadores = await Promise.all(
id_operadores.map(id_operador => getOperador(id_operador))
)
servicios = resultado.map(el => {
const { id_paradero_linea, id_linea } = el
const linea = lineas.find(el => id_linea === el.id_linea) || {}
const {nombre_operador} = operadores.find(op => op.id_operador === linea.id_operador) || {}
return { id_paradero_linea, ...linea, nombre_operador }
})
} catch (error) {
console.log({ error })
} finally {
loading = false
}
}
$: fetchData(parada)
async function onAgregarRuta({ linea, operador }) {
try {
const { id_paradero_linea} = await createLineaParadero(parada.id_paradero, linea.id_linea)
const { id_linea, route_short_name, route_long_name } = linea;
const { nombre_operador } = operador;
servicios = [ ...servicios, { id_paradero_linea, id_linea, route_short_name, route_long_name, nombre_operador } ]
} catch (error) {
console.log({ error })
}
}
async function onEliminarRuta({ id_paradero_linea }) {
try {
await deleteLineaParadero(id_paradero_linea)
servicios = servicios.filter(el => el.id_paradero_linea !== id_paradero_linea)
} catch (error) {
console.log({ error })
}
}
</script>
<div>
<h4 class="mb-3">Servicios</h4>
<div class="p-3 bg-white">
<button class="btn btn-secondary" on:click|preventDefault={() => showModalRutas = true}>
<i class="bi bi-plus-lg"></i> Agregar Servicio
</button>
<div class="my-3"></div>
{#if loading}
<div>
<IconLoading />
Cargando información
</div>
{/if}
<ul class="list-group">
<li class="list-group-item">Servicio 1</li>
<li class="list-group-item">Servicio 2</li>
<li class="list-group-item">Servicio 3</li>
<li class="list-group-item">Servicio 4</li>
{#each servicios as servicio}
<li class="list-group-item d-flex justify-content-between align-items-start">
<a href={"#"} on:click|preventDefault={() => onEliminarRuta(servicio)}>
<i class="bi bi-trash text-danger"></i>
</a>
<div class="ms-2 me-auto">
<div class="fw-bold">{servicio.nombre_operador}</div>
Destino: {servicio.route_long_name}
</div>
<span class="badge bg-primary rounded-pill">{servicio.route_short_name}</span>
</li>
{/each}
</ul>
</div>
</div>
</div>
<ModalRutas
show={showModalRutas}
on:select={ev => onAgregarRuta(ev.detail)}
on:close={() => showModalRutas = false}
/>

View File

@ -35,7 +35,7 @@
<div class="d-flex justify-content-between">
<h5 class="offcanvas-title text-info" id="offcanvasParaderoLabel">
<img src={IconParada} alt="Icono de Paradero" width="64" />
Paradero
Paradero #{parada?.id_paradero} {parada?.location}
</h5>
<button
type="button"

View File

@ -0,0 +1,164 @@
<script>
export let form = {};
let codigo_dividido = "";
$: codigo_dividido = form.route_short_name
? form.route_short_name.match(/\d+|[a-z]+/gi)
: ["", ""];
</script>
<div class="row">
<div class="col-md">
<div class="input-group mb-3">
<div class="input-group-text">ID</div>
<input
type="text"
class="form-control"
value={form.id_linea}
disabled
/>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Operador</div>
<input
type="text"
class="form-control"
bind:value={form.id_operador}
/>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Código LUR</div>
<input
type="text"
class="form-control"
bind:value={form.route_short_name}
/>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Dirección</div>
<input
type="text"
class="form-control"
bind:value={form.route_long_name}
/>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Color Fondo 1</div>
<input type="text" class="form-control" bind:value={form.bgcolor1}>
<div class="form-control">
<input
type="color"
class="w-100"
bind:value={form.bgcolor1}
/>
</div>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Color Texto 1</div>
<input type="text" class="form-control" bind:value={form.color1}>
<div class="form-control">
<input
type="color"
class="w-100"
bind:value={form.color1}
/>
</div>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Color Fondo 2</div>
<input type="text" class="form-control" bind:value={form.bgcolor2}>
<div class="form-control">
<input
type="color"
class="w-100"
bind:value={form.bgcolor2}
/>
</div>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Color Texto 2</div>
<input type="text" class="form-control" bind:value={form.color2}>
<div class="form-control">
<input
type="color"
class="w-100"
bind:value={form.color2}
/>
</div>
</div>
</div>
<div class="col-md">
<table
class="m-auto"
cellpadding="10"
cellspacing="0"
style="--bgcolor1: {form.bgcolor1}; --color1: {form.color1}; --bgcolor2: {form.bgcolor2}; --color2: {form.color2}"
>
<tbody class="color1">
<tr>
<td rowspan="4" width="80" class="color2 text-center">
<h1 class="m-0">{codigo_dividido[0]}</h1>
<h1 class="m-0">{codigo_dividido[1]}</h1>
</td>
<td
><input
type="text"
class="form-control"
placeholder="Linea 1"
bind:value={form.linea1}
/></td
>
</tr>
<tr>
<td
><input
type="text"
class="form-control"
placeholder="Linea 2"
bind:value={form.linea2}
/></td
>
</tr>
<tr>
<td
><input
type="text"
class="form-control"
placeholder="Linea 3"
bind:value={form.linea3}
/></td
>
</tr>
<tr class="color2">
<td
><input
type="text"
class="form-control"
placeholder="Linea 4"
bind:value={form.linea4}
/></td
>
</tr>
</tbody>
</table>
</div>
</div>
<style>
.color1 {
background-color: var(--bgcolor1);
color: var(--color1);
}
.color2 {
background-color: var(--bgcolor2);
color: var(--color2);
}
</style>

View File

@ -0,0 +1,39 @@
<script>
import PageTitle from "../../components/PageTitle.svelte";
import { getLetreroLUR } from "../../services/letreros_lur";
import { getLinea } from "../../services/lineas";
import ModalFormRuta from "./ModalFormRuta.svelte";
import TableRutas from "./TableRutas.svelte";
let loading = false
let ruta = null
async function onEdita({ linea: { id_linea } }) {
try {
const linea = await getLinea(id_linea)
const letrero = await getLetreroLUR(linea.route_short_name)
ruta = { ...linea, ...letrero }
} catch (error) {
console.log({ error })
}
}
// function onNuevaRuta() {
// ruta = {}
// }
</script>
<PageTitle {loading}>Rutas de Buses</PageTitle>
<TableRutas on:loading={ev => loading = ev.detail} on:select={ev => onEdita(ev.detail)}>
<!-- <svelte:fragment slot="buttons">
<button class="btn btn-outline-secondary" on:click|preventDefault={() => onNuevaRuta()}>
<i class="bi bi-plus-lg"></i> Nuevo
</button>
</svelte:fragment> -->
</TableRutas>
{#if ruta}
<ModalFormRuta {ruta} on:close={() => ruta = null} />
{/if}

View File

@ -0,0 +1,62 @@
<script>
import Modal from "../../components/Modal.svelte";
import { createLetreroLUR, deleteLetreroLUR } from "../../services/letreros_lur";
import { createLinea, deleteLinea, updateLinea } from "../../services/lineas";
import FormRuta from "./FormRuta.svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher()
export let ruta = null;
let form = {}
$: form = { ...ruta }
async function onSave() {
try {
console.log({ ruta })
const { id_linea } = ruta;
const { route_short_name, route_long_name,
linea1, linea2, linea3, linea4,
bgcolor1, color1, bgcolor2, color2
} = form;
if (id_linea) {
await updateLinea({ id_linea, route_short_name, route_long_name })
} else {
await createLinea({ route_short_name, route_long_name })
}
await deleteLetreroLUR(route_short_name)
await createLetreroLUR({
codigo: route_short_name,
linea1, linea2, linea3, linea4,
bgcolor1, color1, bgcolor2, color2
})
dispatch('close')
} catch (error) {
console.log({ error })
}
}
// async function onDelete() {
// try {
// const { id_linea, route_short_name } = ruta;
// await deleteLetreroLUR(route_short_name)
// await deleteLinea(id_linea)
// dispatch('close')
// } catch (error) {
// console.log({ error })
// }
// }
</script>
<Modal title="Ruta de Servicio" size="xl"
on:close={() => dispatch('close')}
>
<FormRuta {form} />
<svelte:fragment slot="buttons">
<button class="btn btn-primary" on:click|preventDefault={onSave}><i class="bi bi-save"></i> Guardar</button>
<!-- {#if ruta.id_linea}
<button class="btn btn-danger" on:click|preventDefault={onDelete}><i class="bi bi-trash"></i> Eliminar</button>
{/if} -->
</svelte:fragment>
</Modal>

View File

@ -0,0 +1,19 @@
<script>
import Modal from "../../components/Modal.svelte";
import TableRutas from "./TableRutas.svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let show = true;
function onSelect(item) {
dispatch('select', item)
dispatch('close')
}
</script>
<div class={show ? '' : 'd-none'}>
<Modal title="Rutas" size="xl" on:close={() => dispatch('close')}>
<TableRutas on:select={ev => onSelect(ev.detail)} />
</Modal>
</div>

View File

@ -0,0 +1,102 @@
<script>
import Paginate from "../../components/Paginate.svelte";
import TableResponsive from "../../components/TableResponsive.svelte";
import { getLineas } from "../../services/lineas";
import { getOperador, getOperadores } from "../../services/operadores";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher()
let loading = false;
let offset = 0;
const limit = 10;
let lineas = { results: [], count: 0 };
let operadores = [];
let filtro = {}
async function fetchOperadores() {
try {
operadores = await getOperadores({ ordering: 'nombre_operador' })
} catch (error) {
alert(error)
}
}
async function fetchData(offset, { id_operador = null }) {
try {
loading = true;
const conditions = { offset, limit, ordering: 'route_short_name' };
if (id_operador) conditions.id_operador = id_operador;
lineas = await getLineas(conditions)
} catch (error) {
alert(error)
} finally {
loading = false
}
}
function getNombreOperador(id) {
const operador = operadores.find(el => el.id_operador === id) || {}
return operador.nombre_operador || ''
}
function onSelect(linea) {
const operador = operadores.find(el => el.id_operador === linea.id_operador) || {}
const data = { linea, operador }
dispatch('select', data)
}
fetchOperadores()
$: fetchData(offset, filtro)
$: dispatch('loading',loading)
</script>
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<slot name="buttons" />
</div>
<div class="col-auto">
<div class="input-group">
<div class="input-group-text">Operador</div>
<select class="form-select" bind:value={filtro.id_operador}>
<option value=""></option>
{#each operadores as operador}
<option value={operador.id_operador}>{operador.nombre_operador}</option>
{/each}
</select>
</div>
</div>
</div>
</div>
<div class="card-body">
<TableResponsive>
<thead>
<tr>
<th>ID</th>
<th>Operador</th>
<th>LUR</th>
<th>Dirección</th>
</tr>
</thead>
<tbody>
{#each lineas.results as linea}
<tr>
<td><a href={"#"} on:click|preventDefault={() => onSelect(linea)}>{linea.id_linea}</a></td>
<td>{getNombreOperador(linea.id_operador)}</td>
<td>{linea.route_short_name}</td>
<td>{linea.route_long_name}</td>
</tr>
{/each}
</tbody>
</TableResponsive>
</div>
<div class="card-footer">
<Paginate
forcePage={offset / limit}
count={lineas.count}
{limit}
on:page={ev => offset = (ev.detail - 1) * limit}
/>
</div>
</div>

View File

@ -14,7 +14,7 @@ import PageMapaRutas from '$/pages/mapas/Rutas.svelte'
import PageRoles from '$/pages/roles/Admin.svelte'
import PageRolesyAplicaciones from '$/pages/rolesaplicaciones/Admin.svelte'
import PageParaderos from '$/pages/paraderos/Home.svelte'
import PageLineas from "$/pages/lineas/Home.svelte";
import PageRutas from "$/pages/rutas/Home.svelte";
export const routes = [
{ path: '/', component: PageHome },
@ -32,6 +32,6 @@ export const routes = [
{ path: '/mapas/paraderos', component: PageMapaParaderos },
{ path: '/mapas/rutas', component: PageMapaRutas },
{ path: '/paraderos', component: PageParaderos },
{ path: '/lineas', component: PageLineas },
{ path: '/rutas', component: PageRutas },
{ path: '*', component: PageError },
]

View File

@ -44,5 +44,5 @@ export async function deleteLetreroLUR(id) {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
return res.text()
}

View File

@ -28,7 +28,7 @@ export async function createLinea(data) {
return res.json()
}
export async function updateLinea({ id_paradero: id = null, ...data }) {
export async function updateLinea({ id_linea: id = null, ...data }) {
const res = await fetch(`${base}/lineas/${id}/`, {
method: 'PATCH',
body: JSON.stringify(data),

View File

@ -81,4 +81,34 @@ export async function deleteParaderoImagen(id_paradero_imagen) {
})
if (!res.ok) throw await res.text()
return res.text()
}
export async function getLineasParadero(id_paradero) {
const res = await fetch(`${base}/paraderos-linea/?id_paradero=${id_paradero}`, {
headers: { "Authorization": `Bearer ${getToken()}` }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createLineaParadero(id_paradero, id_linea) {
const res = await fetch(`${base}/paraderos-linea/`, {
method: 'POST',
body: JSON.stringify({ id_paradero, id_linea }),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function deleteLineaParadero(id_paradero_linea) {
const res = await fetch(`${base}/paraderos-linea/${id_paradero_linea}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}` }
})
if (!res.ok) throw await res.text()
return res.text()
}