se agrega mantenedor de usuarios

master/frontend-google-map
Francisco Sandoval 2023-07-12 18:24:21 -04:00
parent 7bd6affb30
commit b8c7a24ae1
20 changed files with 442 additions and 16 deletions

View File

@ -11,6 +11,7 @@ services:
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 ;

View File

@ -21,7 +21,7 @@
import { onMount } from 'svelte'
import { getInfoToken } from '$/services/login'
import { storeSession } from '$/stores/session'
import { storeSession, storeLayout } from '$/stores/global'
onMount(() => {
document.dispatchEvent(new Event('DOMContentLoaded'));
@ -45,14 +45,18 @@
<main>
<div class="container">
<div class="row">
<div class="col-12 col-md-3 bd-sidebar d none d-md-block d-lg-block">
<Sidebar />
</div>
{#if $storeLayout.showSidebar}
<div class="col-12 col-md-3 bd-sidebar d none d-md-block d-lg-block">
<Sidebar />
</div>
{/if}
<div class="col-12 col-md-9">
<div class="col-12 col-md">
<section class="doc-section">
{#each routes as r}
<Route path={r.path} component={r.component} />
<Route path={r.path}>
<svelte:component this={r.component}/>
</Route>
{/each}
</section>
</div>

View File

@ -8,4 +8,5 @@
<div class="sidebar-header text-red mt-3">Mantenedores</div>
<SideLink to="/aplicaciones">Aplicaciones</SideLink>
<SideLink to="/usuarios">Usuarios</SideLink>
</nav>

View File

@ -82,7 +82,8 @@
</table>
</div>
<div>
<div class="d-flex">
<a href={"#"} class="btn btn-sm btn-outline-dark mr-3" on:click|preventDefault={() => onPage(page)}>Refrescar</a>
<Paginate
{offset}
{limit}

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" disabled={!es_nuevo} required on:input={onChangeRut}>
</div>
<div class="col-md mb-3">
Tipo Tratamiento
<select bind:value={form.id_tipo_tratamiento} class="form-control">
<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-control">
<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,34 @@
<script>
import Paginate from '$/components/Paginate.svelte'
let page = 1;
let limit = 15;
let count = 0;
let personas = [];
</script>
<h2 class="h3 mb-3">Personas</h2>
<div class="card">
<div class="card-header"><button class="btn btn-outline-primary">Nuevo</button></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>
</table>
</div>
<div class="card-footer">
<Paginate
offset={(page - 1) / limit}
{limit}
{count}
on:page={ev => page = ev.detail}
/>
</div>
</div>

View File

View File

@ -0,0 +1,70 @@
<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
$: onPage(page)
async function onPage(page) {
try {
const offset = (page - 1) * limit;
usuarios = await getUsuarios({ offset, limit, ordering: 'login' })
} catch(error) {
alert(error.detail || error)
}
}
</script>
<PageTitle>Usuarios</PageTitle>
<div class="mb-3 d-flex">
<Link to='/usuarios/nuevo' class="btn btn-pill-primary">Nuevo</Link>
<div class="m-auto"></div>
<Link to='/' class="btn btn-pill-secondary">Volver</Link>
</div>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr class="table-accent">
<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 class="d-flex">
<a href={"#"} class="btn btn-sm btn-outline-dark mr-3" on:click|preventDefault={() => onPage(page)}>Refrescar</a>
<Paginate
offset={(page - 1) * limit}
{limit}
count={usuarios.count}
on:page={ev => page = ev.detail}
/>
</div>
<style>
.table-responsive {
max-height: calc(100vh - 300px);
}
</style>

View File

@ -0,0 +1,89 @@
<script>
import { onMount } from 'svelte'
import { storeLayout } from '$/stores/global'
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 = ''
$: es_nuevo = !$params.login
$: begin($params)
$: console.log({ form_persona })
async function begin({ login = null }) {
try {
if (!login) return; // si es nuevo, no busco informacion
const { persona, ...data } = await getUsuario(login)
form_persona = persona;
form_usuario = data;
} catch (error) {
console.error(error)
alert(error.detail || error)
}
}
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>
<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 ml-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,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="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="persona-es_vigente" checked={form.vigente}>
<label class="custom-control-label" for="persona-es_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,11 @@
<script>
import { onMount } from 'svelte'
import { storeLayout } from '$/stores/global'
onMount(() => {
$storeLayout.showSidebar = false;
return () => {
$storeLayout.showSidebar = true;
}
})
</script>

View File

@ -1,9 +1,8 @@
<script>
import { storeSession } from "$/stores/session";
import { storeSession } from "$/stores/global";
import PageTitle from "$/components/PageTitle.svelte";
let persona = {}
$: persona = $storeSession.persona || {};
$: console.log({ persona })
</script>
<PageTitle>Perfil de usuario</PageTitle>

View File

@ -1,11 +1,17 @@
import PageHome from '$/pages/site/Home.svelte'
import PagePerfil from '$/pages/usuario/Perfil.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/Create.svelte'
import PageUsuarioModifica from '$/pages/usuarios/Create.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: '*', component: PageError },
]

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.json()
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.json()
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.json()
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.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,5 @@
import { writable } from 'svelte/store'
export const storeMessages = writable([]);
export const storeSession = writable({});
export const storeLayout = writable({ showSidebar: true });

View File

@ -1,3 +0,0 @@
import { writable } from 'svelte/store'
export const storeMessages = writable([]);

View File

@ -1,3 +0,0 @@
import { writable } from 'svelte/store'
export const storeSession = writable({});