commit inicial

francisco/photos
Francisco Sandoval 2023-12-05 10:37:40 -03:00
commit ec34d983a2
101 changed files with 6031 additions and 0 deletions

24
.gitignore vendored 100644
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored 100644
View File

@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}

47
README.md 100644
View File

@ -0,0 +1,47 @@
# Svelte + Vite
This template should help get you started developing with Svelte in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## Need an official Svelte framework?
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
## Technical considerations
**Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
**Why include `.vscode/extensions.json`?**
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
**Why enable `checkJs` in the JS template?**
It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration.
**Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/sveltejs/svelte-hmr/tree/master/packages/svelte-hmr#preservation-of-local-state).
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
```js
// store.js
// An extremely simple external store
import { writable } from 'svelte/store'
export default writable(0)
```

View File

@ -0,0 +1,20 @@
version: "3"
name: transporte-frontend
services:
app:
image: node:18-alpine
volumes:
- ../:/app
ports:
- 3000:3000
environment:
- VITE_PORT=3000
- VITE_BACKEND=http://localhost:4000/api
# - VITE_BACKEND=http://transporte.hz.kursor.cl/api
working_dir: /app
command: sh -c "
[ ! -d node_modules ] && npm install ;
chmod -R o+w . ;
npm run dev
"

12
index.html 100644
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>...</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

37
jsconfig.json 100644
View File

@ -0,0 +1,37 @@
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ESNext",
"module": "ESNext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"verbatimModuleSyntax": true,
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true,
"baseUrl": ".",
"paths": {
"$/*": ["src/*"]
}
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

1002
package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

25
package.json 100644
View File

