modal lineas se deja junto a modal del operador como pestaña

francisco/photos
Francisco Sandoval 2024-02-04 17:20:47 -03:00
parent e83bde6c69
commit e0a12ee7e4
14 changed files with 308 additions and 262 deletions

View File

@ -0,0 +1,33 @@
<script>
import { createEventDispatcher } from "svelte";
import Modal from "./Modal.svelte";
import { getGtfsArchivo } from "$/services/gtfs_archivo";
const dispatch = createEventDispatcher();
let open = true
async function onRefrescar() {
try {
const rows = await getGtfsArchivo({ status: 'PROCESANDO' })
open = rows.length > 0
!open && dispatch('close')
} catch (error) {
console.log({ error })
globalThis.toast.error('Error en consulta de estado de actualizacion')
}
}
onRefrescar()
</script>
{#if open}
<Modal title="Actualizandose..."
classPosition='modal-dialog-centered'
showButtonClose={false}>
<div class="text-center">
<button class="btn btn-primary" on:click|preventDefault={onRefrescar}>
<i class="bi bi-arrow-repeat"></i> Refrescar
</button>
</div>
</Modal>
{/if}

View File

@ -9,6 +9,8 @@
export let classBody = ""; export let classBody = "";
export let classFooter = ""; export let classFooter = "";
export let size = ""; export let size = "";
export let classPosition = "";
export let showButtonClose = true
</script> </script>
<!-- Modal --> <!-- Modal -->
@ -18,13 +20,15 @@
aria-hidden="true" aria-hidden="true"
style="display: block; background-color: rgb(0 0 0 / 30%)" style="display: block; background-color: rgb(0 0 0 / 30%)"
> >
<div class={"modal-dialog " + (size ? " modal-" + size : "")}> <div class={"modal-dialog " + (size ? " modal-" + size : "") + ' ' + classPosition}>
<div class="modal-content"> <div class="modal-content">
<div class={"modal-header " + classHeader}> <div class={"modal-header " + classHeader}>
<h5 class={"modal-title " + classTitle}>{title}</h5> <h5 class={"modal-title " + classTitle}>{title}</h5>
<button type="button" class="btn-close" aria-label="Close" {#if showButtonClose}
on:click|preventDefault={() => dispatch("close")}> <button type="button" class="btn-close" aria-label="Close"
</button> on:click|preventDefault={() => dispatch("close")}>
</button>
{/if}
</div> </div>
<div class={"modal-body " + classBody}> <div class={"modal-body " + classBody}>
<slot /> <slot />
@ -32,11 +36,14 @@
<div class={"modal-footer " + classFooter}> <div class={"modal-footer " + classFooter}>
<slot name="buttons" /> <slot name="buttons" />
<div class="me-auto" /> <div class="me-auto" />
<button {#if showButtonClose}
type="button" <button
class="btn btn-outline-secondary" type="button"
on:click|preventDefault={() => dispatch("close")}>Cerrar</button class="btn btn-outline-secondary"
> on:click|preventDefault={() => dispatch("close")}
>Cerrar
</button>
{/if}
</div> </div>
</div> </div>
</div> </div>

View File

@ -20,12 +20,14 @@
import { getInfoToken } from '$/services/login' import { getInfoToken } from '$/services/login'
import { storeSession } from '$/stores/global' import { storeSession } from '$/stores/global'
import Toast from '$/components/Toast.svelte'; import Toast from '$/components/Toast.svelte';
import ActualizandoGtfs from '$/components/ActualizandoGtfs.svelte';
let triggerEvent = false; let triggerEvent = false;
// @ts-ignore // @ts-ignore
const history = createHistory(hashHistory()) const history = createHistory(hashHistory())
let routes = [] let routes = []
let actualizando = true
getRoutes() getRoutes()
.then(data => routes = data) .then(data => routes = data)
@ -55,12 +57,15 @@
<main class="content"> <main class="content">
<div class="container-fluid p-0"> <div class="container-fluid p-0">
{#each routes as r, index} {#if actualizando}
<Route path={r.path} > <ActualizandoGtfs on:close={() => actualizando = false} />
<svelte:component this={r.component} /> {:else}
</Route> {#each routes as r, index}
{/each} <Route path={r.path} >
<svelte:component this={r.component} />
</Route>
{/each}
{/if}
</div> </div>
</main> </main>

View File

@ -22,7 +22,7 @@
]; ];
const operadores = await Promise.all( const operadores = await Promise.all(
id_operadores.map((id_operador) => getOperador(id_operador)), id_operadores.filter(id_operador => id_operador !== null).map((id_operador) => getOperador(id_operador)),
); );
servicios = resultado.map((el) => { servicios = resultado.map((el) => {

View File

@ -10,9 +10,9 @@
import { getMarcasParaderos } from "$/services/mapas" import { getMarcasParaderos } from "$/services/mapas"
// libs // libs
import { onMount } from 'svelte'
import { storeParaderos } from "$/stores/global" import { storeParaderos } from "$/stores/global"
import imagenParada from '$/assets/parada.png' import imagenParada from '$/assets/parada.png'
import { onMount } from "svelte";
let myMap = null let myMap = null
let elMap = null let elMap = null

View File

@ -3,7 +3,6 @@
import { getOperadores } from "$/services/operadores"; import { getOperadores } from "$/services/operadores";
import PageTitle from "$/components/PageTitle.svelte"; import PageTitle from "$/components/PageTitle.svelte";
import ModalOperador from "./ModalOperador.svelte"; import ModalOperador from "./ModalOperador.svelte";
import ModalOperadorLineas from "./ModalOperadorLineas.svelte";
import { useLocation } from "svelte-navigator"; import { useLocation } from "svelte-navigator";
import { getPermisosPath } from "$/services/usuarios"; import { getPermisosPath } from "$/services/usuarios";
@ -89,7 +88,6 @@
{#if ordering === 'vigente'}<i class="bi bi-caret-up-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} {#if ordering === '-vigente'}<i class="bi bi-caret-down-fill"></i>{/if}
</th> </th>
<th><a href={"#"}>Lineas</a></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -99,8 +97,6 @@
<td>{app.id_operador}</td> <td>{app.id_operador}</td>
<td><a href={"#"} on:click|preventDefault={() => onEdita(app)}>{app.nombre_operador}</a></td> <td><a href={"#"} on:click|preventDefault={() => onEdita(app)}>{app.nombre_operador}</a></td>
<td>{app.vigente ? '✅':'🚫'}</td> <td>{app.vigente ? '✅':'🚫'}</td>
<td align="center"><a href={"#"} on:click|preventDefault={() => onVerLineas(app)}><i class="bi bi-bus-front-fill"></i></a></td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
@ -119,23 +115,12 @@
</div> </div>
</div> </div>
{#if operador}
{#if verLineas}
{#if operador}
<ModalOperadorLineas
{operador}
{escritura}
on:close={() => operador = null}
on:close={() => verLineas = null}
on:refresh={() => onPage(page)}
/>
{/if}
{:else if operador}
<ModalOperador <ModalOperador
{operador} {operador}
{escritura} {escritura}
on:close={() => operador = null} on:close={() => operador = null}
on:refresh={() => onPage(page)} on:refresh={() => onPage(page)}
/> />
{/if} {/if}

View File

@ -1,22 +1,48 @@
<script> <script>
import Modal from "../../components/Modal.svelte"; import Modal from "../../components/Modal.svelte";
import { getOperador, createOperador, updateOperador, deleteOperador } from "$/services/operadores"; import {
getOperador,
createOperador,
updateOperador,
deleteOperador,
} from "$/services/operadores";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import TabOperador from "./TabOperador.svelte";
import "./modal.css";
import TabLineas from "./TabLineas.svelte";
import { getLineas, updateLinea } from "$/services/lineas";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let operador = {}; export let operador = {};
export let escritura = false; export let escritura = false;
let form = {} let form = {};
let lineas = []
let loading = false; let loading = false;
let tab = 0;
$: begin(operador.id_operador) $: tab === 0 && begin(operador.id_operador);
$: tab === 1 && fetchLineas()
async function begin(id) { async function begin(id) {
try { try {
if (!id) return; if (!id) return;
form = await getOperador(id) || {} form = (await getOperador(id)) || {};
} catch (error) { } catch (error) {
globalThis.toast.success(error.detail || error) globalThis.toast.success(error.detail || error);
}
}
async function fetchLineas() {
try {
lineas = []
const data = await getLineas({ ordering: 'route_long_name' })
lineas = data.map(linea => ({
...linea,
activo: linea.id_operador == operador.id_operador,
modificado: false,
}));
} catch (error) {
globalThis.toast.error(error.detail || error);
} }
} }
@ -24,19 +50,15 @@
try { try {
loading = true; loading = true;
if (operador.id_operador) { if (operador.id_operador) {
form = await updateOperador(form) form = await updateOperador(form);
} else { } else {
form = await createOperador(form) form = await createOperador(form);
} }
globalThis.toast.success('Se ha guardado el Operador') globalThis.toast.success("Se ha guardado el Operador");
dispatch('refresh') dispatch("refresh");
dispatch('close') dispatch("close");
} catch (error) { } catch (error) {
if (error.detail) { globalThis.toast.error(error.detail || error);
globalThis.toast.success(error.detail)
} else {
globalThis.toast.success(JSON.stringify(error))
}
} finally { } finally {
loading = false; loading = false;
} }
@ -44,14 +66,41 @@
async function onDelete() { async function onDelete() {
try { try {
if (!confirm('Eliminará el registro?')) return; if (!confirm("Eliminará el registro?")) return;
loading = true; loading = true;
await deleteOperador(form.id_operador) await deleteOperador(form.id_operador);
globalThis.toast.success('Se ha eliminado el Operador') globalThis.toast.success("Se ha eliminado el Operador");
dispatch('refresh') dispatch("refresh");
dispatch('close') dispatch("close");
} catch (error) { } catch (error) {
globalThis.toast.error(error.detail || error) globalThis.toast.error(error.detail || error);
} finally {
loading = false;
}
}
async function onSaveLineas() {
try {
loading = true;
const lineasModificadas = lineas.filter(linea => linea.modificado).map(linea => ({
id_linea: linea.id_linea,
id_operador: linea.activo ? operador.id_operador : null, // Establece id_operador basado en 'activo'.
}));
if (operador.id_operador) {
for (let ln of lineasModificadas) {
form = await updateLinea(ln);
}
}
globalThis.toast.success("Se han guardado las lineas asociadas ");
} catch (error) {
if (error.detail) {
globalThis.toast.error(error.detail);
} else {
globalThis.toast.error(JSON.stringify(error));
}
} finally { } finally {
loading = false; loading = false;
} }
@ -59,43 +108,63 @@
</script> </script>
<form action="" on:submit|preventDefault={onSave}> <form action="" on:submit|preventDefault={onSave}>
<Modal title={'Operador #' + (operador.id_operador || 'Nuevo')} <Modal
title={"Operador #" + (operador.id_operador || "Nuevo")}
size="lg" size="lg"
on:close={() => dispatch('close')}> classBody="mymodal"
<div class={"form" + (escritura ? '' : ' disabled')}> on:close={() => dispatch("close")}
<div class="row mb-3"> >
<div class="col-md-3">ID</div> <ul class="nav nav-tabs">
<div class="col-md"> <li class="nav-item">
{#if operador.id_operador} <a
<input type="text" value={form.id_operador} disabled class="form-control"> class={"nav-link" + (tab === 0 ? " active" : "")}
{:else} href={"#"}
<input type="text" bind:value={form.id_operador} required class="form-control"> on:click|preventDefault={() => (tab = 0)}>Operador</a
{/if} >
</div> </li>
</div> <li class={"nav-item" + (form.id_operador ? "" : " disabled")}>
<div class="row mb-3"> <a
<div class="col-md-3">Nombre</div> class={"nav-link" + (tab === 1 ? " active" : "")}
<div class="col-md"> href={"#"}
<input type="text" bind:value={form.nombre_operador} required class="form-control"> on:click|preventDefault={() => (tab = 1)}>Lineas</a
</div> >
</div> </li>
</ul>
<div class="mb-3"> <div class="p-3 bg-white">
<div class="form-check form-switch"> {#if tab === 0}
<input class="form-check-input" type="checkbox" bind:checked={form.vigente} role="switch" id="vigente"> <TabOperador {operador} bind:form={form} {escritura} />
<label class="form-check-label" for="vigente">Vigente</label> {/if}
</div> {#if tab === 1}
</div> <TabLineas {escritura} bind:lineas={lineas} />
{/if}
</div> </div>
<svelte:fragment slot="buttons"> <svelte:fragment slot="buttons">
{#if escritura} {#if escritura && tab === 0}
<button class="btn btn-primary"type="submit" disabled={loading}>Guardar</button> <button class="btn btn-primary" type="submit" disabled={loading}
<button class="btn btn-danger" on:click|preventDefault={onDelete} disabled={loading}>Eliminar</button> >Guardar</button
>
<button
class="btn btn-danger"
on:click|preventDefault={onDelete}
disabled={loading}>Eliminar</button
>
{/if}
{#if escritura && tab === 1}
<button
class="btn btn-primary"
type="button"
on:click|preventDefault={onSaveLineas}
disabled={loading}>Guardar</button
>
{/if} {/if}
</svelte:fragment> </svelte:fragment>
</Modal> </Modal>
</form> </form>
<style> <style>
.disabled { pointer-events: none !important; } .disabled {
pointer-events: none !important;
}
</style> </style>

View File

@ -1,148 +0,0 @@
<script>
import Modal from "../../components/Modal.svelte";
import { getOperador } from "$/services/operadores";
import { getLinea, getLineas, updateLinea } from "$/services/lineas";
import { createEventDispatcher } from "svelte";
import TableResponsive from "$/components/TableResponsive.svelte";
const dispatch = createEventDispatcher();
export let operador = {};
export let escritura = false;
let form = {}
let loading = false;
let lineas=[];
let lineasNull=[];
//console.log({ operador })
$: begin(operador.id_operador )
async function begin(id) {
try {
if (!id) return;
form = await getOperador(id) || {}
} catch (error) {
globalThis.toast.success(error.detail || error)
}
}
getLineas()
.then(data => {
lineas = data.filter(linea =>
linea.id_operador === operador.id_operador || linea.id_operador === null
);
lineas = lineas.map(linea => ({
...linea,
activo: linea.id_operador !== null,
modificado: false ,
}));
// Ordenar las líneas por route_long_name
lineas = lineas.sort((a, b) => {
if (a.route_long_name < b.route_long_name) return -1;
if (a.route_long_name > b.route_long_name) return 1;
return 0;
});
})
.catch(error => globalThis.toast.error(error));
function toggleCheckbox(linea, event) {
const newState = event.target.checked;
linea.activo = newState;
linea.modificado = true;
lineas = lineas; // Asignar el array a sí mismo para asegurar la reactividad en Svelte
//console.log({ linea })
return
}
async function onSave() {
const lineasModificadas = lineas
.filter(linea => linea.modificado)
.map(linea => ({
id_linea: linea.id_linea,
id_operador: linea.activo ? operador.id_operador : null // Establece id_operador basado en 'activo'.
}));
try {
loading = true;
if (operador.id_operador)
{
for (let ln of lineasModificadas)
{
//console.log({ ln })
form = await updateLinea(ln)
}
}
globalThis.toast.success('Se han guardado las lineas asociadas ')
dispatch('refresh')
dispatch('close')
} catch (error) {
if (error.detail) {
globalThis.toast.success(error.detail)
} else {
globalThis.toast.success(JSON.stringify(error))
}
} finally {
loading = false;
}
}
</script>
<form action="" on:submit|preventDefault={onSave}>
<Modal title={'Lineas Asociadas a ' + (operador.nombre_operador )}
size="lg"
on:close={() => dispatch('close')} >
<div class={"form" + (escritura ? '' : ' disabled')}>
<TableResponsive>
<thead>
<tr>
<th>Linea</th>
<th>Asociada</th>
</tr>
</thead>
<tbody>
{#each lineas as linea}
<tr>
<td>{linea.route_long_name}</td>
<td>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
bind:checked = {linea.activo}
on:change={(event) => toggleCheckbox(linea, event)}
id={`check-linea-${linea.id_linea}`}>
<label class="form-check-label" for={`check-linea-${linea.id_linea}`}>&nbsp;</label>
</div>
</td>
</tr>
{/each}
</tbody>
</TableResponsive>
</div>
<svelte:fragment slot="buttons">
{#if escritura}
<button class="btn btn-primary" type="submit" disabled={loading}>Guardar</button>
{/if}
</svelte:fragment>
</Modal>
</form>
<style>
.disabled { pointer-events: none !important; }
</style>

View File

@ -0,0 +1,44 @@
<script>
import TableResponsive from "$/components/TableResponsive.svelte";
export let lineas = [];
export let escritura = false;
function toggleCheckbox(linea, event) {
const newState = event.target.checked;
linea.activo = newState;
linea.modificado = true;
lineas = lineas; // Asignar el array a sí mismo para asegurar la reactividad en Svelte
}
</script>
<div class={"form" + (escritura ? "" : " disabled")}>
<TableResponsive>
<thead>
<tr>
<th>Linea</th>
<th>Asociada</th>
</tr>
</thead>
<tbody>
{#each lineas as linea}
<tr>
<td>{linea.route_long_name}</td>
<td>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
bind:checked={linea.activo}
on:change={(event) => toggleCheckbox(linea, event)}
id={`check-linea-${linea.id_linea}`}
/>
<label class="form-check-label" for={`check-linea-${linea.id_linea}`}>&nbsp;</label>
</div>
</td>
</tr>
{/each}
</tbody>
</TableResponsive>
</div>

View File

@ -0,0 +1,32 @@
<script>
export let form = {}
export let operador = {}
export let escritura = false
</script>
<div class={"form" + (escritura ? '' : ' disabled')}>
<div class="row mb-3">
<div class="col-md-3">ID</div>
<div class="col-md">
{#if operador.id_operador}
<input type="text" value={form.id_operador} disabled class="form-control">
{:else}
<input type="text" bind:value={form.id_operador} required class="form-control">
{/if}
</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Nombre</div>
<div class="col-md">
<input type="text" bind:value={form.nombre_operador} required class="form-control">
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" bind:checked={form.vigente} role="switch" id="vigente">
<label class="form-check-label" for="vigente">Vigente</label>
</div>
</div>
</div>

View File

@ -0,0 +1,12 @@
.disabled { pointer-events: none !important; }
.mymodal { background-color: var(--bs-gray-200); }
.nav-tabs {
--bs-nav-tabs-border-color: var(--bs-white);
--bs-nav-tabs-border-width: 0;
}
.nav-tabs .nav-link.active {
--bs-nav-tabs-link-active-bg: var(--bs-white);
--bs-nav-tabs-link-active-color: var(--bs-secondary);
--bs-nav-tabs-link-active-border-color: var(--bs-white);
}

View File

@ -4,8 +4,8 @@
import { getLinea } from "../../services/lineas"; import { getLinea } from "../../services/lineas";
import ModalFormRuta from "./ModalFormRuta.svelte"; import ModalFormRuta from "./ModalFormRuta.svelte";
import TableRutas from "./TableRutas.svelte"; import TableRutas from "./TableRutas.svelte";
let loading = false
let loading = false
let ruta = null let ruta = null
async function onEdita({ linea: { id_linea } }) { async function onEdita({ linea: { id_linea } }) {
@ -22,7 +22,6 @@
} }
</script> </script>
<PageTitle {loading}>Rutas de Buses</PageTitle> <PageTitle {loading}>Rutas de Buses</PageTitle>
<TableRutas on:loading={ev => loading = ev.detail} on:select={ev => onEdita(ev.detail)} /> <TableRutas on:loading={ev => loading = ev.detail} on:select={ev => onEdita(ev.detail)} />

View File

@ -5,40 +5,47 @@
import CantidadParaderosComuna from "./CantidadParaderosComuna.svelte"; import CantidadParaderosComuna from "./CantidadParaderosComuna.svelte";
import CantidadBusesRecorrido from "./CantidadBusesRecorrido.svelte"; import CantidadBusesRecorrido from "./CantidadBusesRecorrido.svelte";
import CantidadBusesLinea from "./CantidadBusesLinea.svelte"; import CantidadBusesLinea from "./CantidadBusesLinea.svelte";
import ActualizandoGtfs from "$/components/ActualizandoGtfs.svelte";
let loading1 = false let loading1 = false
let loading2 = false let loading2 = false
let loading3 = false let loading3 = false
let loading4 = false let loading4 = false
let loading5 = false let loading5 = false
let actualizando = true
</script> </script>
<PageTitle {#if actualizando}
loading={loading1 || loading2 || loading3 || loading4 || loading5 } <ActualizandoGtfs on:close={() => actualizando = false} />
><strong>Análisis</strong> de datos</PageTitle> {:else}
<PageTitle
loading={loading1 || loading2 || loading3 || loading4 || loading5 }
><strong>Análisis</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">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<CantidadLineas on:loading={ev => loading1 = ev.detail} /> <CantidadLineas on:loading={ev => loading1 = ev.detail} />
</div>
<div class="col">
<CantidadParaderos on:loading={ev => loading2 = ev.detail} />
</div>
</div> </div>
<div class="col">
<CantidadParaderos on:loading={ev => loading2 = ev.detail} /> <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> </div>
</div> </div>
<div class="row"> <div class="col-xs-12 col-md-5">
<div class="col"> <CantidadBusesLinea on:loading={ev => loading5 = ev.detail} />
<CantidadParaderosComuna on:loading={ev => loading3 = ev.detail} />
</div>
<div class="col">
<CantidadBusesRecorrido on:loading={ev => loading4 = ev.detail} />
</div>
</div> </div>
</div> </div>
{/if}
<div class="col-xs-12 col-md-5">
<CantidadBusesLinea on:loading={ev => loading5 = ev.detail} />
</div>
</div>

View File

@ -4,6 +4,7 @@
import PageTitle from '$/components/PageTitle.svelte' import PageTitle from '$/components/PageTitle.svelte'
import { getPermisosPath, getUsuarios } from '$/services/usuarios' import { getPermisosPath, getUsuarios } from '$/services/usuarios'
import { storePermisoApp } from '$/stores/global'; import { storePermisoApp } from '$/stores/global';
import ActualizandoGtfs from '$/components/ActualizandoGtfs.svelte';
let usuarios = { count: 0, results: [] } let usuarios = { count: 0, results: [] }
let page = 1 let page = 1