@ -0,0 +1,25 @@
{
"name": "app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "VITE_BACKEND= vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.4",
"svelte": "^3.58.0",
"vite": "^4.3.9"
},
"dependencies": {
"@adminkit/core": "^3.4.0",
"bootstrap": "^5.3.0",
"bootstrap-icons": "^1.10.5",
"history": "^5.3.0",
"svelte-navigator": "^3.2.2",
"svelte-pagination": "^0.0.1",
"svelte-qrcode": "^1.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

View File

@ -0,0 +1,74 @@
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400");
@import url("https://fonts.googleapis.com/css?family=Roboto+Slab:300,400");
main .container { padding-top: 2rem; }
footer { height: auto; }
footer>.container { padding-bottom: 3rem; }
footer>.container>.row { display: block; }
:root {
--bs-body-font-family: "Roboto Slab",serif
}
.sidebar, .sidebar-content {
background: #006FB3;
}
.sidebar-link, a.sidebar-link {
background: rgba(255,255,255,.05);
color: rgba(255,255,255,.8);
}
.sidebar-item.active .sidebar-link:hover, .sidebar-item.active>.sidebar-link {
background: rgba(0,0,0,.05);
border-left-color: #FE6565;
}
.navbar-bg {
position: relative;
background: #cce0ef;
}
.navbar-bg::after {
position: absolute;
width: 100%;
height: 2px;
bottom: -2px;
left: 0;
right: 0;
content: ' ';
background: linear-gradient(to right,#0f69b4 0,#0f69b4 50%,#e22c2c 50%,#e22c2c 100%);
}
h3::after {
content: ' #';
color: #006FB3;
}
footer { position: relative; }
footer::after {
position: absolute;
width: 100px;
height: 4px;
bottom: 0;
left: 1rem;
content: ' ';
background: linear-gradient(to right,#0f69b4 0,#0f69b4 50%,#e22c2c 50%,#e22c2c 100%);
}
footer.footer { background: #0A132D; }
.text-muted { color: #fff !important; }
input:required:invalid,
select:required:invalid,
textarea:required:invalid
{
border-color: var(--bs-danger);
}
input:required:valid,
select:required:valid,
textarea:required:valid
{
border-color: var(--bs-success);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>

View File

@ -0,0 +1,43 @@
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let title = "modal title";
export let className = "";
export let classTitle = "";
export let classHeader = "";
export let classBody = "";
export let classFooter = "";
export let size = "";
</script>
<!-- Modal -->
<div
class={"modal show " + className}
tabindex="-1"
aria-hidden="true"
style="display: block; background-color: rgb(0 0 0 / 30%)"
>
<div class={"modal-dialog " + (size ? " modal-" + size : "")}>
<div class="modal-content">
<div class={"modal-header " + classHeader}>
<h5 class={"modal-title " + classTitle}>{title}</h5>
<button type="button" class="btn-close" aria-label="Close"
on:click|preventDefault={() => dispatch("close")}>
</button>
</div>
<div class={"modal-body " + classBody}>
<slot />
</div>
<div class={"modal-footer " + classFooter}>
<slot name="buttons" />
<div class="me-auto" />
<button
type="button"
class="btn btn-outline-secondary"
on:click|preventDefault={() => dispatch("close")}>Cerrar</button
>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
<script>
import { onMount, createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher();
export let google_api_key = ''
let mapEl = null
onMount(() => {
if (!globalThis.google) {
const el = document.createElement('script')
el.src = "https://maps.googleapis.com/maps/api/js?key=" + google_api_key + "&callback=initMap&v=weekly"
document.body.appendChild(el)
} else {
globalThis.initMap()
}
})
globalThis.initMap = function() {
const google = globalThis.google;
const google_map = new google.maps.Map(mapEl);
dispatch('start', google_map)
}
</script>
<div bind:this={mapEl} style="height: 800px; max-height: 90vh;">Cargando...</div>

View File

@ -0,0 +1,15 @@
<script>
import IconLoading from "./IconLoading.svelte";
export let loading = false;
</script>
<div class="row">
<div class="col-auto">
<h3 class="h3 mb-3"><slot/></h3>
</div>
{#if loading}
<div class="col">
<IconLoading />
</div>
{/if}
</div>

View File

@ -0,0 +1,52 @@
<script>
import Pagination from "svelte-pagination";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let forcePage = 1;
export let limit = 15;
export let count = 0;
export let disabled = false;
let timePage = null;
function handleChange(e) {
const { selected = 1 } = e.detail;
dispatch("page", selected)
}
</script>
<div class="pagination-container">
<Pagination
pageCount={Math.ceil(count / limit) +1}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
previousLabel="«"
nextLabel="»"
containerClassName="pagination px-0 mb-0"
pageClassName={"page-item" + (disabled ? " disabled" : "")}
previousClassName={"page-item" + (disabled ? " disabled" : "")}
nextClassName={"page-item" + (disabled ? " disabled" : "")}
breakClassName={"page-item" + (disabled ? " disabled" : "")}
pageLinkClassName="page-link"
previousLinkClassName="page-link"
nextLinkClassName="page-link"
breakLinkClassName="page-link"
activeClassName="active"
activeLinkClassName="active"
hrefBuilder={() => {}}
ariaLabelBuilder={null}
extraAriaContext={null}
initialPage={forcePage || 1}
{forcePage}
on:change={handleChange}
/>
<span class="page-link d-inline-block bg-light text-info">
{#if count}
{count} registros
{:else}
Sin registros
{/if}
</span>
</div>

View File

@ -0,0 +1,6 @@
<div class="table-responsive">
<table class="table table-hover table-bordered">
<slot />
</table>
</div>

View File

@ -0,0 +1,21 @@
<footer class="footer">
<div class="container-fluid">
<div class="row text-muted">
<div class="col-6 text-start">
<p class="mb-0">
<a class="text-muted" href="https://www.empresa.com/"
target="_blank" rel="noreferrer">
<strong>EMPRESA S.A.</strong>
</a> &copy;
</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>
</li>
</ul>
</div>
</div>
</div>
</footer>

View File

@ -0,0 +1,37 @@
<svelte:head>
<title>Transporte: Acceso</title>
</svelte:head>
<script>
import { routes } from '$/routes/guest.routes'
import { Router, Route, createHistory } from 'svelte-navigator'
import hashHistory from './hashHistory'
const history = createHistory(hashHistory())
</script>
<Router {history} primary={false}>
<main class="d-flex w-100">
<div class="container d-flex flex-column">
<div class="row vh-100">
<div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100">
<div class="d-table-cell align-middle">
<div class="text-center mt-4">
<h1 class="h2">Gestión de Transporte</h1>
<p class="lead">
Acceda a su cuenta para continuar
</p>
</div>
{#each routes as r}
<Route path={r.path}>
<svelte:component this={r.component} />
</Route>
{/each}
</div>
</div>
</div>
</div>
</main>
</Router>

View File

@ -0,0 +1,65 @@
<svelte:head>
<title>Transporte: Usuario</title>
</svelte:head>
<script>
import 'bootstrap/dist/css/bootstrap.min.css'
import '@adminkit/core/dist/css/app.css'
import '@adminkit/core/dist/js/app.js'
import 'bootstrap-icons/font/bootstrap-icons.css'
import '../assets/colors.css'
import '../assets/custom.css'
import Navbar from './Navbar.svelte'
import Sidebar from './Sidebar.svelte'
import Footer from './Footer.svelte'
import Notifications from './Notifications.svelte'
import { routes } from '$/routes/user.routes'
import { Router, Route, createHistory } from 'svelte-navigator'
import hashHistory from './hashHistory'
import { onMount } from 'svelte'
import { getInfoToken } from '$/services/login'
import { storeSession } from '$/stores/global'
let triggerEvent = false;
const history = createHistory(hashHistory())
onMount(() => {
triggerEvent && document.dispatchEvent(new Event('DOMContentLoaded'));
})
async function begin() {
try {
$storeSession = await getInfoToken()
} catch (error) {
alert(error.message || error)
}
}
begin()
</script>
<div class="wrapper">
<Router {history} primary={false}>
<Sidebar />
<div class="main">
<Navbar />
<main class="content">
<div class="container-fluid p-0">
{#each routes as r}
<Route path={r.path}>
<svelte:component this={r.component} />
</Route>
{/each}
</div>
</main>
<Footer />
</div>
</Router>
</div>
<Notifications />

View File

@ -0,0 +1,32 @@
<script>
import { onMount } from 'svelte'
import { storeMessages } from '$/stores/global'
let count = 0
let classBell = '';
storeMessages.subscribe(val => count = val.length || 0)
$: if (count) {
classBell = ' bell'
setTimeout(() => classBell = '', 6000);
}
onMount(() => {
return () => {
count = 0
storeMessages.set([])
}
})
</script>
<li class="nav-item dropdown">
<a class="nav-icon dropdown-toggle" href={"#"} data-bs-toggle="offcanvas" data-bs-target="#offcanvasRight" aria-controls="offcanvasRight">
<div class="position-relative">
{#if count}
<i class={"align-middle bi bi-bell-fill text-danger" + classBell}></i>
<span class="indicator">{count}</span>
{:else}
<i class="align-middle bi bi-bell"></i>
{/if}
</div>
</a>
</li>

View File

@ -0,0 +1,35 @@
<script>
import { storeSession } from '$/stores/global'
import { Link } from 'svelte-navigator'
let avatar = null;
let persona = {}
$: persona = $storeSession
$: avatar = $storeSession?.avatar_img || '/avatars/avatar2.png';
function onLogout() {
sessionStorage.clear();
document.location.href = '/';
}
</script>
<li class="nav-item dropdown">
<a class="nav-icon dropdown-toggle d-inline-block d-sm-none" href={"#"} data-bs-toggle="dropdown">
<i class="align-middle" data-feather="settings"></i>
</a>
<a class="nav-link dropdown-toggle d-none d-sm-inline-block" href={"#"} data-bs-toggle="dropdown">
<img src={avatar} class="avatar img-fluid rounded me-1" alt={persona.nombres} />
<span class="text-dark">{$storeSession.login}</span>
</a>
<div class="dropdown-menu dropdown-menu-end">
<Link class="dropdown-item" to="/perfil">
<i class="align-middle me-1" data-feather="user"></i> Perfil
</Link>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href={"#"} on:click|preventDefault={onLogout}>
Salir
</a>
</div>
</li>

View File

@ -0,0 +1,17 @@
<script>
import NavUser from './NavUser.svelte'
import NavNotices from './NavNotices.svelte'
</script>
<nav class="navbar navbar-expand navbar-light navbar-bg">
<a class="sidebar-toggle js-sidebar-toggle" href={null}>
<i class="hamburger align-self-center"></i>
</a>
<div class="navbar-collapse collapse">
<ul class="navbar-nav navbar-align">
<NavNotices />
<NavUser />
</ul>
</div>
</nav>

View File

@ -0,0 +1,53 @@
<script>
import { deleteNotificacion } from '$/services/notificaciones'
import { storeMessages } from '$/stores/global'
let messages = []
let hoy = (new Date()).toISOString().substring(0,10);
storeMessages.subscribe(val => messages = val)
async function onDelete(id) {
try {
await deleteNotificacion(id)
storeMessages.update(val => val.filter(el => el.id !== id))
} catch(error) {
globalThis.toast.error(error.message || error)
}
}
function formatTime(time) {
if (!time) return '--:--';
const myDate = new Date(time);
if (myDate.toISOString() < hoy) {
return myDate.toLocaleString()
} else {
return myDate.toLocaleTimeString()
}
}
</script>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight" aria-labelledby="offcanvasRightLabel" style="visibility: inherit;">
<div class="offcanvas-header">
<h5 class="offcanvas-title text-info" id="offcanvasRightLabel"><i class="bi bi-bell-fill text-info"></i> Alertas</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
{#each messages as m, index}
<div class={"alert d-flex mb-3" + (index === 0 ? ' alert-primary shadow' : ' alert-info shadow-sm')}>
<i class="bi bi-info-circle fs-1 me-2"></i>
<div>
<strong>{formatTime(m.fecha_hora)}</strong>
<div>{m.mensaje}</div>
</div>
<button type="button" class="btn-close ms-auto" aria-label="Close" on:click|preventDefault={() => onDelete(m.id)}></button>
</div>
{/each}
</div>
</div>
<style>
.shadow { box-shadow: 0 .5rem 1rem var(--bs-primary)!important; }
.shadow-sm { box-shadow: 0 .125rem .25rem var(--bs-info)!important; }
</style>

View File

@ -0,0 +1,15 @@
<script>
import { Link, useMatch } from 'svelte-navigator'
export let to = '#'
const absoluteMatch = useMatch(to)
let match;
$: match = $absoluteMatch?.fullPath === to;
</script>
<li class={"sidebar-item" + (match ? ' active' : '')}>
<Link class="sidebar-link" {to}>
<slot />
</Link>
</li>

View File

@ -0,0 +1,80 @@
<script>
import { Link } from 'svelte-navigator'
import SideLink from './SideLink.svelte'
</script>
<nav id="sidebar" class="sidebar js-sidebar">
<div class="sidebar-content js-simplebar">
<Link class="sidebar-brand" to="/">
<span class="fs-1">🚌</span>
<span class="align-middle">
Transporte
</span>
</Link>
<ul class="sidebar-nav">
<li class="sidebar-header">Pages</li>
<SideLink to="/">
<i class="align-middle bi bi-house fs-4"></i>
<span class="align-middle">Inicio</span>
</SideLink>
<SideLink to="/perfil">
<i class="align-middle bi bi-person-lines-fill fs-4"></i>
<span class="align-middle">Perfil</span>
</SideLink>
<li class="sidebar-header">Mapas</li>
<SideLink to="/mapas/paraderos">
<i class="align-middle bi bi-map fs-4" />
<span class="align-middle">Paraderos</span>
</SideLink>
<SideLink to="/mapas/rutas">
<i class="align-middle bi bi-map fs-4" />
<span class="align-middle">Rutas</span>
</SideLink>
<li class="sidebar-header">Mantenedores</li>
<SideLink to="/paraderos">
<i class="align-middle bi bi-bus-front fs-4" />
<span class="align-middle">Paraderos</span>
</SideLink>
<SideLink to="/aplicaciones">
<i class="align-middle bi bi-terminal fs-4" />
<span class="align-middle">Aplicaciones</span>
</SideLink>
<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">
<i class="align-middle bi bi-geo-alt fs-4" />
<span class="align-middle">Comunas</span>
</SideLink>
<SideLink to="/usuarios">
<i class="align-middle bi bi-people fs-4" />
<span class="align-middle">Usuarios</span>
</SideLink>
<SideLink to="/personas">
<i class="align-middle bi bi-people fs-4" />
<span class="align-middle">Personas</span>
</SideLink>
<SideLink to="/roles">
<i class="align-middle bi bi-person-badge fs-4" />
<span class="align-middle">Roles</span>
</SideLink>
</ul>
</div>
</nav>

View File

@ -0,0 +1,43 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { createHashHistory } from "history";
function createHashSource() {
const history = createHashHistory();
let listeners = [];
history.listen(location => {
if (history.action === "POP") {
listeners.forEach(listener => listener(location));
}
});
return {
get location() {
return history.location;
},
addEventListener(name, handler) {
if (name !== "popstate") return;
listeners.push(handler);
},
removeEventListener(name, handler) {
if (name !== "popstate") return;
listeners = listeners.filter(fn => fn !== handler);
},
history: {
get state() {
return history.location.state;
},
pushState(state, title, uri) {
history.push(uri, state);
},
replaceState(state, title, uri) {
history.replace(uri, state);
},
go(to) {
history.go(to);
},
},
};
}
export default createHashSource;

13
src/main.js 100644
View File

@ -0,0 +1,13 @@
import AppGuest from '$/layouts/LayoutGuest.svelte'
import AppUser from '$/layouts/LayoutUser.svelte'
import { getToken } from '$/services/_config'
let app = null;
if (getToken()) {
app = new AppUser({ target: document.getElementById('app') })
} else {
app = new AppGuest({ target: document.getElementById('app') })
}
export default app

View File

@ -0,0 +1,116 @@
<script>
import Paginate from "$/components/Paginate.svelte";
import { getAplicaciones } from "$/services/aplicaciones";
import PageTitle from "$/components/PageTitle.svelte";
import ModalAplicacion from "./ModalAplicacion.svelte";
const limit = 15;
let page = 1;
let offset = 0;
let count = 0;
let ordering = 'id_aplicacion'
let aplicaciones = []
let aplicacion = null
let loading = false;
$: onPage(page)
async function onPage(p) {
try {
loading = true
offset = (p - 1) * limit;
const data = await getAplicaciones({ offset, limit, ordering })
aplicaciones = data.results;
count = data.count;
} catch (error) {
alert(error)
} finally {
loading = false;
}
}
function onEdita(item) {
aplicacion = item;
}
function onNuevo() {
aplicacion = {}
}
function onOrderBy(field) {
ordering = ordering === field ? '-' + field : field;
onPage(page)
}
</script>
<PageTitle {loading}>Aplicaciones</PageTitle>
<div class="card">
<div class="card-header">
<button class="btn btn-primary" on:click|preventDefault={onNuevo}>
<i class="bi bi-plus-lg"></i> Nuevo
</button>
</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_aplicacion')}>ID</a>
{#if ordering === 'id_aplicacion'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-id_aplicacion'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('nombre_app')}>Nombre</a>
{#if ordering === 'nombre_app'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-nombre_app'}<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 aplicaciones as app, index}
<tr>
<td class="table-light">{offset + index + 1}</td>
<td>{app.id_aplicacion}</td>
<td><a href={"#"} on:click|preventDefault={() => onEdita(app)}>{app.nombre_app}</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
{offset}
{limit}
{count}
on:page={ev => page = ev.detail}
/>
</div>
</div>
{#if aplicacion}
<ModalAplicacion
{aplicacion}
on:close={() => aplicacion = null}
on:refresh={() => onPage(page)}
/>
{/if}
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -0,0 +1,93 @@
<script>
import Modal from "../../components/Modal.svelte";
import { getAplicacion, createAplicacion, updateAplicacion, deleteAplicacion } from "$/services/aplicaciones";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let aplicacion = {}
let form = {}
let loading = false;
$: begin(aplicacion.id_aplicacion)
async function begin(id) {
try {
if (!id) return;
form = await getAplicacion(id) || {}
} catch (error) {
alert(error.detail || error)
}
}
async function onSave() {
try {
loading = true;
if (aplicacion.id_aplicacion) {
form = await updateAplicacion(form)
} else {
form = await createAplicacion(form)
}
alert('Se ha guardado la aplicación')
dispatch('refresh')
dispatch('close')
} catch (error) {
if (error.detail) {
alert(error.detail)
} else {
alert(JSON.stringify(error))
}
} finally {
loading = false;
}
}
async function onDelete() {
try {
if (!confirm('Eliminará el registro?')) return;
loading = true;
await deleteAplicacion(form.id_aplicacion)
alert('Se ha eliminado la aplicación')
dispatch('refresh')
dispatch('close')
} catch (error) {
alert(error.detail || error)
} finally {
loading = false;
}
}
</script>
<form action="" on:submit|preventDefault={onSave}>
<Modal title={'Aplicacion #' + (aplicacion.id_aplicacion || 'Nuevo')}
size="lg"
on:close={() => dispatch('close')}>
<div class="form">
<div class="row mb-3">
<div class="col-md-3">ID</div>
<div class="col-md">
{#if aplicacion.id_aplicacion}
<input type="number" value={form.id_aplicacion} disabled class="form-control">
{:else}
<input type="number" bind:value={form.id_aplicacion} 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_app} 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>
<svelte:fragment slot="buttons">
<button class="btn btn-primary"type="submit" disabled={loading}>Guardar</button>
<button class="btn btn-danger" on:click|preventDefault={onDelete} disabled={loading}>Eliminar</button>
</svelte:fragment>
</Modal>
</form>

View File

@ -0,0 +1,146 @@
<script>
import { Link } from 'svelte-navigator'
import Paginate from '$/components/Paginate.svelte'
import PageTitle from '$/components/PageTitle.svelte'
import { getComunas, getComuna, createComuna, updateComuna, deleteComuna } from '$/services/comunas'
import Modal from '$/components/Modal.svelte';
import FormComuna from './Form.svelte';
let comuna = null
let es_nuevo = true
let comunas = { count: 0, results: [] }
let page = 1
let loading = false;
const limit = 15
$: onPage(page)
async function onPage(page) {
try {
loading = true
const offset = (page - 1) * limit;
comunas = await getComunas({ offset, limit, ordering: 'id_region' })
} catch(error) {
alert(error.detail || error)
} finally {
loading = false
}
}
function onNuevo() {
comuna = {}
es_nuevo = true
}
async function onEditar({ id_comuna }) {
try {
loading = true
comuna = await getComuna(id_comuna)
es_nuevo = false
} catch (error) {
alert(error.detail || error)
} finally {
loading = false
}
}
async function onSave() {
try {
if (es_nuevo) {
await createComuna(comuna)
} else {
await updateComuna(comuna)
}
alert('Comuna guardada con exito')
comuna = null; // se cierra el modal
onPage(page) // refresco la tabla de registros
} catch (error) {
alert(error.detail || error)
}
}
async function onDelete() {
try {
if (!confirm(`Desea eliminar la comuna [${comuna.nombre_comuna}]?`)) return;
await deleteComuna(comuna.id_comuna)
alert('Comuna eliminada con exito')
comuna = null; // se cierra el modal
onPage(page) // refresco la tabla de registros
} catch (error) {
alert(error.detail || error)
}
}
</script>
<PageTitle {loading}>Comunas</PageTitle>
<div class="card">
<div class="card-header">
<div class="mb-3 d-flex">
<button class="btn btn-primary me-3"
on:click|preventDefault={onNuevo}>
<i class="bi bi-plus-lg"></i> Nuevo
</button>
<div class="m-auto"></div>
<Link to='/' class="btn btn-outline-secondary">Volver</Link>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr class="table-light">
<th>ID</th>
<th>Nombre</th>
<th>Región</th>
</tr>
</thead>
<tbody>
{#each comunas.results as row}
<tr>
<td><a class="d-block" href={"#"} on:click|preventDefault={() => onEditar(row)}>{row.id_comuna}</a></td>
<td>{row.nombre_comuna}</td>
<td>{row.id_region}</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
offset={(page - 1) * limit}
{limit}
count={comunas.count}
on:page={ev => page = ev.detail}
/>
</div>
</div>
{#if !!comuna}
<form action="" on:submit|preventDefault={onSave}>
<Modal title="Comuna"
on:close={() => comuna = null}
>
<FormComuna bind:form={comuna} {es_nuevo} />
<svelte:fragment slot="buttons">
<button type="submit" class="btn btn-primary">Guardar</button>
{#if !es_nuevo}
<button class="btn btn-danger" on:click|preventDefault={onDelete}>Eliminar</button>
{/if}
</svelte:fragment>
</Modal>
</form>
{/if}
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -0,0 +1,37 @@
<script>
import { getRegiones } from '$/services/regiones'
export let form = {}
export let es_nuevo = true
let regiones = []
$: begin()
async function begin() {
try {
regiones = await getRegiones()
} catch (error) {
alert(error.detail || error)
}
}
</script>
<div class="input-group mb-3">
<div class="input-group-text">ID</div>
<input type="number" bind:value={form.id_comuna} class="form-control" disabled>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Comuna</div>
<input type="text" bind:value={form.nombre_comuna} class="form-control" required>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Región</div>
<select bind:value={form.id_region} class="form-select" required>
<option value=""></option>
{#each regiones as region}
<option value={region.id_region}>{region.nombre_region}</option>
{/each}
</select>
</div>

View File

@ -0,0 +1,86 @@
<script>
import { getLineas } from "$/services/lineas";
import { getOperadores } from "$/services/operadores";
export let id_operador;
export let id_linea;
export let codigo;
export let ver_buses;
export let ver_paraderos;
let operadores = []
let lineas = []
let lineas_operador = []
getOperadores({ vigente: 1 })
.then(data => data.sort((a,b) => a.nombre_operador < b.nombre_operador? -1 : 1))
.then(data => operadores = data)
.catch(error => alert(error))
getLineas({ vigente: 1 })
.then(data => data.sort((a,b) => a.nombre_linea < b.nombre_linea? -1 : 1))
.then(data => lineas = data)
.catch(error => alert(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);
const lineas_ordenadas = lineas_filtradas.sort((a,b) => a.route_short_name < b.route_short_name ? -1 : 1);
lineas_operador = lineas_ordenadas;
}
}
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>
</div>
</div>

View File

@ -0,0 +1,161 @@
<script>
// servicios
import {
getParadero,
updateParadero,
getParaderoImagenes,
createParaderoImagen,
deleteParaderoImagen,
} from "$/services/paraderos";
import IconLoading from "../../components/IconLoading.svelte";
export let parada = null;
let form = {};
let imagenes = [];
let loading = false;
$: init(!!parada);
async function init(show) {
try {
if (parada) {
loading = true;
form = await getParadero(parada.id_paradero);
imagenes = await getParaderoImagenes(parada.id_paradero);
}
} catch (error) {
alert(error);
} finally {
loading = false;
}
}
async function onSave() {
try {
await updateParadero(form);
alert("Información guardada");
} catch (error) {
alert(error);
}
}
async function onSaveImagen({ target: form }) {
try {
const [file = null] = form.file1.files;
await createParaderoImagen(parada.id_paradero, file);
imagenes = await getParaderoImagenes(parada.id_paradero);
form.file1.value = "";
} catch (error) {
alert(error);
}
}
async function onDeleteImagen({ id_paradero_imagen }) {
try {
if (!confirm("Estás seguro de eliminar la imagen?")) return;
await deleteParaderoImagen(id_paradero_imagen);
imagenes = imagenes.filter(
(imagen) => imagen.id_paradero_imagen !== id_paradero_imagen
);
} catch (error) {
alert(error);
}
}
</script>
{#if loading}
<IconLoading />
{/if}
<div>
Coordenadas:
<div class="input-group mb-3">
<div class="input-group-text">Latitud</div>
<div class="form-control">{form.stop_lat || '...'}</div>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Longitud</div>
<div class="form-control">{form.stop_lon || '...'}</div>
</div>
Propiedades:
<form on:submit|preventDefault={onSave}>
<div class="input-group mb-3">
<div class="input-group-text">Identificador</div>
<input
type="text"
disabled
value={form.id_paradero || '...'}
class="form-control"
/>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Nombre</div>
<input
type="text"
bind:value={form.stop_name}
class="form-control"
/>
</div>
<div class="input-group mb-3">
<div class="input-group-text">Descripción</div>
<input
type="text"
bind:value={form.stop_desc}
class="form-control"
/>
</div>
<hr />
<div class="text-center">
<button type="submit" class="btn btn-primary"
><i class="bi bi-save" /> Guardar</button
>
</div>
</form>
<div class="my-3" />
<!-- imagenes -->
{#each imagenes as imagen}
<div class="seccion-imagen mb-3">
<img src={imagen.url} alt="imagen paradero" class="img-fluid" />
<a
href={"#"}
class="btn btn-danger"
on:click|preventDefault={() => onDeleteImagen(imagen)}
>
<i class="bi bi-trash" /> Eliminar
</a>
</div>
{/each}
<!-- agregar imagen -->
<form action="" on:submit|preventDefault={onSaveImagen}>
<div class="card">
<div class="card-body">
<div class="form-control" style="overflow: hidden">
<input
type="file"
name="file1"
accept="*.png,*.jpg,*.jpeg"
/>
</div>
</div>
<div class="card-footer text-center">
<button class="btn btn-primary" type="submit">
<i class="bi bi-plus-lg" /> Agregar imagen
</button>
</div>
</div>
</form>
</div>
<style>
.seccion-imagen {
position: relative;
}
.seccion-imagen > a {
position: absolute;
bottom: 0.5rem;
right: 0.5rem;
}
</style>

View File

@ -0,0 +1,142 @@
<script>
import QrCode from 'svelte-qrcode'
import IconLoading from "../../components/IconLoading.svelte";
import { createDispositivo, deleteDispositivo, getDispositivos } from "../../services/dispositivos";
import { getTiposDispositivo } from "../../services/tipos_dispositivo";
import { url_base } from "../../services/_config";
import { getInfoPublic } from '../../services/paraderos';
export let parada = null;
let loading = false;
let tipos_dispositivos = [];
let dispositivos = [];
let url_qrcode = null;
let nuevoDispositivo = null;
async function fetchDispositivos({ id_paradero = null }) {
try {
if (!id_paradero) return;
loading = true
dispositivos = await getDispositivos({ id_paradero })
url_qrcode = `${url_base}/public/infoStop?codigoParadero=${id_paradero}`;
} catch (error) {
console.log({ error });
} finally {
loading = false
}
}
async function fetchInfoPublico({ id_paradero = null }) {
try {
if (!id_paradero) return;
const info = await getInfoPublic(id_paradero)
url_qrcode = info.url_public
} catch (error) {
console.log({ error });
}
}
async function fetchTiposDispositivos() {
try {
tipos_dispositivos = await getTiposDispositivo()
} catch (error) {
console.log({ error })
}
}
$: Promise.all([fetchDispositivos(parada), fetchInfoPublico(parada), fetchTiposDispositivos()])
async function onSave() {
try {
loading = true
await createDispositivo(nuevoDispositivo)
const tipo_dispositivo = tipos_dispositivos.find(el => el.id_tipo_dispositivo === nuevoDispositivo.id_tipo_dispositivo) || {}
dispositivos = [ ...dispositivos, { ...nuevoDispositivo, tipo_dispositivo }]
nuevoDispositivo = null
} catch (error) {
alert(error)
} finally {
loading = false
}
}
async function onDelete({ id_dispositivo }) {
try {
if (!confirm(`Desea eliminar el dispositivo ${id_dispositivo} ?`)) return;
loading = true
await deleteDispositivo(id_dispositivo)
dispositivos = dispositivos.filter(el => el.id_dispositivo !== id_dispositivo)
} catch (error) {
alert(error)
} finally {
loading = false
}
}
</script>
<div>
<h4 class="mb-3">Dispositivos</h4>
{#if !nuevoDispositivo}
<table class="table table-bordered">
<thead>
<tr>
<th>ID Dipositivo</th>
<th>Tipo</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{#each dispositivos as disp}
<tr>
<td>{disp.id_dispositivo}</td>
<td>{disp.tipo_dispositivo?.descripcion || '--'}</td>
<td><a href={"#"} on:click|preventDefault={() => onDelete(disp)}><i class="bi bi-trash text-danger"></i></a></td>
</tr>
{/each}
</tbody>
</table>
<div class="my-3 text-center">
<button class="btn btn-outline-primary" on:click|preventDefault={() => nuevoDispositivo = {}}>Nuevo Dispositivo</button>
</div>
{:else}
<form action="" on:submit|preventDefault={onSave}>
<div class="input-group">
<div class="input-group-text">ID Dispositivo</div>
<input type="text" class="form-control" bind:value={nuevoDispositivo.id_dispositivo}>
</div>
<div class="input-group">
<div class="input-group-text">Tipo Dispositivo</div>
<select class="form-select" bind:value={nuevoDispositivo.id_tipo_dispositivo}>
{#each tipos_dispositivos as tipo}
<option value={tipo.id_tipo_dispositivo}>{ tipo.descripcion }</option>
{/each}
</select>
</div>
<div class="d-flex justify-content-between my-3">
<button type="submit" class="btn btn-primary">Guardar</button>
<button class="btn btn-outline-secondary" on:click|preventDefault={() => nuevoDispositivo = null}>Cancelar</button>
</div>
</form>
{/if}
{#if loading}
<div>
<IconLoading />
Cargando información
</div>
{/if}
<div class="m-6"></div>
<div class="text-center mb-3">
{#if url_qrcode}
<QrCode value={url_qrcode} />
{/if}
</div>
<div class="text-center">
<button class="btn btn-secondary">Imprimir QR</button>
</div>
</div>

View File

@ -0,0 +1,71 @@
<script>
import IconLoading from "../../components/IconLoading.svelte";
import { getLinea } from "../../services/lineas";
import { getOperador } from "../../services/operadores";
import { getLineasParadero } from "../../services/paraderos";
export let parada = null;
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);
</script>
<div>
<h4 class="mb-3">Servicios</h4>
<div class="p-3 bg-white">
{#if loading}
<div>
<IconLoading />
Cargando información
</div>
{/if}
<ul class="list-group">
{#each servicios as servicio}
<li class="list-group-item d-flex justify-content-between align-items-start">
<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>

View File

@ -0,0 +1,75 @@
<script>
import Modal from "../../components/Modal.svelte";
import { createEventDispatcher } from "svelte";
import { getLetreroLUR } from "../../services/letreros_lur";
import IconLoading from "../../components/IconLoading.svelte";
const dispatch = createEventDispatcher();
export let codigo = null;
let letrero = {};
let codigo_dividido = []
let loading = false
$: fetchLetreroLUR(codigo)
async function fetchLetreroLUR(codigo) {
try {
loading = true
letrero = await getLetreroLUR(codigo) || {}
codigo_dividido = letrero.codigo.match(/\d+|[a-z]+/gi)
} catch (error) {
console.log({ error })
} finally {
loading = false;
}
}
</script>
<Modal title="Letrero" classBody="bg-white" on:close={() => dispatch('close')}>
{#if loading}
<IconLoading />
{/if}
{#if !loading && letrero.codigo}
<table class="m-auto border border-secondary" cellpadding="10" cellspacing="0"
style="--bgcolor1: {letrero.bgcolor1}; --color1: {letrero.color1}; --bgcolor2: {letrero.bgcolor2}; --color2: {letrero.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 width="220">{letrero.linea1 || ''}</td>
</tr>
<tr>
<td>{letrero.linea2 || ''}</td>
</tr>
<tr>
<td>{letrero.linea3 || ''}</td>
</tr>
<tr class="color2">
<td>{letrero.linea4 || ''}</td>
</tr>
</tbody>
</table>
{:else}
{#if !loading}
<p class="alert alert-warning p-3">Información del Letrero no registrada.</p>
{/if}
{/if}
</Modal>
<style>
.color1 {
background-color: var(--bgcolor1);
color: var(--color1);
}
.color2 {
background-color: var(--bgcolor2);
color: var(--color2);
}
</style>

View File

@ -0,0 +1,92 @@
<script>
import Modal from "$/components/Modal.svelte";
import { getParadero, getUrlImagen, createParadero, updateParadero, deleteParadero, saveImageParadero } from "$/services/paraderos";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let paradero = { id_paradero: null };
let form = {};
let inputfile = null;
let imagenEl = null;
$: infoParadero(paradero)
async function infoParadero({ id_paradero }) {
try {
if (!id_paradero) return;
form = await getParadero(id_paradero)
} catch (error) {
alert(error)
}
}
async function onSaveParadero() {
try {
form = form.id_paradero ? await updateParadero(form) : await createParadero(form)
alert('Información guardada')
} catch (error) {
alert(error)
}
}
async function onDeleteParadero() {
try {
await deleteParadero(form.id_paradero)
alert('Paradero eliminado')
dispatch('delete', form)
dispatch('close')
} catch (error) {
alert(error)
}
}
async function onChangeImage() {
try {
const { url_image } = await saveImageParadero(paradero.id_paradero, inputfile.files[0])
imagenEl.src = url_image + '?nocache=' + (new Date()).valueOf()
alert('Imagen cambiada')
} catch (error) {
alert(error)
}
}
</script>
<form on:submit|preventDefault={onSaveParadero}>
<Modal size="lg"
title={'Paradero #' + paradero.id_paradero}
on:close={() => dispatch('close')}>
<div class="input-group mb-3">
<div class="input-group-text">Dirección</div>
<input type="text" bind:value={form.stop_name} class="form-control">
</div>
<div class="input-group mb-3">
<div class="input-group-text">Latitud</div>
<input type="number" step="any" disabled value={form.stop_lat} class="form-control">
</div>
<div class="input-group mb-3">
<div class="input-group-text">Longitud</div>
<input type="number" step="any" disabled value={form.stop_lon} class="form-control">
</div>
{#if form.id_paradero}
<div class="input-group mb-3">
<div class="input-group-text"><i class="bi bi-image"></i></div>
<div class="form-control"><input type="file" bind:this={inputfile}></div>
<button class="btn btn-primary" on:click|preventDefault={onChangeImage}>Cambiar Imagen</button>
</div>
<img alt="Imagen Paradero" class="img-fluid" bind:this={imagenEl} src={getUrlImagen(form.id_paradero)}>
{/if}
<svelte:fragment slot="buttons">
<button type="submit" class="btn btn-primary me-2">
<i class="bi bi-save"></i> Guardar
</button>
<button class="btn btn-danger me-2" on:click|preventDefault={onDeleteParadero}>
<i class="bi bi-trash"></i> Eliminar
</button>
</svelte:fragment>
</Modal>
</form>

View File

@ -0,0 +1,120 @@
<script>
// imagenes
import IconParada from "$/assets/parada.png";
// libs
import { createEventDispatcher } from "svelte";
import FormParadero from "./FormParadero.svelte";
import FormParaderoDispositivos from "./FormParaderoDispositivos.svelte";
import FormParaderoServicios from "./FormParaderoServicios.svelte";
const dispatch = createEventDispatcher();
export let parada = null;
let canvas = null;
let tab = 0;
$: init(!!parada);
function init(show) {
if (!canvas) return;
if (show) {
canvas.classList.add("show");
} else {
canvas.classList.remove("show");
tab = 0;
}
}
</script>
<div
class="offcanvas offcanvas-end"
tabindex="-1"
bind:this={canvas}
aria-labelledby="offcanvasParaderoLabel"
style="visibility: inherit;"
>
<div class="offcanvas-header d-block pb-0">
<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 #{parada?.id_paradero} {parada?.location}
</h5>
<button
type="button"
class="btn-close"
aria-label="Close"
on:click|preventDefault={() => dispatch("close")}
/>
</div>
<nav class="mb-0" style="--bs-nav-tabs-link-active-border-color: var(--bs-dark)">
<ul class="nav nav-tabs">
<li class="nav-item">
<a
class={"nav-link" + (tab === 0 ? " active" : "")}
href={"#"}
on:click|preventDefault={() => (tab = 0)}>
General
</a>
</li>
<li class="nav-item">
<a
class={"nav-link" + (tab === 1 ? " active" : "")}
href={"#"}
on:click|preventDefault={() => (tab = 1)}>
Dispositivos
</a>
</li>
<li class="nav-item">
<a
class={"nav-link" + (tab === 2 ? " active" : "")}
href={"#"}
on:click|preventDefault={() => (tab = 2)}>
Servicios
</a>
</li>
</ul>
</nav>
</div>
<div class="offcanvas-body bg-white py-3">
{#if tab === 0}
<FormParadero {parada} />
{/if}
{#if tab === 1}
<FormParaderoDispositivos {parada} />
{/if}
{#if tab === 2}
<FormParaderoServicios {parada} />
{/if}
</div>
</div>
{#if parada}
<div
class="offcanvas-backdrop fade show"
on:click|preventDefault={() => dispatch("close")}
on:keydown={() => {}}
/>
{/if}
{#if parada}
<style>
html {
overflow-y: hidden;
}
.offcanvas {
--bs-offcanvas-width: 500px;
--bs-offcanvas-bg: 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);
}
</style>
{/if}

View File

@ -0,0 +1,198 @@
<script>
// components
import PageTitle from "$/components/PageTitle.svelte"
import IconLoading from "$/components/IconLoading.svelte"
import OffcanvasParadero from "./OffcanvasParadero.svelte"
// services
import { getRegiones } from "$/services/regiones"
import { getComunas } from "$/services/comunas"
import { getMarcasParaderos } from "$/services/mapas"
// libs
import { onMount } from 'svelte'
import { storeParaderos } from "$/stores/global"
import imagenParada from '$/assets/parada.png'
let myMap = null
let elMap = null
let loading = false
let regiones = []
let comunas = []
let comunas_x_region = []
let L = null
let iconParada = null
let markers = []
let form = {}
let parada = null
cargar_regiones_comunas()
$: myMap && crear_marcadores_por_criterio()
onMount(() => {
if(globalThis.L) create_map();
cargar_paraderos_todos($storeParaderos)
})
function create_map() {
if (!elMap || !globalThis.L) return;
if (!L) L = globalThis.L;
if (!iconParada) {
iconParada = L.icon({
iconUrl: imagenParada,
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',
{ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }
).addTo(myMap);
}
// obtener coordenadas actuales
// centrar mapa en coordenadas del navegador
navigator.geolocation.getCurrentPosition(
({ coords }) => {
const { latitude, longitude } = coords;
myMap.setView([latitude, longitude], 16)
},
(error) => console.log({ error })
)
}
async function cargar_regiones_comunas() {
try {
regiones = await getRegiones()
comunas = await getComunas()
} catch (error) {
alert(error)
}
}
async function cargar_paraderos_todos(data_default) {
try {
loading = true
const paraderos = data_default || await getMarcasParaderos()
storeParaderos.set(paraderos)
} catch (error) {
alert(error)
} finally {
loading = false
}
}
function crear_marcadores_por_criterio() {
if (form.id_region) {
comunas_x_region = comunas.filter(comuna => comuna.id_region === form.id_region)
}
if (!$storeParaderos?.marks) {
setTimeout(crear_marcadores_por_criterio, 1000)
return
}
// 1. eliminar los marcadores anteriores
markers.forEach(marker => marker.remove())
// 2. filtrar resultado de marcadores
const paraderos = $storeParaderos.marks.filter(m => {
// filtrar por comunas de la region
if (form.id_region && !form.id_comuna) {
const existe = comunas_x_region.findIndex(com => com.id_comuna === m.id_comuna) !== -1
if (!existe) return false
}
// filtrar por comuna seleccionada
if (form.id_comuna && m.id_comuna !== form.id_comuna) return false
// filtrar por texto de busqueda
if (form.search && m.location.toUpperCase().indexOf(form.search.toUpperCase()) === -1) return false
// filtro coincide a criterio
return true
})
// 3. crear marcadores
for (let mark of paraderos) {
const { lat, lng } = mark.position
const marker = L.marker([lat, lng], { icon: iconParada }).addTo(myMap)
const { title, location } = mark;
const html = `${title}<br>${location}`
marker.bindTooltip(html)
marker.on('click', function() {
parada = { ...mark }
})
markers.push(marker)
}
// 4. centrar mapa
if (myMap && paraderos.length) {
const { lat, lng } = paraderos[0].position
myMap.setView([ lat, lng ],16)
const bounds = myMap.getBounds()
paraderos.forEach(el => bounds.extend(el.position))
myMap.fitBounds(bounds)
}
}
function run_search_text() {
form.time_search && clearTimeout(form.time_search)
form.time_search = setTimeout(() => crear_marcadores_por_criterio(), 1000)
}
</script>
<svelte:head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" on:load={create_map}></script>
</svelte:head>
<div class="row">
<div class="col-md">
<PageTitle>
{#if loading}<IconLoading />{/if}
Paraderos
</PageTitle>
</div>
<div class="col-md-auto">
<div class="input-group mb-sm-3">
<div class="input-group-text">Región</div>
<select bind:value={form.id_region} class="form-select" on:change={crear_marcadores_por_criterio}>
<option value=""></option>
{#each regiones as r}
<option value={r.id_region}>{r.nombre_region}</option>
{/each}
</select>
</div>
</div>
<div class="col-md-auto">
<div class="input-group mb-sm-3">
<div class="input-group-text">Comuna</div>
<select bind:value={form.id_comuna} class="form-select" on:change={crear_marcadores_por_criterio}>
<option value=""></option>
{#each comunas_x_region as c}
<option value={c.id_comuna}>{c.nombre_comuna}</option>
{/each}
</select>
</div>
</div>
<div class="col-md-auto">
<div class="input-group mb-sm-3">
<input type="search" bind:value={form.search} class="form-control" on:input={run_search_text}>
<div class="input-group-text"><i class="bi bi-search"></i></div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div bind:this={elMap} style="height: 100vh;"></div>
</div>
</div>
<OffcanvasParadero {parada} on:close={() => parada = null} />

View File

@ -0,0 +1,212 @@
<script>
// componentes
import PageTitle from "$/components/PageTitle.svelte";
import imagenPartida from '$/assets/partida.png'
import imagenTermino from '$/assets/termino.png'
import imagenParada from '$/assets/parada.png'
import imagenAutobus from '$/assets/autobus.png'
import { onMount } from "svelte";
// servicios
import { getRutas } from "$/services/mapas";
import ModalLetreroLUR from "./ModalLetreroLUR.svelte";
import FiltroRutas from "./FiltroRutas.svelte";
import { getBusesLinea, getParaderosLinea } from "$/services/lineas";
let myMap = null
let elMap = null
let loading = false
let polyline = null
let iconPartida = null
let iconTermino = null
let iconParada = null
let iconAutobus = null
let L = null // leaflet.js
let marker1 = null
let marker2 = null
let showLetrero = false
let id_operador = ''
let id_linea = ''
let codigo = null
let ver_buses = false
let ver_paraderos = false
let markers_paraderos = []
let markers_buses = []
onMount(() => { create_map() })
function create_map() {
if (!elMap || !globalThis.L) return;
if (!L) L = globalThis.L;
if (!iconPartida) {
iconPartida = L.icon({
iconUrl: imagenPartida,
iconSize: [40, 40],
iconAnchor: [20, 40],
popupAnchor: [0, -20]
})
}
if (!iconTermino) {
iconTermino = L.icon({
iconUrl: imagenTermino,
iconSize: [40, 40],
iconAnchor: [20, 40],
popupAnchor: [0, -20]
})
}
if (!iconParada) {
iconParada = L.icon({
iconUrl: imagenParada,
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -16]
})
}
if (!iconAutobus) {
iconAutobus = L.icon({
iconUrl: imagenAutobus,
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',
{ 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(
({ coords }) => {
const { latitude, longitude } = coords;
myMap.setView([latitude, longitude], 16);
},
(error) => console.log({ error })
)
}
async function onMostrarRuta(id_operador, id_linea) {
try {
loading = true
polyline && polyline.remove()
marker1 && marker1.remove()
marker2 && marker2.remove()
if (!id_operador || !id_linea) return;
const data = await getRutas({ id_linea })
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());
if (coordenadas) {
marker1 = L.marker(coordenadas[0], { icon: iconPartida }).addTo(myMap)
marker1.bindTooltip('Inicio')
marker2 = L.marker(coordenadas[coordenadas.length -1], { icon: iconTermino }).addTo(myMap)
marker2.bindTooltip('Termino')
}
} catch (error) {
alert(error)
} finally {
loading = false;
}
}
async function onMostrarParaderos(ver_paraderos) {
// 1. eliminar marcadores anteriores
markers_paraderos.forEach(marker => marker.remove())
if (!ver_paraderos || !id_linea) return;
const paraderos = await getParaderosLinea(id_linea)
// 2. crear marcadores
for (let mark of paraderos) {
const { stop_lat: lat, stop_lon: lng } = mark
const marker = L.marker([lat, lng], { icon: iconParada }).addTo(myMap)
const { id_paradero: title, stop_name: location } = mark;
const html = `${title}<br>${location}`
marker.bindTooltip(html)
markers_paraderos.push(marker)
}
}
async function onMostrarBuses(ver_buses) {
// 1. eliminar marcadores anteriores
markers_buses.forEach(marker => marker.remove())
if (!ver_buses || !id_linea) return;
const buses = await getBusesLinea(id_linea)
console.log({ buses })
// 2. crear marcadores
for (let mark of buses) {
const { latitude: lat, longitude: lng } = mark
const marker = L.marker([lat, lng], { icon: iconAutobus }).addTo(myMap)
const html = `${mark.Patente_vehiculo}<br>Velocidad: ${mark.speed}`
marker.bindTooltip(html)
markers_buses.push(marker)
console.log({ marker })
}
}
$: onMostrarRuta(id_operador, id_linea)
$: onMostrarParaderos(ver_paraderos)
$: onMostrarBuses(ver_buses)
</script>
<svelte:head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" on:load={create_map}></script>
</svelte:head>
<div class="row">
<div class="col-md-auto">
<PageTitle {loading}>
Rutas
</PageTitle>
</div>
<div class="col-md">
<FiltroRutas
bind:id_operador={id_operador}
bind:id_linea={id_linea}
bind:codigo={codigo}
bind:ver_buses={ver_buses}
bind:ver_paraderos={ver_paraderos}
/>
</div>
{#if id_operador && codigo}
<div class="col-auto">
<button class="btn btn-secondary" on:click|preventDefault={() => showLetrero = true}>
Ver Letrero
</button>
</div>
{/if}
</div>
<div class="card">
<div class="card-body">
<div bind:this={elMap} style="height: 100vh;"></div>
</div>
</div>
{#if showLetrero}
<ModalLetreroLUR {codigo} on:close={() => showLetrero = false} />
{/if}

View File

@ -0,0 +1,23 @@
.table-paraderos td {
min-width: 150px;
--margin-input: -.75rem;
}
.table-paraderos td:focus {
background: #80ffa6;
}
.table-paraderos td:has(input) {
padding: 0;
--margin-input: 0;
}
.table-paraderos td > input {
border: none;
margin: var(--margin-input) 0;
background-color: transparent;
padding: .75rem;
}
.table-paraderos td > input:focus {
background-color: #80ffa6;
}

View File

@ -0,0 +1,21 @@
<script>
import "./Home.css"
import TablaEditaParada from "./TablaEditaParada.svelte";
</script>
<div class="card">
<div class="card-header">
<button class="btn btn-primary"><i class="bi bi-save"></i> Guardar</button>
</div>
<div class="card-body">
<TablaEditaParada parada={null} />
</div>
<div class="card-footer"></div>
</div>
<datalist id="ex1">
<option value="prueba1"></option>
<option value="prueba2"></option>
<option value="prueba3"></option>
</datalist>

View File

@ -0,0 +1,184 @@
<script>
export let parada = null;
let table = null;
let data = [{}];
function onKeyDownCell(ev) {
if (ev.key === "Escape") {
ev.target.blur();
return;
}
if (ev.key === "Enter") {
ev.returnValue = false;
const { nextElementSibling: nextTd } = ev.target;
if (nextTd && nextTd.contentEditable === "true") {
nextTd.focus();
} else if (nextTd && nextTd.querySelector("input")) {
nextTd.querySelector("input").focus();
} else {
ev.target.blur();
const { cellIndex } = ev.target;
const rowTable = ev.target.parentElement;
if (rowTable.children.length === cellIndex + 1) {
data = [...data, {}];
setTimeout(() => {
table
.querySelector("tbody > tr:last-child > td")
.focus();
}, 0);
}
}
return;
}
if (ev.key === "ArrowDown") {
const { cellIndex } = ev.target;
const { rowIndex } = ev.target.parentElement;
const rowTable =
table.querySelectorAll("tbody tr")[rowIndex] || null;
if (rowTable) {
ev.target.blur();
const cellTable = rowTable.querySelectorAll("td")[cellIndex];
cellTable.focus();
}
return;
}
if (ev.key === "ArrowUp") {
const { cellIndex } = ev.target;
const { rowIndex } = ev.target.parentElement;
const rowTable =
table.querySelectorAll("tbody tr")[rowIndex - 2] || null;
if (rowTable) {
ev.target.blur();
const cellTable = rowTable.querySelectorAll("td")[cellIndex];
cellTable.focus();
}
return;
}
}
function onKeyDownInput(ev) {
if (ev.key === "Enter") {
ev.returnValue = false;
const { nextElementSibling: nextTd } = ev.target.parentElement;
if (nextTd && nextTd.contentEditable === "true") {
nextTd.focus();
} else if (nextTd.querySelector("input")) {
nextTd.querySelector("input").focus();
}
}
}
</script>
<div class="table-responsive">
<table
class="table table-hover table-bordered table-paraderos"
bind:this={table}
>
<thead>
<tr>
<th>Instalación</th>
<th>Secuencia</th>
<th>Comuna</th>
<th>Eje</th>
<th>Cod. Eje</th>
<th>Desde</th>
<th>Hasta</th>
<th>Uso Parada</th>
<th>Sentido</th>
<th>Tipo Parada</th>
<th>Nombre Corredor</th>
<th>Refugio</th>
<th>Categoría</th>
<th>Nombre Parada</th>
<th>Referencia Urbana</th>
</tr>
</thead>
<tbody>
{#each data as item}
<tr>
<td
contenteditable
data-name="instalacion"
bind:innerText={item.instalacion}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
data-name="secuencia"
bind:innerText={item.secuencia}
on:keydown={onKeyDownCell}
/>
<!-- <td contenteditable bind:innerText={item.comuna} on:keydown={onKeyDownCell}></td> -->
<td
><input
type="text"
list="ex1"
class="form-select"
on:keydown={onKeyDownInput}
/></td
>
<td
contenteditable
bind:innerText={item.eje}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.codigo_eje}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.desde}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.hasta}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.uso_parada}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.sentido}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.tipo_parada}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.nombre_corredor}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.refugio}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.categoria}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.nombre_parada}
on:keydown={onKeyDownCell}
/>
<td
contenteditable
bind:innerText={item.referencia_urbana}
on:keydown={onKeyDownCell}
/>
</tr>
{/each}
</tbody>
</table>
</div>

View File

@ -0,0 +1,66 @@
<script>
import { Link } from 'svelte-navigator'
import Paginate from '$/components/Paginate.svelte'
import { getPersonas } from '$/services/personas';
import PageTitle from '$/components/PageTitle.svelte';
let page = 1;
let limit = 15;
let personas = { results: [], count: 0 };
let loading = false;
$: onPage(page)
async function onPage(page) {
try {
loading = true
const offset = (page - 1) * limit;
personas = await getPersonas({ offset, limit })
} catch (error) {
alert(error.detail || error)
} finally {
loading = false;
}
}
</script>
<PageTitle {loading}>Personas</PageTitle>
<div class="card">
<div class="card-header">
<Link to='/personas/nuevo' class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Nuevo
</Link>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>RUT</th>
<th>Nombres</th>
<th>Apellido 1</th>
<th>Apellido 2</th>
<th>Teléfono</th>
</tr>
</thead>
<tbody>
{#each personas.results as p}
<tr>
<td><Link to={'/personas/' + p.rut}>{p.rut}-{p.dv}</Link></td>
<td>{p.nombres}</td>
<td>{p.apellido_a}</td>
<td>{p.apellido_b}</td>
<td>{p.fono}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div class="card-footer">
<Paginate
offset={(page - 1) / limit}
{limit}
count={personas.count}
on:page={ev => page = ev.detail}
/>
</div>
</div>

View File

@ -0,0 +1,84 @@
<script>
import { getTiposPersona } from "$/services/tipos_persona";
export let form = {}
export let es_nuevo = true
let tipos_tratamiento = [];
let rut = '';
$: begin(es_nuevo)
$: !es_nuevo && (rut = `${form.rut || ''}-${form.dv || ''}`);
async function begin(es_nuevo) {
try {
tipos_tratamiento = await getTiposPersona()
} catch (error) {
alert(error.detail || error)
}
}
function onChangeRut({ target: { value } }) {
const [ rut = null, dv = null ] = value.split('-')
form = { ...form, rut, dv }
}
</script>
<div class="row">
<div class="col-md mb-3">
RUT
<input type="text" value={rut} class="form-control" required disabled={!es_nuevo} on:input={onChangeRut}>
</div>
<div class="col-md mb-3">
Tipo Tratamiento
<select bind:value={form.id_tipo_tratamiento} class="form-select">
<option value=""></option>
{#each tipos_tratamiento as tipo}
<option value={tipo.id_tipo_tratamiento}>{tipo.tratamiento}</option>
{/each}
</select>
</div>
</div>
<div class="row">
<div class="col-md mb-3">
Nombres
<input type="text" bind:value={form.nombres} class="form-control" required>
</div>
<div class="col-md mb-3">
Apellido 1
<input type="text" bind:value={form.apellido_a} class="form-control" required>
</div>
<div class="col-md mb-3">
Apellido 2
<input type="text" bind:value={form.apellido_b} class="form-control">
</div>
</div>
<div class="row">
<div class="col-md mb-3">
Teléfono
<input type="tel" bind:value={form.fono} class="form-control">
</div>
<div class="col-md mb-3">
Email
<input type="email" bind:value={form.email} class="form-control" required>
</div>
<div class="col-md mb-3">
Fecha de nacimiento
<input type="date" bind:value={form.fecha_nacimiento} class="form-control">
</div>
</div>
<div class="row">
<div class="col-md mb-3">
Comuna
<select bind:value={form.id_comuna} class="form-select">
<option value=""></option>
</select>
</div>
<div class="col-md mb-3">
Dirección
<input type="text" bind:value={form.direccion} class="form-control">
</div>
</div>

View File

@ -0,0 +1,67 @@
<script>
import { onMount } from 'svelte'
import { storeLayout } from '$/stores/global'
import FormPersona from './Form.svelte'
import { Link, useParams, useNavigate } from 'svelte-navigator';
import { getPersona, createPersona, updatePersona } from '$/services/personas';
import PageTitle from '../../components/PageTitle.svelte';
onMount(() => {
$storeLayout.showSidebar = false;
return () => {
$storeLayout.showSidebar = true;
}
})
const params = useParams()
const navigate = useNavigate()
let es_nuevo = true;
let form = {}
let loading = false;
$: es_nuevo = !$params.rut
$: begin($params)
async function begin({ rut = null }) {
try {
if (!rut) return; // si es nuevo, no busco informacion
loading = true
form = await getPersona(rut)
} catch (error) {
alert(error.detail || error)
} finally {
loading = false
}
}
async function onSave() {
try {
if (es_nuevo) {
// para crear un usuario, es requerido enviar los datos de la persona ademas.
await createPersona(form)
} else {
await updatePersona(form)
}
alert('Datos guardados con exito!')
navigate('/personas')
} catch (error) {
alert(error.detail || error)
}
}
</script>
<PageTitle {loading}>{es_nuevo ? 'Nuevo Persona': 'Editar Persona'}</PageTitle>
<form action="" on:submit|preventDefault={onSave}>
<div class="card">
<div class="card-header d-flex">
<button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> Guardar</button>
<div class="m-auto"></div>
<Link to="/personas" class="btn btn-outline-secondary">Volver</Link>
</div>
<div class="card-body">
<h4 class="h4 mb-3">Datos de la persona</h4>
<FormPersona bind:form={form} {es_nuevo} />
</div>
</div>
</form>

View File

@ -0,0 +1,109 @@
<script>
import Paginate from "$/components/Paginate.svelte";
import { getRoles } from "$/services/roles";
import PageTitle from "$/components/PageTitle.svelte";
import ModalRol from "./ModalRol.svelte";
const limit = 15;
let page = 1;
let offset = 0;
let count = 0;
let ordering = 'id_rol'
let roles = []
let rol = null
let loading = false;
$: onPage(page)
async function onPage(p) {
try {
loading = true
offset = (p - 1) * limit;
const data = await getRoles({ offset, limit, ordering })
roles = data.results;
count = data.count;
} catch (error) {
alert(error)
} finally {
loading = false;
}
}
function onEdita(item) {
rol = item;
}
function onNuevo() {
rol = {}
}
function onOrderBy(field) {
ordering = ordering === field ? '-' + field : field;
onPage(page)
}
</script>
<PageTitle {loading}>Roles</PageTitle>
<div class="card">
<div class="card-header">
<button class="btn btn-primary" on:click|preventDefault={onNuevo}>
<i class="bi bi-plus-lg"></i> Nuevo
</button>
</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_rol')}>ID</a>
{#if ordering === 'id_rol'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-id_rol'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('nombre_rol')}>Nombre</a>
{#if ordering === 'nombre_rol'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-nombre_rol'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
</tr>
</thead>
<tbody>
{#each roles as app, index}
<tr>
<td class="table-light">{offset + index + 1}</td>
<td>{app.id_rol}</td>
<td><a href={"#"} on:click|preventDefault={() => onEdita(app)}>{app.nombre_rol}</a></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
{offset}
{limit}
{count}
on:page={ev => page = ev.detail}
/>
</div>
</div>
{#if rol}
<ModalRol
{rol}
on:close={() => rol = null}
on:refresh={() => onPage(page)}
/>
{/if}
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -0,0 +1,275 @@
<script>
import Modal from "../../components/Modal.svelte";
import { getRol, createRol, updateRol, deleteRol } from "$/services/roles";
import { getAplicaciones } from "$/services/aplicaciones";
import { getRolesyaplicaciones , createRolyaplicacion , updateRolyaplicacion , deleteRolyaplicacion } from "$/services/rolesyaplicaciones";
import { createEventDispatcher } from "svelte";
import { getRolesaplicaciones } from "../../services/rolesyaplicaciones";
const dispatch = createEventDispatcher();
export let rol = {}
let form = {}
let loading = false;
let form2 = []
let form3 = []
let rolaplicaciones = []
let form_inicio = []
$: begin(rol.id_rol)
$: begin2(rol.id_rol)
async function begin(id) {
try {
if (!id) return;
form = await getRol(id) || {}
} catch (error) {
alert(error.detail || error)
}
}
async function begin2(id) {
try {
form2 = await getAplicaciones() || []
if (!id) {
rolaplicaciones = []
}
else{
rolaplicaciones = await getRolesyaplicaciones(id)
}
form3 = combinarArreglos(form2,rolaplicaciones)
for(const obj1 of form3){
const nuevoItem = {
id_rol_app: obj1.id_rol_app,
id_aplicacion: obj1.id_aplicacion,
nombre_app: obj1.nombre_app,
vigente: obj1.vigente,
id_rol:obj1.id_rol,
solo_visualizar:obj1.solo_visualizar,
acceso:obj1.acceso
};
form_inicio.push(nuevoItem)
}
} catch (error) {
alert(error.detail || error)
}
}
async function onSave() {
try {
loading = true;
if (rol.id_rol) {
form = await updateRol(form)
form3 = CrearEditarEliminar(rol.id_rol,form_inicio,form3)
for(const obj1 of form3){
if (obj1.opcion == "Crear"){
await createRolyaplicacion(obj1)
}
else if (obj1.opcion == "Editar"){
await updateRolyaplicacion(obj1)
}
else if (obj1.opcion == "Eliminar"){
await deleteRolyaplicacion(obj1.id_rol_app)
}
}
} else {
form = await createRol(form)
form3 = Arreglo_crear(form3,form.id_rol)
for(const obj1 of form3){
await createRolyaplicacion(obj1)
}
}
alert('Se ha guardado la aplicación')
dispatch('refresh')
dispatch('close')
} catch (error) {
if (error.detail) {
alert(error.detail)
} else {
alert(JSON.stringify(error))
}
} finally {
loading = false;
}
}
async function onDelete() {
try {
if (!confirm('Eliminará el registro?')) return;
loading = true;
for(const obj1 of form_inicio){
if (obj1.id_rol_app != false){
if(obj1.acceso = true)
await deleteRolyaplicacion(obj1.id_rol_app)
}
}
await deleteRol(form.id_rol)
alert('Se ha eliminado la aplicación')
dispatch('refresh')
dispatch('close')
} catch (error) {
alert(error.detail || error)
} finally {
loading = false;
}
}
function combinarArreglos(arreglo1, arreglo2) {
const resultado = [];
for (const obj1 of arreglo1) {
const objResultado = { ...obj1 };
const obj2 = arreglo2.find(item => item.id_aplicacion === obj1.id_aplicacion);
if (obj2) {
objResultado.id_rol = obj2.id_rol !== undefined ? obj2.id_rol : null;
objResultado.solo_visualizar = obj2.solo_visualizar !== undefined ? obj2.solo_visualizar : null;
objResultado.id_rol_app = obj2.id_rol_app !== undefined ? obj2.id_rol_app : null;
objResultado.acceso = true;
} else {
objResultado.id_rol = null;
objResultado.solo_visualizar = null;
objResultado.acceso = false;
objResultado.id_rol_app = false;
}
resultado.push(objResultado);
}
return resultado;
}
function Arreglo_crear(input_data,id) {
const result = [];
for (const item of input_data) {
if (item.acceso) {
result.push({
"id_aplicacion": item.id_aplicacion,
"id_rol": id,
"solo_visualizar": item.solo_visualizar
});
}
}
return result;
}
function CrearEditarEliminar(id, arregloInicial, arregloFinal) {
const resultado = [];
for (let i = 0; i < arregloInicial.length; i++) {
const inicio = arregloInicial[i];
const fin = arregloFinal[i];
const nuevoItem = {
id_aplicacion: inicio.id_aplicacion,
id_rol: id,
solo_visualizar: fin.solo_visualizar,
};
if (inicio.acceso !== fin.acceso) {
nuevoItem.opcion = inicio.acceso ? "Eliminar" : "Crear";
if(inicio.acceso == true){
nuevoItem.id_rol_app = inicio.id_rol_app
}
} else if (inicio.solo_visualizar !== fin.solo_visualizar) {
nuevoItem.opcion = "Editar";
nuevoItem.id_rol_app = inicio.id_rol_app
} else {
nuevoItem.opcion = null;
}
resultado.push(nuevoItem);
}
return resultado;
}
</script>
<form action="" on:submit|preventDefault={onSave}>
<Modal title={'rol #' + (rol.id_rol || 'Nuevo')}
size="lg"
on:close={() => dispatch('close')}>
<div class="form">
<div class="row mb-3">
<div class="col-md-3">Rol ID</div>
<div class="col-md">
{#if rol.id_rol}
<input type="number" value={form.id_rol} disabled class="form-control">
{:else}
<input type="number" bind:value={form.id_rol} required class="form-control">
{/if}
</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Nombre Rol</div>
<div class="col-md">
<input type="text" bind:value={form.nombre_rol} required class="form-control">
</div>
</div>
<h4 class="h4 mb-3">Permiso a aplicaciones </h4>
<pre>
</pre>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Aplicaciones</th>
<th>Acceso</th>
<th>Solo visualizar</th>
</tr>
</thead>
<tbody>
{#each form3 as aplicacion}
<tr>
<td>{aplicacion.nombre_app}</td>
<td><input type="checkbox" bind:checked={aplicacion.acceso} ></td>
<td><input type="checkbox" bind:checked={aplicacion.solo_visualizar} disabled = {!aplicacion.acceso} ></td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</div>
<svelte:fragment slot="buttons">
<button class="btn btn-primary"type="submit" disabled={loading}>Guardar</button>
<button class="btn btn-danger" on:click|preventDefault={onDelete} disabled={loading}>Eliminar</button>
</svelte:fragment>
</Modal>
</form>

View File

@ -0,0 +1,121 @@
<script>
import Paginate from "$/components/Paginate.svelte";
import { getRolesaplicaciones , getRolesyaplicaciones } from "$/services/rolesyaplicaciones";
import PageTitle from "$/components/PageTitle.svelte";
import ModalRol from "./ModalRolaplicacion.svelte";
const limit = 15;
let page = 1;
let offset = 0;
let count = 0;
let ordering = 'id_rol'
let roles = []
let rol = null
let loading = false;
let data2 = []
$: onPage(page)
async function onPage(p) {
try {
loading = true
offset = (p - 1) * limit;
const data = await getRolesaplicaciones({ offset, limit, ordering })
data2= await getRolesyaplicaciones(1)
roles = data.results;
count = data.count;
} catch (error) {
alert(error)
} finally {
loading = false;
}
}
function onEdita(item) {
rol = item;
}
function onNuevo() {
rol = {}
}
function onOrderBy(field) {
ordering = ordering === field ? '-' + field : field;
onPage(page)
}
</script>
<PageTitle {loading}>rolesyaplicaciones</PageTitle>
<div>
{data2}
{#each data2 as rol}
{rol.id_rol}
{rol.id_aplicacion}
{/each}
</div>
<div class="card">
<div class="card-header">
<button class="btn btn-primary" on:click|preventDefault={onNuevo}>
<i class="bi bi-plus-lg"></i> Nuevo
</button>
</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_aplicacion')}>ID rol</a>
{#if ordering === 'id_aplicacion'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-id_aplicacion'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
<th>
<a href={"#"} on:click|preventDefault={() => onOrderBy('id_rol')}>Id app</a>
{#if ordering === 'id_rol'}<i class="bi bi-caret-up-fill"></i>{/if}
{#if ordering === '-id_rol'}<i class="bi bi-caret-down-fill"></i>{/if}
</th>
<th style="width:5%">Solo Visualizar</th>
</tr>
</thead>
<tbody>
{#each roles as app, index}
<tr>
<td class="table-light">{offset + index + 1}</td>
<td>{app.id_rol}</td>
<td><a href={"#"} on:click|preventDefault={() => onEdita(app)}>{app.id_aplicacion}</a></td>
<td>{app.solo_visualizar}</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
{offset}
{limit}
{count}
on:page={ev => page = ev.detail}
/>
</div>
</div>
{#if rol}
<ModalRol
{rol}
on:close={() => rol = null}
on:refresh={() => onPage(page)}
/>
{/if}
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -0,0 +1,95 @@
<script>
import Modal from "../../components/Modal.svelte";
import { getRolyaplicacion, createRolyaplicacion, updateRolyaplicacion, deleteRolyaplicacion } from "$/services/rolesyaplicaciones";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let rol = {}
let form = {}
let loading = false;
$: begin(rol.id_aplicacion)
async function begin(id) {
try {
if (!id) return;
form = await getRolyaplicacion(id) || {}
} catch (error) {
alert(error.detail || error)
}
}
async function onSave() {
try {
loading = true;
if (rol.id_aplicacion) {
form = await updateRolyaplicacion(form)
} else {
form = await createRolyaplicacion(form)
}
alert('Se ha guardado la aplicación')
dispatch('refresh')
dispatch('close')
} catch (error) {
if (error.detail) {
alert(error.detail)
} else {
alert(JSON.stringify(error))
}
} finally {
loading = false;
}
}
async function onDelete() {
try {
if (!confirm('Eliminará el registro?')) return;
loading = true;
await deleteRolyaplicacion(form.id_aplicacion)
alert('Se ha eliminado la aplicación')
dispatch('refresh')
dispatch('close')
} catch (error) {
alert(error.detail || error)
} finally {
loading = false;
}
}
</script>
<form action="" on:submit|preventDefault={onSave}>
<Modal title={'Aplicacion #' + (rol.id_aplicacion || 'Nuevo')}
size="lg"
on:close={() => dispatch('close')}>
<div class="form">
<div class="row mb-3">
<div class="col-md-3">ID app</div>
<div class="col-md">
{#if rol.id_aplicacion}
<input type="number" value={form.id_aplicacion} disabled class="form-control">
{:else}
<input type="number" bind:value={form.id_aplicacion} required class="form-control">
{/if}
</div>
</div>
<div class="row mb-3">
<div class="col-md-3">ID rol</div>
<div class="col-md">
<input type="text" bind:value={form.id_rol} 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.solo_visualizar} role="switch" id="vigente">
<label class="form-check-label" for="vigente">solo_visualizar</label>
</div>
</div>
</div>
<svelte:fragment slot="buttons">
<button class="btn btn-primary"type="submit" disabled={loading}>Guardar</button>
<button class="btn btn-danger" on:click|preventDefault={onDelete} disabled={loading}>Eliminar</button>
</svelte:fragment>
</Modal>
</form>

View File

@ -0,0 +1,168 @@
<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} required>
<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} required>
<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} required>
<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} required>
<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 border border-secondary"
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}
required
/></td
>
</tr>
<tr>
<td
><input
type="text"
class="form-control"
placeholder="Linea 2"
bind:value={form.linea2}
required
/></td
>
</tr>
<tr>
<td
><input
type="text"
class="form-control"
placeholder="Linea 3"
bind:value={form.linea3}
required
/></td
>
</tr>
<tr class="color2">
<td
><input
type="text"
class="form-control"
placeholder="Linea 4"
bind:value={form.linea4}
required
/></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,32 @@
<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 {
loading = true
const linea = await getLinea(id_linea)
const letrero = await getLetreroLUR(linea.route_short_name).catch(() => Promise.resolve({}))
ruta = { ...linea, ...letrero }
} catch (error) {
console.log({ error })
} finally {
loading = false
}
}
</script>
<PageTitle {loading}>Rutas de Buses</PageTitle>
<TableRutas on:loading={ev => loading = ev.detail} on:select={ev => onEdita(ev.detail)} />
{#if ruta}
<ModalFormRuta {ruta} on:close={() => ruta = null} />
{/if}

View File

@ -0,0 +1,85 @@
<script>
import IconLoading from "../../components/IconLoading.svelte";
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 = {}
let error_messages = null;
let loading = false
$: form = { ...ruta }
async function onSave() {
try {
loading = true
error_messages = null
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).catch(() => {})
await createLetreroLUR({
codigo: route_short_name,
linea1, linea2, linea3, linea4,
bgcolor1, color1, bgcolor2, color2
})
dispatch('close')
} catch (error) {
const data = JSON.parse(error) || null
if (data) error_messages = data;
console.log({ data })
} finally {
loading = false
}
}
// 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>
<form on:submit|preventDefault={onSave}>
<Modal title="Ruta de Servicio" size="xl"
on:close={() => dispatch('close')}
>
<FormRuta {form} />
{#if error_messages}
<div class="alert alert-danger">
<ul>
{#each Object.entries(error_messages) as [field, message]}
<li>{field}: {message[0]}</li>
{/each}
</ul>
</div>
{/if}
<svelte:fragment slot="buttons">
<button class="btn btn-primary" type="submit" disabled={loading}><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} -->
{#if loading}<IconLoading />{/if}
</svelte:fragment>
</Modal>
</form>

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

@ -0,0 +1 @@
NO EXISTE

View File

@ -0,0 +1,16 @@
<script>
import PageTitle from "$/components/PageTitle.svelte";
</script>
<PageTitle>Inicio</PageTitle>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Empty card</h5>
</div>
<div class="card-body">
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,73 @@
<script>
import { Link } from 'svelte-navigator'
import { createToken } from '$/services/login'
let form = { username: '', pass: '' }
let message_error = '';
let loading = false;
// validar usuario contraseña
async function onIngresar() {
try {
loading = true;
message_error = ''
const { token } = await createToken(form)
sessionStorage.setItem('token', token)
document.location.reload();
} catch (error) {
message_error = error.message || error
setTimeout(() => message_error = '', 3000)
} finally {
loading = false;
}
}
</script>
<div class="container p-4 bg-neutral box-login">
<div style="margin-top: 100px;"></div>
<div class="card mb-4 m-auto">
<div class="card-header bg-dark text-center p-3">
<h2 class="text-light">Acceso</h2>
</div>
<div class="card-body">
<form on:submit|preventDefault={onIngresar}>
<div class="mb-3">
<label class="form-label" for={null}>Cuenta</label>
<input class="form-control form-control-lg" type="text" bind:value={form.username} required
placeholder="Ingrese su cuenta de usuario">
</div>
<div class="mb-3">
<label class="form-label" for={null}>Contraseña</label>
<input class="form-control form-control-lg" type="password" bind:value={form.password} required
placeholder="Ingrese su contraseña">
</div>
{#if message_error}
<div class="mb-3 text-danger">{message_error}</div>
{/if}
<Link to="/recovery">Olvido su contraseña?</Link>
<div class="text-center mt-3">
<button type="submit" class="btn btn-dark" disabled={loading}>
<span class="fa fa-key mr-3"></span>
Ingresar al sistema
</button>
</div>
</form>
</div>
</div>
</div>
<style>
.box-login {
height: 100vh;
max-width: inherit;
}
.box-login .card {
width: 400px;
max-width: 100%;
min-width: 200px;
}
</style>

View File

@ -0,0 +1 @@
En desarrollo

View File

@ -0,0 +1 @@
En desarrollo

View File

@ -0,0 +1,82 @@
<script>
import { Link } from 'svelte-navigator'
import Paginate from '$/components/Paginate.svelte'
import PageTitle from '$/components/PageTitle.svelte'
import { getUsuarios } from '$/services/usuarios'
let usuarios = { count: 0, results: [] }
let page = 1
const limit = 15
let loading = false
$: onPage(page)
async function onPage(page) {
try {
loading = true
const offset = (page - 1) * limit;
usuarios = await getUsuarios({ offset, limit, ordering: 'login' })
} catch(error) {
alert(error.detail || error)
} finally {
loading = false;
}
}
</script>
<PageTitle {loading}>Usuarios</PageTitle>
<div class="card">
<div class="card-header">
<div class="mb-3 d-flex">
<Link to='/usuarios/nuevo' class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Nuevo
</Link>
<div class="m-auto"></div>
<Link to='/' class="btn btn-outline-secondary">Volver</Link>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr class="table-light">
<th>Login</th>
<th>Nombres</th>
<th>Apellido 1</th>
<th>Apellido 2</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{#each usuarios.results as row}
<tr>
<td><Link to={"/usuarios/" + row.login}>{row.login}</Link></td>
<td>{row.persona.nombres}</td>
<td>{row.persona.apellido_a}</td>
<td>{row.persona.apellido_b}</td>
<td>{row.persona.email}</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
offset={(page - 1) * limit}
{limit}
count={usuarios.count}
on:page={ev => page = ev.detail}
/>
</div>
</div>
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -0,0 +1,29 @@
<script>
export let form = {}
export let es_nuevo = true
export let clave2 = ''
</script>
<div class="row">
<div class="col-md mb-3">
Login
<input type="text" bind:value={form.login} class="form-control" required disabled={!es_nuevo}>
</div>
<div class="col-md mb-3 pt-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" bind:checked={form.vigente} id="usuario-vigente">
<label class="form-check-label" for="usuario-vigente">Vigente</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md mb-3">
Contraseña
<input type="password" bind:value={form.clave} class="form-control" required={es_nuevo || !!clave2}>
</div>
<div class="col-md mb-3">
Repita Contraseña
<input type="password" bind:value={clave2} class="form-control" required={es_nuevo || !!form.clave}>
</div>
</div>

View File

@ -0,0 +1,65 @@
<script>
import { storeSession } from "$/stores/global";
import PageTitle from "$/components/PageTitle.svelte";
let persona = {}
let avatar = null;
$: persona = $storeSession.persona || {};
$: avatar = $storeSession?.avatar_img || '/avatars/avatar2.png';
</script>
<PageTitle>Perfil de usuario</PageTitle>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-2">
<img class="img-fluid" width="100%" alt="" src={avatar} />
<section class="section">
<div class="section-body">
<p>Twitter del perfil: <br />
@twitter
</p>
</div>
</section>
</div>
<div class="col">
<section class="section">
<div class="section-header with-profile-charge">
<h4 class="pb-2 border-bottom border-accent">{persona.nombres}</h4>
<div class="section-icons">
<a class="link-icon" href={"#"}><i class="cl cl-facebook"></i></a>
<a class="link-icon" href={"#"}><i class="cl cl-twitter"></i></a>
</div>
<div class="profile-charge">{persona.apellido_a} {persona.apellido_b}</div>
</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>
</section>
</div>
</div>
</div>
<div class="card-footer">
<section class="section">
<div class="section-header">
<h6 class="pb-2 border-bottom border-accent">Información de contacto:</h6>
</div>
<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

@ -0,0 +1,98 @@
<script>
import { onMount } from 'svelte'
import { storeLayout } from '$/stores/global'
import PageTitle from '$/components/PageTitle.svelte';
import FormPersona from '../personas/Form.svelte'
import FormUsuario from './Form.svelte'
import { Link, useParams, useNavigate } from 'svelte-navigator';
import { getUsuario, createUsuario, updateUsuario, deleteUsuario } from '$/services/usuarios';
import { updatePersona } from '$/services/personas';
onMount(() => {
$storeLayout.showSidebar = false;
return () => {
$storeLayout.showSidebar = true;
}
})
const params = useParams()
const navigate = useNavigate()
let es_nuevo = true;
let form_persona = {}
let form_usuario = {}
let clave2 = ''
let loading = false;
$: es_nuevo = !$params.login
$: begin($params)
async function begin({ login = null }) {
try {
if (!login) return; // si es nuevo, no busco informacion
loading = true
const { persona, ...data } = await getUsuario(login)
form_persona = persona;
form_usuario = data;
} catch (error) {
console.error(error)
alert(error.detail || error)
} finally {
loading = false;
}
}
async function onSave() {
try {
if (form_usuario.clave && form_usuario.clave !== clave2) {
return alert('Segunda contraseña debe coincidir')
}
if (es_nuevo) {
// para crear un usuario, es requerido enviar los datos de la persona ademas.
await createUsuario({ ...form_persona, ...form_usuario})
} else {
await updateUsuario(form_usuario)
await updatePersona(form_persona)
}
alert('Datos guardados con exito!')
navigate('/usuarios')
} catch (error) {
alert(error.detail || error)
}
}
async function onDelete() {
try {
if (!confirm(`Desea eliminar a ${form_usuario.login}?`)) return;
await deleteUsuario($params.login)
alert('Registro eliminado con exito!')
navigate('/usuarios')
} catch (error) {
alert(error.detail || error)
}
}
</script>
<PageTitle {loading}>{es_nuevo ? 'Nuevo Usuario': 'Editar Usuario'}</PageTitle>
<form action="" on:submit|preventDefault={onSave}>
<div class="card">
<div class="card-header d-flex">
<button type="submit" class="btn btn-primary"><i class="bi bi-save"></i> Guardar</button>
{#if !es_nuevo}
<button type="button" class="btn btn-danger ms-2"
on:click|preventDefault={onDelete}>
<i class="bi bi-trash"></i> Eliminar
</button>
{/if}
<div class="m-auto"></div>
<Link to="/usuarios" class="btn btn-outline-secondary">Volver</Link>
</div>
<div class="card-body">
<h4 class="h4 mb-3">Datos de la persona</h4>
<FormPersona bind:form={form_persona} {es_nuevo} />
<hr>
<h4 class="h4 my-3">Datos del usuario</h4>
<FormUsuario bind:form={form_usuario} bind:clave2={clave2} {es_nuevo} />
</div>
</div>
</form>

View File

@ -0,0 +1,10 @@
import PageLogin from '$/pages/site/Login.svelte'
import PageRecovery from '$/pages/site/Recovery.svelte'
import PagePassword from '$/pages/site/Password.svelte'
export const routes = [
{ path: '/', component: PageLogin },
{ path: '/recovery', component: PageRecovery },
{ path: '/new-password', component: PagePassword },
{ path: '*', component: PageLogin },
]

View File

@ -0,0 +1,37 @@
import PageHome from '$/pages/site/Home.svelte'
import PagePerfil from '$/pages/usuarios/Perfil.svelte'
import PageError from '$/pages/site/Error.svelte'
import PageAplicaciones from '$/pages/aplicaciones/Admin.svelte'
import PageUsuarios from '$/pages/usuarios/Admin.svelte'
import PageUsuarioCreate from '$/pages/usuarios/Usuario.svelte'
import PageUsuarioModifica from '$/pages/usuarios/Usuario.svelte'
import PageComunas from '$/pages/comunas/Admin.svelte'
import PagePersonas from '$/pages/personas/Admin.svelte'
import PagePersonaCreate from '$/pages/personas/Persona.svelte'
import PagePersonaModifica from '$/pages/personas/Persona.svelte'
import PageMapaParaderos from '$/pages/mapas/Paraderos.svelte'
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 PageRutas from "$/pages/rutas/Home.svelte";
export const routes = [
{ path: '/', component: PageHome },
{ path: '/perfil', component: PagePerfil },
{ path: '/aplicaciones', component: PageAplicaciones },
{ path: '/usuarios', component: PageUsuarios },
{ path: '/usuarios/nuevo', component: PageUsuarioCreate },
{ path: '/usuarios/:login', component: PageUsuarioModifica },
{ path: '/roles', component: PageRoles },
{ path: '/rolesaplicaciones', component: PageRolesyAplicaciones },
{ path: '/comunas', component: PageComunas },
{ path: '/personas', component: PagePersonas },
{ path: '/personas/nuevo', component: PagePersonaCreate },
{ path: '/personas/:rut', component: PagePersonaModifica },
{ path: '/mapas/paraderos', component: PageMapaParaderos },
{ path: '/mapas/rutas', component: PageMapaRutas },
{ path: '/paraderos', component: PageParaderos },
{ path: '/rutas', component: PageRutas },
{ path: '*', component: PageError },
]

View File

@ -0,0 +1,3 @@
export const url_base = document.location.origin;
export const base = import.meta.env.VITE_BACKEND || '/api';
export const getToken = () => sessionStorage.getItem('token') || null;

View File

@ -0,0 +1,48 @@
import { base, getToken } from './_config'
export async function getAplicaciones(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/aplicaciones/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function getAplicacion(id) {
const res = await fetch(`${base}/aplicaciones/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function createAplicacion(data) {
const res = await fetch(`${base}/aplicaciones/`, {
method: 'POST',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function updateAplicacion({ id_aplicacion: id, ...data }) {
const res = await fetch(`${base}/aplicaciones/${id}/`, {
method: 'PATCH',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function deleteAplicacion(id) {
const res = await fetch(`${base}/aplicaciones/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.text()
}

View File

@ -0,0 +1,48 @@
import { base, getToken } from './_config'
export async function getComunas(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/comunas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getComuna(id) {
const res = await fetch(`${base}/comunas/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createComuna(data) {
const res = await fetch(`${base}/comunas/`, {
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 updateComuna({ id_comuna: id, ...data }) {
const res = await fetch(`${base}/comunas/${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 deleteComuna(id) {
const res = await fetch(`${base}/comunas/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.text()
}

View File

@ -0,0 +1,41 @@
import { base, getToken } from './_config'
export async function getDispositivos(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/dispositivos/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createDispositivo(data) {
const res = await fetch(`${base}/dispositivos/`, {
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 updateDispositivo({ id_dispositivo: id, ...data }) {
const res = await fetch(`${base}/dispositivos/${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 deleteDispositivo(id) {
const res = await fetch(`${base}/dispositivos/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.text()
}

View File

@ -0,0 +1,48 @@
import { base, getToken } from './_config'
export async function getLetrerosLUR(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/letreros-lur/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getLetreroLUR(id) {
const res = await fetch(`${base}/letreros-lur/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createLetreroLUR(data) {
const res = await fetch(`${base}/letreros-lur/`, {
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 updateLetreroLUR({ codigo: id = null, ...data }) {
const res = await fetch(`${base}/letreros-lur/${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 deleteLetreroLUR(id) {
const res = await fetch(`${base}/letreros-lur/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.text()
}

View File

@ -0,0 +1,64 @@
import { base, getToken } from './_config'
export async function getLineas(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/lineas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getLinea(id) {
const res = await fetch(`${base}/lineas/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createLinea(data) {
const res = await fetch(`${base}/lineas/`, {
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 updateLinea({ id_linea: id = null, ...data }) {
const res = await fetch(`${base}/lineas/${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 deleteLinea(id) {
const res = await fetch(`${base}/lineas/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getParaderosLinea(id_linea) {
const res = await fetch(`${base}/lineas/paraderos?id_linea=${id_linea}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getBusesLinea(id_linea) {
const res = await fetch(`${base}/lineas/buses?id_linea=${id_linea}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,19 @@
import { base, getToken } from "./_config";
export async function createToken({ username, password }) {
const res = await fetch(`${base}/auth/`, {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: { "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getInfoToken() {
const res = await fetch(`${base}/auth/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,20 @@
import { base, getToken } from './_config'
export async function getMarcasParaderos(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/mapas/paraderos/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getRutas(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/mapas/rutas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,5 @@
import { base, getToken } from "./_config";
export async function deleteNotificacion(id) {
//
}

View File

@ -0,0 +1,48 @@
import { base, getToken } from './_config'
export async function getOperadores(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/operadores/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getOperador(id) {
const res = await fetch(`${base}/operadores/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createOperador(data) {
const res = await fetch(`${base}/operadores/`, {
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 updateOperador({ id_operador: id = null, ...data }) {
const res = await fetch(`${base}/operadores/${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 deleteOperador(id) {
const res = await fetch(`${base}/operadores/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,123 @@
import { base, getToken } from './_config'
export function getUrlImagen(id_paradero) {
return `${base}/paraderos-image/${id_paradero}`
}
export async function getParaderos(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/paraderos/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getParadero(rut) {
const res = await fetch(`${base}/paraderos/${rut}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createParadero(data) {
const res = await fetch(`${base}/paraderos/`, {
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 updateParadero({ id_paradero: id = null, ...data }) {
const res = await fetch(`${base}/paraderos/${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 deleteParadero(id) {
const res = await fetch(`${base}/paraderos/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getParaderoImagenes(id_paradero) {
const res = await fetch(`${base}/paraderos-image/?id_paradero=${id_paradero}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createParaderoImagen(id_paradero, file) {
const form = new FormData()
form.append('id_paradero', id_paradero)
form.append('imagen', file)
form.append('content_type', file.type)
const res = await fetch(`${base}/paraderos-image/`, {
method: 'POST',
body: form,
headers: { "Authorization": `Bearer ${getToken()}` }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function deleteParaderoImagen(id_paradero_imagen) {
const res = await fetch(`${base}/paraderos-image/${id_paradero_imagen}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}` }
})
if (!res.ok) throw await res.text()
return res.text()
}
export async function getLineasParadero(id_paradero) {
const res = await fetch(`${base}/lineas-paradero/?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}/lineas-paradero/`, {
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}/lineas-paradero/${id_paradero_linea}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}` }
})
if (!res.ok) throw await res.text()
return res.text()
}
export async function getInfoPublic(id_paradero) {
const res = await fetch(`${base}/paraderos/info-public/${id_paradero}`, {
headers: { "Authorization": `Bearer ${getToken()}` }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,39 @@
import { base, getToken } from './_config'
export async function getPersonas(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/personas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getPersona(rut) {
const res = await fetch(`${base}/personas/${rut}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createPersona(data) {
const res = await fetch(`${base}/personas/`, {
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 updatePersona({ rut: id = null, ...data }) {
const res = await fetch(`${base}/personas/${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()
}

View File

@ -0,0 +1,48 @@
import { base, getToken } from './_config'
export async function getRegiones(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/regiones/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getRegion(id) {
const res = await fetch(`${base}/regiones/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createRegion(data) {
const res = await fetch(`${base}/regiones/`, {
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 updateRegion({ id, ...data }) {
const res = await fetch(`${base}/regiones/${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 deleteRegion(id) {
const res = await fetch(`${base}/regiones/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,47 @@
import { base, getToken } from './_config'
export async function getRoles(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/roles/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function getRol(id) {
const res = await fetch(`${base}/roles/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function createRol(data) {
const res = await fetch(`${base}/roles/`, {
method: 'POST',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function updateRol({ id_rol: id, ...data }) {
const res = await fetch(`${base}/roles/${id}/`, {
method: 'PATCH',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function deleteRol(id) {
const res = await fetch(`${base}/roles/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.text()
}

View File

@ -0,0 +1,58 @@
import { base, getToken } from './_config'
export async function getRolesyaplicaciones(idRol) {
const params = idRol ? { id_rol: idRol } : null; // Preparar los parámetros de la consulta
const query = params ? '?' + new URLSearchParams(params).toString() : '';
const res = await fetch(`${base}/rolyaplicacion/${query}`, {
headers: {"Authorization": `Bearer ${getToken()}`,"Content-Type": "application/json"}
});
if (!res.ok) throw await res.json();
return res.json();
}
export async function getRolesaplicaciones(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/rolyaplicacion/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function getRolyaplicacion(id) {
const res = await fetch(`${base}/rolyaplicacion/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function createRolyaplicacion(data) {
const res = await fetch(`${base}/rolyaplicacion/`, {
method: 'POST',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function updateRolyaplicacion({ id_rol_app: id, ...data }) {
const res = await fetch(`${base}/rolyaplicacion/${id}/`, {
method: 'PATCH',
body: JSON.stringify(data),
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}
export async function deleteRolyaplicacion(id) {
const res = await fetch(`${base}/rolyaplicacion/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.text()
}

View File

@ -0,0 +1,48 @@
import { base, getToken } from './_config'
export async function getRutas(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/rutas/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function getRuta(id) {
const res = await fetch(`${base}/rutas/${id}/`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}
export async function createRuta(data) {
const res = await fetch(`${base}/rutas/`, {
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 updateRuta({ id_paradero: id = null, ...data }) {
const res = await fetch(`${base}/rutas/${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 deleteRuta(id) {
const res = await fetch(`${base}/rutas/${id}/`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.text()
return res.json()
}

View File

@ -0,0 +1,11 @@
import { base, getToken } from './_config'
export async function getTiposDispositivo(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/tipos/dispositivo/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}

View File

@ -0,0 +1,11 @@
import { base, getToken } from './_config'
export async function getTiposPersona(params) {
const query = !params ? '' : '?' + (new URLSearchParams(params).toString());
const res = await fetch(`${base}/tipos/persona/${query}`, {
headers: { "Authorization": `Bearer ${getToken()}`, "Content-Type": "application/json" }
})
if (!res.ok) throw await res.json()
return res.json()
}

View File

@ -0,0 +1,48 @@
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 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, ...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

@ -0,0 +1,10 @@
import { writable } from 'svelte/store'
const cacheParaderos = JSON.parse(sessionStorage.getItem('cache-paraderos') || 'null') || null;
export const storeMessages = writable([]);
export const storeParaderos = writable(cacheParaderos);
export const storeSession = writable({});
export const storeLayout = writable({ showSidebar: true });
storeParaderos.subscribe(val => sessionStorage.setItem('cache-paraderos', JSON.stringify(val)))

2
src/vite-env.d.ts vendored 100644
View File

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

7
svelte.config.js 100644
View File

@ -0,0 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}

Some files were not shown because too many files have changed in this diff Show More