diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 177d68a..e070b49 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { ConceptosTurneablesComponent } from './conceptos-turneables/components/ import { MonitoreoActivacionesComponent } from './monitor-activaciones/monitoreo-activaciones.component'; import { WebhookLogComponent } from './webhook-log/webhook-log.component'; import { EstadoFuentesAutenticasComponent } from './fuentes-autenticas/components/estado-fa.component'; +import { restriccionHudsComponent } from './restriccion-huds/restriccion-huds'; const appRoutes: Routes = [ { path: '', redirectTo: '/login', pathMatch: 'full' }, @@ -24,6 +25,7 @@ const appRoutes: Routes = [ { path: 'novedades', loadChildren: () => import('./registro-novedades/novedades.module').then(m => m.NovedadesModule) }, { path: 'rupers', loadChildren: () => import('./rupers/rupers.module').then(m => m.RupersModule) }, { path: 'fuentes-autenticas', component: EstadoFuentesAutenticasComponent, canActivate: [RoutingGuard] }, + { path: 'restriccion-huds', component: restriccionHudsComponent, canActivate: [RoutingGuard] }, { path: '**', redirectTo: '/home', pathMatch: 'full' } ]; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b0b3519..92a2896 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { environment } from './../environments/environment'; +import { environment } from '../environments/environment'; import { Server } from '@andes/shared'; import { Plex } from '@andes/plex'; import { Auth } from '@andes/auth'; @@ -34,8 +34,7 @@ export class AppComponent { if (this.auth.loggedIn()) { this.auth.organizaciones().subscribe(data => { if (data.length > 1) { - this.menuList = [{ label: 'Seleccionar Organización', icon: 'hospital-building', route: '/login/select-organizacion' }, - ...this.menuList]; + this.menuList = [{ label: 'Seleccionar Organización', icon: 'hospital-building', route: '/login/select-organizacion' }, ...this.menuList]; this.plex.updateMenu(this.menuList); } }); @@ -84,6 +83,10 @@ export class AppComponent { this.menuList.push({ label: 'Elementos RUP', icon: 'magnify', route: '/rupers/elementos-rup' }); } + if (this.auth.check('monitoreo:restriccionHuds')) { + this.menuList.push({ label: 'Restricciones a la HUDS', icon: 'magnify', route: '/restriccion-huds' }); + } + this.menuList.push({ label: 'Cerrar Sesión', icon: 'logout', route: '/login/logout' }); this.plex.updateMenu(this.menuList); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b234b06..3b335b9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,6 +7,9 @@ import { Server } from '@andes/shared'; import { routing } from './app-routing.module'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +// pipes +import { SharedModule } from '@andes/shared'; + // Components import { AppComponent } from './app.component'; import { HomeComponent } from './home/home.component'; @@ -30,7 +33,17 @@ import { ModulosModule } from './modulos/modulos.module'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { EstadoFuentesAutenticasComponent } from './fuentes-autenticas/components/estado-fa.component'; import { FuentesAutenticasService } from './fuentes-autenticas/services/fuentes-autenticas.service'; -import { TokenExpiredInterceptor } from './services/token-expired.interceptor'; +import { restriccionHudsComponent } from './restriccion-huds/restriccion-huds'; +import { UsuariosHttp } from './services/usuarios.service'; +import { PermisosService } from './services/permisos.service'; +import { PacienteBuscarComponent } from './restriccion-huds/paciente-buscar.component'; +import { PacienteListadoComponent } from './restriccion-huds/paciente-listado.component'; +import { PacienteBuscarService } from './services/paciente-buscar.service'; +import { PacienteCacheService } from './services/pacienteCache.service'; +import { PacienteService } from './services/paciente.service'; +import { AdjuntosService } from './services/adjuntos.service'; +import { GaleriaArchivosComponent } from './shared/galeria-archivos.component'; +import { ProfesionalService } from './services/profesional.service'; @NgModule({ declarations: [ @@ -43,7 +56,11 @@ import { TokenExpiredInterceptor } from './services/token-expired.interceptor'; NuevoConceptoTurneableComponent, MonitoreoActivacionesComponent, BuscadorSnomedComponent, - EstadoFuentesAutenticasComponent + EstadoFuentesAutenticasComponent, + restriccionHudsComponent, + PacienteBuscarComponent, + PacienteListadoComponent, + GaleriaArchivosComponent ], imports: [ BrowserModule, @@ -54,7 +71,8 @@ import { TokenExpiredInterceptor } from './services/token-expired.interceptor'; AuthModule, InfiniteScrollModule, ModulosModule, - NoopAnimationsModule + NoopAnimationsModule, + SharedModule ], providers: [ Server, @@ -70,11 +88,13 @@ import { TokenExpiredInterceptor } from './services/token-expired.interceptor'; WebhookLogService, ModulosService, FuentesAutenticasService, - { - provide: HTTP_INTERCEPTORS, - useClass: TokenExpiredInterceptor, - multi: true, - } + UsuariosHttp, + PermisosService, + PacienteCacheService, + PacienteService, + PacienteBuscarService, + AdjuntosService, + ProfesionalService ], bootstrap: [AppComponent] }) diff --git a/src/app/interfaces/IContacto.ts b/src/app/interfaces/IContacto.ts new file mode 100644 index 0000000..30368f4 --- /dev/null +++ b/src/app/interfaces/IContacto.ts @@ -0,0 +1,7 @@ +export interface IContacto { + tipo: any; + valor: string; + ranking: number; + activo: boolean; + ultimaActualizacion: Date; +} diff --git a/src/app/interfaces/ICreatedBy.ts b/src/app/interfaces/ICreatedBy.ts new file mode 100644 index 0000000..fe4468b --- /dev/null +++ b/src/app/interfaces/ICreatedBy.ts @@ -0,0 +1,11 @@ +export interface ICreatedBy { + documento: number; + username: number; + apellido: string; + nombre: string; + nombreCompleto: string; + organizacion: { + id: string; + nombre: string; + }; +} diff --git a/src/app/interfaces/IDireccion.ts b/src/app/interfaces/IDireccion.ts new file mode 100644 index 0000000..6761c19 --- /dev/null +++ b/src/app/interfaces/IDireccion.ts @@ -0,0 +1,11 @@ +import { IUbicacion } from './IUbicacion'; + +export interface IDireccion { + valor: String; + codigoPostal: String; + ubicacion: IUbicacion; + ranking: Number; + geoReferencia: [Number, Number]; + ultimaActualizacion: Date; + activo: Boolean; +} diff --git a/src/app/interfaces/IMatricula.ts b/src/app/interfaces/IMatricula.ts new file mode 100644 index 0000000..146bdc7 --- /dev/null +++ b/src/app/interfaces/IMatricula.ts @@ -0,0 +1,7 @@ +export interface IMatricula { + numero: Number; + descripcion: String; + activo: Boolean; + fechaInicio: Date; + fechaVencimiento: Date; +} diff --git a/src/app/interfaces/IObraSocial.ts b/src/app/interfaces/IObraSocial.ts new file mode 100644 index 0000000..a8f7119 --- /dev/null +++ b/src/app/interfaces/IObraSocial.ts @@ -0,0 +1,14 @@ +export interface IObraSocial { + id: string; + tipoDocumento: String; + dni: Number; + transmite: String; + nombre: String; + codigoFinanciador: Number; + financiador: String; + version: Date; + numeroAfiliado: String; + prepaga?: Boolean; + codigoPuco?: Number; + idObraSocial?: Number; +} diff --git a/src/app/interfaces/IPaciente.ts b/src/app/interfaces/IPaciente.ts new file mode 100644 index 0000000..afcbda9 --- /dev/null +++ b/src/app/interfaces/IPaciente.ts @@ -0,0 +1,106 @@ +import { IPacienteRelacion } from './IPacienteRelacion.inteface'; +import { IContacto } from './IContacto'; +import { IDireccion } from './IDireccion'; +import { EstadoCivil } from '../shared/enumerados'; +import { IUbicacion } from './IUbicacion'; +import { IObraSocial } from './IObraSocial'; +import { ICreatedBy } from './ICreatedBy'; + +export interface IPaciente { + id: string; + documento: string; + cuil: string; + activo: boolean; + estado: string; + nombre: string; + apellido: string; + nombreCompleto: string; + alias: string; + contacto: IContacto[]; + sexo: string; + genero: string; + tipoIdentificacion: string; + numeroIdentificacion: string; + fechaNacimiento: Date; // Fecha Nacimiento + edad: number; + edadReal: { valor: number; unidad: string }; + fechaFallecimiento: Date; + direccion: IDireccion[]; + lugarNacimiento?: IUbicacion; + estadoCivil: EstadoCivil; + fotoId: string; + foto: string; + createdBy: ICreatedBy; + relaciones: [IPacienteRelacion]; + financiador: [{ + codigoPuco: Number; + nombre: string; + financiador: string; + id: string; + numeroAfiliado: string; + }]; + identificadores: [{ + entidad: string; + valor: string; + }]; + claveBlocking: [string]; + entidadesValidadoras?: [string]; + scan: string; + reportarError: Boolean; + notaError: string; + carpetaEfectores?: [{ + organizacion: { + id: string; + nombre: string; + }; + nroCarpeta: string; + }]; + notas?: [{ + fecha: Date; + nota: string; + destacada: Boolean; + }]; + _score?: number; + vinculos: [string]; + documentos: { + fecha: Date; + tipo: { + id: string; + label: string; + }; + archivos: { + id: string; + ext: string; + }[]; + }[]; + idPacientePrincipal?: string; +} + +export interface IPacienteBasico { + id: string; + nombre: string; + apellido: string; + alias: string; + documento: string; + numeroIdentificacion: string; + sexo: string; + genero: string; + fechaNacimiento: Date; + obraSocial?: IObraSocial; + telefono?: string; + carpetaEfectores?: [{ + organizacion: { + id: string; + nombre: string; + }; + nroCarpeta: string; + }]; +} + +export interface IPacienteRestringido { + idPaciente: string; + observaciones: string; + createdBy: any; + createdAt: Date; + archivos?: any[]; +} diff --git a/src/app/interfaces/IPacienteMatch.inteface.ts b/src/app/interfaces/IPacienteMatch.inteface.ts new file mode 100644 index 0000000..87ebcc4 --- /dev/null +++ b/src/app/interfaces/IPacienteMatch.inteface.ts @@ -0,0 +1,8 @@ + +import { IPaciente } from './IPaciente'; + +export interface IPacienteMatch { + id: String; + paciente: IPaciente; + _score: number; +} diff --git a/src/app/interfaces/IPacienteRelacion.inteface.ts b/src/app/interfaces/IPacienteRelacion.inteface.ts new file mode 100644 index 0000000..0e394bb --- /dev/null +++ b/src/app/interfaces/IPacienteRelacion.inteface.ts @@ -0,0 +1,22 @@ + +export interface IPacienteRelacion { + id?: string; + relacion: { + id: string; + nombre: string; + opuesto: string; + }; + referencia: string; + nombre: string; + apellido: string; + documento: string; + numeroIdentificacion?: string; + fechaNacimiento?: Date; + fechaFallecimiento?: Date; + sexo?: string; + foto: any; + fotoId: any; + activo?: boolean; + alias?: string; + genero?: string; +} diff --git a/src/app/interfaces/IProfesional.ts b/src/app/interfaces/IProfesional.ts new file mode 100644 index 0000000..ad2152c --- /dev/null +++ b/src/app/interfaces/IProfesional.ts @@ -0,0 +1,84 @@ +import { IUbicacion } from './IUbicacion'; +// import { IMatricula } from './IMatricula'; +import { Sexo, Genero, EstadoCivil, tipoComunicacion } from '../shared/enumerados'; + +export interface IProfesional { + id: String; + documento: String; + activo: Boolean; + habilitado: boolean; + nombre: String; + apellido: String; + contacto: [{ + tipo: tipoComunicacion; + valor: String; + ranking: Number; // Specify preferred order of use (1 = highest) // Podemos usar el rank para guardar un historico de puntos de contacto (le restamos valor si no es actual???) + ultimaActualizacion: Date; + activo: Boolean; + }]; + sexo: Sexo; + genero: Genero; // identidad autopercibida + fechaNacimiento: Date; // Fecha Nacimiento + fechaFallecimiento: Date; + direccion: [{ + valor: String; + codigoPostal: String; + ubicacion: IUbicacion; + ranking: Number; + geoReferencia: { + type: [Number]; // [, ] + index: '2d'; // create the geospatial index + }; + ultimaActualizacion: Date; + activo: Boolean; + }]; + estadoCivil: EstadoCivil; + foto: String; + rol: String; // Ejemplo Jefe de Terapia intensiva + especialidad: [{ // El listado de sus especialidades + id: string; + nombre: String; + }]; + matriculas: [{ + numero: Number; + descripcion: String; + fechaInicio: Date; + fechaVencimiento: Date; + activo: Boolean; + }]; + formacionGrado: [{ + exportadoSisa?: Boolean; + profesion: { + nombre: string; + codigo: number; + tipoDeFormacion: String; + }; + entidadFormadora: { + nombre: string; + codigo: number; + }; + titulo: string; + fechaEgreso: Date; + fechaTitulo: Date; + renovacion: boolean; + papelesVerificados: boolean; + matriculacion?: [{ + matriculaNumero: Number; + libro: String; + folio: String; + inicio: Date; + baja: { + motivo: String; + fecha: any; + }; + notificacionVencimiento: Boolean; + fin: Date; + revalidacionNumero: Number; + }]; + matriculado: boolean; + fechaDeInscripcion?: Date; + }]; + profesionalMatriculado: Boolean; + profesionExterna: any; + matriculaExterna: String; +} diff --git a/src/app/interfaces/IUbicacion.ts b/src/app/interfaces/IUbicacion.ts new file mode 100644 index 0000000..b5802dd --- /dev/null +++ b/src/app/interfaces/IUbicacion.ts @@ -0,0 +1,19 @@ +export interface IUbicacion { + barrio: { + id: String; + nombre: String; + }; + localidad: { + id: String; + nombre: String; + }; + provincia: { + id: String; + nombre: String; + }; + pais: { + id: String; + nombre: String; + }; + lugar?: String; +} diff --git a/src/app/interfaces/IUsuario.ts b/src/app/interfaces/IUsuario.ts new file mode 100644 index 0000000..46ad0df --- /dev/null +++ b/src/app/interfaces/IUsuario.ts @@ -0,0 +1,10 @@ +export interface IUsuario { + id: string; + usuario: number; + nombre: string; + apellido: string; + documento: string; + foto: string; + disclaimers?: any[]; + pacienteRestringido: any[]; +} diff --git a/src/app/pipes/edad.pipe copy.ts b/src/app/pipes/edad.pipe copy.ts new file mode 100644 index 0000000..6ee2212 --- /dev/null +++ b/src/app/pipes/edad.pipe copy.ts @@ -0,0 +1,52 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import * as moment_ from 'moment'; +const moment = moment_; + +@Pipe({ name: 'edad', pure: false }) +// pure: false - Info: https://stackoverflow.com/questions/34456430/ngfor-doesnt-update-data-with-pipe-in-angular2 +export class EdadPipe implements PipeTransform { + transform(value: any): any { + const fechaLimite = value.fechaFallecimiento ? moment(value.fechaFallecimiento) : moment(); + const fechaNac = moment(value.fechaNacimiento, 'YYYY-MM-DD HH:mm:ss'); + const difDias = fechaLimite.diff(fechaNac, 'd'); // Diferencia en días + const difAnios = Math.floor(difDias / 365.25); + const difMeses = Math.floor(difDias / 30.4375); + const difHs = fechaLimite.diff(fechaNac, 'h'); // Diferencia en horas + const difMn = fechaLimite.diff(fechaNac, 'm'); // Diferencia en minutos + + if (difAnios !== 0) { + return `${String(difAnios)} años`; + } else if (difMeses !== 0) { + return `${String(difMeses)} meses`; + } else if (difDias !== 0) { + return `${String(difDias)} días`; + } else if (difHs !== 0) { + return `${String(difHs)} horas y ${difMn - (difHs * 60)} minutos`; + } else if (difMn !== 0) { + return `${String(difMn)} minutos`; + } + + return ''; + } +} + + +export function calcularEdad(fechaNacimiento: Date, unit: 'y' | 'm' | 'd' = 'y', hasta: Date = null) { + if (!fechaNacimiento) { + return null; + } + + const fechaLimite = hasta ? moment(hasta) : moment(); + const from = moment(fechaNacimiento); + + const difDias = fechaLimite.diff(from, 'd'); + + switch (unit) { + case 'y': + return Math.floor(difDias / 365.25); + case 'm': + return Math.floor(difDias / 30.4375); + case 'd': + return Math.floor(difDias); + } +} diff --git a/src/app/pipes/edad.pipe.ts b/src/app/pipes/edad.pipe.ts new file mode 100644 index 0000000..6ee2212 --- /dev/null +++ b/src/app/pipes/edad.pipe.ts @@ -0,0 +1,52 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import * as moment_ from 'moment'; +const moment = moment_; + +@Pipe({ name: 'edad', pure: false }) +// pure: false - Info: https://stackoverflow.com/questions/34456430/ngfor-doesnt-update-data-with-pipe-in-angular2 +export class EdadPipe implements PipeTransform { + transform(value: any): any { + const fechaLimite = value.fechaFallecimiento ? moment(value.fechaFallecimiento) : moment(); + const fechaNac = moment(value.fechaNacimiento, 'YYYY-MM-DD HH:mm:ss'); + const difDias = fechaLimite.diff(fechaNac, 'd'); // Diferencia en días + const difAnios = Math.floor(difDias / 365.25); + const difMeses = Math.floor(difDias / 30.4375); + const difHs = fechaLimite.diff(fechaNac, 'h'); // Diferencia en horas + const difMn = fechaLimite.diff(fechaNac, 'm'); // Diferencia en minutos + + if (difAnios !== 0) { + return `${String(difAnios)} años`; + } else if (difMeses !== 0) { + return `${String(difMeses)} meses`; + } else if (difDias !== 0) { + return `${String(difDias)} días`; + } else if (difHs !== 0) { + return `${String(difHs)} horas y ${difMn - (difHs * 60)} minutos`; + } else if (difMn !== 0) { + return `${String(difMn)} minutos`; + } + + return ''; + } +} + + +export function calcularEdad(fechaNacimiento: Date, unit: 'y' | 'm' | 'd' = 'y', hasta: Date = null) { + if (!fechaNacimiento) { + return null; + } + + const fechaLimite = hasta ? moment(hasta) : moment(); + const from = moment(fechaNacimiento); + + const difDias = fechaLimite.diff(from, 'd'); + + switch (unit) { + case 'y': + return Math.floor(difDias / 365.25); + case 'm': + return Math.floor(difDias / 30.4375); + case 'd': + return Math.floor(difDias); + } +} diff --git a/src/app/pipes/nombre.pipe.ts b/src/app/pipes/nombre.pipe.ts new file mode 100644 index 0000000..2389991 --- /dev/null +++ b/src/app/pipes/nombre.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'nombre', pure: false }) + +export class NombrePipe implements PipeTransform { + transform(value: any): any { + if (!value) { + return null; + } else if (value.alias) { + return value.apellido + ', ' + value.alias; + } else { + if (value.apellido && value.nombre) { + return value.apellido + ', ' + value.nombre; + } else { + return (value.apellido ? value.apellido : value.nombre); + } + } + } +} diff --git a/src/app/restriccion-huds/paciente-buscar.component.ts b/src/app/restriccion-huds/paciente-buscar.component.ts new file mode 100644 index 0000000..59d98c8 --- /dev/null +++ b/src/app/restriccion-huds/paciente-buscar.component.ts @@ -0,0 +1,92 @@ +import { Component, Output, EventEmitter, OnInit, OnDestroy, Input } from '@angular/core'; +import { Plex } from '@andes/plex'; +import { PacienteBuscarResultado } from './paciente-buscar.inteface'; +import { PacienteBuscarService } from '../services/paciente-buscar.service'; +import { Subscription } from 'rxjs'; + +interface PacienteEscaneado { + documento: string; + apellido: string; + nombre: string; + sexo: string; + fechaNacimiento: Date; + scan: string; +} + +@Component({ + selector: 'paciente-buscar', + templateUrl: 'paciente-buscar.html', + styleUrls: [] +}) + +export class PacienteBuscarComponent implements OnInit, OnDestroy { + public textoLibre: string = null; + public autoFocus = 0; + public routes; + private pacienteRoute = '/apps/mpi/paciente'; + private searchSubscription = new Subscription(); + get disabled() { + return !this.textoLibre || this.textoLibre.length === 0; + } + + @Input() hostComponent = ''; + @Input() create = false; + /* returnScannedPatient en true retorna un objeto con los datos del paciente escaneado en caso de + que este no estuviera registrado */ + @Input() returnScannedPatient = false; + + // Eventos + @Output() searchStart: EventEmitter = new EventEmitter(); + @Output() searchEnd: EventEmitter = new EventEmitter(); + @Output() searchClear: EventEmitter = new EventEmitter(); + + constructor( + private plex: Plex, + private pacienteBuscar: PacienteBuscarService) { + } + + public ngOnInit() { + this.autoFocus = this.autoFocus + 1; + this.routes = [ + { label: 'BEBÉ', route: `${this.pacienteRoute}/bebe/${this.hostComponent}` }, + { label: 'EXTRANJERO', route: `${this.pacienteRoute}/extranjero/${this.hostComponent}` }, + { label: 'CON DNI ARGENTINO', route: `${this.pacienteRoute}/con-dni/${this.hostComponent}` }, + { label: 'SIN DNI ARGENTINO', route: `${this.pacienteRoute}/sin-dni/${this.hostComponent}` }, + ]; + } + + ngOnDestroy(): void { + if (this.searchSubscription) { + this.searchSubscription.unsubscribe(); + } + } + + /** + * Busca paciente cada vez que el campo de busqueda cambia su valor + */ + public buscar($event) { + /* Error en Plex, ejecuta un change cuando el input pierde el foco porque detecta que cambia el valor */ + if ($event.type) { + return; + } + const textoLibre = (this.textoLibre && this.textoLibre.length) ? this.textoLibre.trim() : ''; + if (textoLibre && textoLibre.length) { + // Controla el scanner + if (!this.pacienteBuscar.controlarScanner(textoLibre)) { + this.plex.info('warning', 'El lector de código de barras no está configurado. Comuníquese con la Mesa de Ayuda de TICS'); + return; + } + if (this.searchSubscription) { + this.searchSubscription.unsubscribe(); + } + this.searchStart.emit(); + this.pacienteBuscar.search(textoLibre, this.returnScannedPatient).subscribe((data) => { + if (data) { + this.searchEnd.emit(data); + } + }); + } else { + this.searchClear.emit(); + } + } +} diff --git a/src/app/restriccion-huds/paciente-buscar.html b/src/app/restriccion-huds/paciente-buscar.html new file mode 100644 index 0000000..bf3967b --- /dev/null +++ b/src/app/restriccion-huds/paciente-buscar.html @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/src/app/restriccion-huds/paciente-buscar.inteface.ts b/src/app/restriccion-huds/paciente-buscar.inteface.ts new file mode 100644 index 0000000..6232a60 --- /dev/null +++ b/src/app/restriccion-huds/paciente-buscar.inteface.ts @@ -0,0 +1,8 @@ +// import { IPacienteMatch } from '../interfaces/IPacienteMatch.inteface'; + +export interface PacienteBuscarResultado { + err: any; + pacientes: any[]; + escaneado?: boolean; + scan?: string; +} diff --git a/src/app/restriccion-huds/paciente-listado.component.ts b/src/app/restriccion-huds/paciente-listado.component.ts new file mode 100644 index 0000000..e602010 --- /dev/null +++ b/src/app/restriccion-huds/paciente-listado.component.ts @@ -0,0 +1,173 @@ +import { IPacienteMatch } from './../interfaces/IPacienteMatch.inteface'; +import { IPaciente } from '../interfaces/IPaciente'; +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Plex } from '@andes/plex'; +import { PacienteBuscarService } from '../services/paciente-buscar.service'; +import { IPacienteRelacion } from '../interfaces/IPacienteRelacion.inteface'; +import { calcularEdad } from '../pipes/edad.pipe'; +// import { NombrePipe } from '../pipes/nombre.pipe'; +// import { EdadPipe } from '../pipes/edad.pipe'; + +@Component({ + selector: 'paciente-listado', + templateUrl: 'paciente-listado.html', + styleUrls: ['paciente-listado.scss'] +}) +export class PacienteListadoComponent { + private _pacientes: IPacienteMatch[] | IPaciente[] = []; + pacienteSeleccionado: IPaciente; + selectedId: string; + + // Propiedades públicas + public listado: IPaciente[]; // Contiene un listado plano de pacientes + public listadoRelaciones: IPacienteRelacion[]; + public desplegado: Boolean = false; + public seletedOn: Boolean = true; + public coloresItems = { + impar: { + border: '#00000000', + hover: '#00a8e099', + background: '#00a8e01a' + }, + par: { + border: '#00000000', + hover: '#00a8e099', + background: '#0027381a' + } + }; + /** + * Listado de pacientes para mostrar. Acepta una lista de pacientes o un resultado de una búsqueda + * + * @type {(IPacienteMatch[] | IPaciente[])} + */ + @Input() + get pacientes(): IPacienteMatch[] | IPaciente[] { + return this._pacientes; + } + set pacientes(value: IPacienteMatch[] | IPaciente[]) { + this._pacientes = value; + if (value && value.length) { + // Test if IPacienteMatch + if ('paciente' in value[0]) { + this.listado = (value as IPacienteMatch[]).map(i => i.paciente); + } else { + this.listado = value as IPaciente[]; + } + } else { + this.listado = []; + } + } + + /** + * Indica la altura del listado respecto a su contenedor + */ + + @Input() height: string | number = 80; + + /** + * Cantidad de pixeles a reducir de la pantalla completa. + */ + @Input() set offset(value: number) { + if (value) { + this.height = `calc(100% - ${value}px)`; + } + } + + // Indica si debe aparecer el boton 'editar' en cada resultado + @Input() editing = false; + + // Indica si debe aparecer el boton 'relaciones' en cada paciente + @Input() showRelaciones = true; + + // Evento que se emite cuando se selecciona un paciente (click en la lista) + @Output() selected: EventEmitter = new EventEmitter(); + + // Evento que se emite cuando se presiona el boton 'editar' de un paciente + @Output() edit: EventEmitter = new EventEmitter(); + + // Evento que se emite cuando se presiona el boton 'verRelaciones' de un paciente + @Output() relaciones: EventEmitter = new EventEmitter(); + + constructor( + private plex: Plex, + private pacienteBuscar: PacienteBuscarService) { } + + onScroll() { + this.pacienteBuscar.findByText().subscribe((resultado: any) => { + if (resultado) { + this.listado = this.listado.concat(resultado.pacientes); + } + }); + } + + public seleccionar(paciente: IPaciente) { + if (this.seletedOn) { + (paciente.id) ? this.selected.emit(paciente) : this.selected.emit(null); + } else { + this.seletedOn = true; + } + } + + public editar(paciente: IPaciente) { + (paciente.id) ? this.edit.emit(paciente) : this.edit.emit(null); + } + + public openBtnRelaciones(paciente: IPaciente) { + const igualPaciente = this.pacienteSeleccionado?.id === paciente.id; + return (this.desplegado && igualPaciente); + } + /** + * Se visualiza el botón para ver las relaciones de un paciente, + * sólo en aquellas relaciones que son hijo/a, tutelado menores de 11 años y que sean pacientes activos + * @param paciente un item del listado + */ + public showBtnRelaciones(paciente: IPaciente) { + this.listadoRelaciones = []; + if (this.showRelaciones && paciente.relaciones?.length) { + const limiteEdad = 11; + const relaciones = ['progenitor/a', 'tutor']; + this.listadoRelaciones = paciente.relaciones.filter(rela => { + const relacionesTutor = rela?.relacion?.opuesto && relaciones.includes(rela.relacion.opuesto); + const cumpleEdad = rela?.fechaNacimiento && calcularEdad(rela.fechaNacimiento, 'y') < limiteEdad; + return (relacionesTutor && rela.activo && rela.fechaNacimiento && cumpleEdad); + }); + } + return this.listadoRelaciones.length; + } + + public verRelaciones(paciente: IPaciente) { + this.seletedOn = false; + if (this.desplegado) { + this.desplegado = false; + this.pacienteSeleccionado = null; + } else { + if (paciente.id && paciente.relaciones?.length) { + this.relaciones.emit(paciente.relaciones); + this.desplegado = true; + this.pacienteSeleccionado = paciente; + + } else { + this.relaciones.emit(null); + } + } + } + + /** + * retorna true/false al querer mostrar el documento del tutor de un paciente menor de 5 años + * @param paciente + */ + public showDatosTutor(paciente: IPaciente) { + // si es un paciente sin documento menor a 5 años mostramos datos de un familiar/tutor + const edad = 5; + const rel = paciente.relaciones; + return !paciente.documento && !paciente.numeroIdentificacion && paciente.edad < edad && rel !== null && rel.length; + } + /** + * + * @param pos posición en el listado + * @returns color del item + */ + public colorItem(pos) { + return (pos % 2 === 0) ? this.coloresItems.par : this.coloresItems.impar; + } +} diff --git a/src/app/restriccion-huds/paciente-listado.html b/src/app/restriccion-huds/paciente-listado.html new file mode 100644 index 0000000..51faf25 --- /dev/null +++ b/src/app/restriccion-huds/paciente-listado.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + Sin DNI + + + {{ paciente.genero }} + + + + {{ paciente.estado || 'S/D' }} + + + Fallecido: + {{ paciente.fechaFallecimiento | fecha}} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/restriccion-huds/paciente-listado.scss b/src/app/restriccion-huds/paciente-listado.scss new file mode 100644 index 0000000..0a53134 --- /dev/null +++ b/src/app/restriccion-huds/paciente-listado.scss @@ -0,0 +1,7 @@ +img { + height: 60px; +} + +.img-fallecido { + filter: grayscale(100%); +} \ No newline at end of file diff --git a/src/app/restriccion-huds/restriccion-huds.html b/src/app/restriccion-huds/restriccion-huds.html new file mode 100644 index 0000000..b240d0c --- /dev/null +++ b/src/app/restriccion-huds/restriccion-huds.html @@ -0,0 +1,202 @@ + + + + + + + + +
+ No hay usuarios que coincidan con los filtros de + búsqueda +
+ + + + + + {{user.usuario}} + + {{user.apellido}} + + + {{user.nombre}} + + + SIN DATOS + + + + + + + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + Matriculado + No + Matriculado + + + + + + + + + + + + + + + + + + + + + + + + {{ paciente.genero }} + + + {{ paciente.estado || 'S/D' }} + + Fallecido: + {{ paciente.fechaFallecimiento | fecha}} + + + + + + + + + + + + + + + + + + + +
+ No se encontró ningun paciente.. +
+
+ + + + + + + {{ pacienteSelected.genero }} + + + {{ pacienteSelected.estado || 'S/D' }} + + Fallecido: + {{ pacienteSelected.fechaFallecimiento | fecha}} + + + + + + + + + + + + + + + + + + Archivo inválido + + + + + + + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/app/restriccion-huds/restriccion-huds.ts b/src/app/restriccion-huds/restriccion-huds.ts new file mode 100644 index 0000000..224057e --- /dev/null +++ b/src/app/restriccion-huds/restriccion-huds.ts @@ -0,0 +1,421 @@ +import { Component, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core'; +import { Plex } from '@andes/plex'; +import { Router, ActivatedRoute } from '@angular/router'; +import { merge, BehaviorSubject, of } from 'rxjs'; +import { switchMap, tap, distinctUntilChanged, debounceTime, map } from 'rxjs/operators'; +import { PermisosService } from '../services/permisos.service'; +import { UsuariosHttp } from '../services/usuarios.service'; +import { asObject, mergeObject, cache, Unsubscribe } from '@andes/shared'; +import { Auth } from '@andes/auth'; +import { IPaciente, IPacienteRestringido } from '../interfaces/IPaciente'; +import { PacienteCacheService } from '../services/pacienteCache.service'; +import { PacienteService } from '../services/paciente.service'; +import { AdjuntosService } from '../services/adjuntos.service'; +import { ProfesionalService } from '../services/profesional.service'; +import { DomSanitizer } from '@angular/platform-browser'; +import * as moment from 'moment'; +import { PlexVisualizadorService } from '@andes/plex'; + +@Component({ + selector: 'restriccion-huds', + templateUrl: 'restriccion-huds.html', +}) + +export class restriccionHudsComponent implements OnInit { + + @ViewChild('upload', { static: false }) uploadElement: ElementRef; + + constructor( + public permisosService: PermisosService, + public usuariosService: UsuariosHttp, + public plex: Plex, + private router: Router, + private route: ActivatedRoute, + private auth: Auth, + private cd: ChangeDetectorRef, + private pacienteCache: PacienteCacheService, + public pacienteService: PacienteService, + public adjuntosService: AdjuntosService, + public profesionalService: ProfesionalService, + public sanitizer: DomSanitizer, + private plexVisualizador: PlexVisualizadorService + ) { + } + + public verPerfiles = this.auth.check('usuarios:perfiles') || this.auth.check('global:usuarios:perfiles'); + public readOnly = !this.auth.check('usuarios:write'); + + refresh = new BehaviorSubject({}); + refresh$ = this.refresh.asObservable(); + + private _search = new BehaviorSubject(null); + private search$ = this._search.asObservable().pipe( + debounceTime(300), + distinctUntilChanged() + ); + + get search() { + return this._search.getValue(); + } + + set search(value) { + this._search.next(value); + } + + public pacienteRestringido: IPacienteRestringido[] = []; + public restringidos: IPaciente[] = []; + public usuarios$; + public loading = false; + public resultadoBusqueda = null; + public showBuscarPaciente = false; + public showEditarPaciente = false; + public agregarPaciente = false; + public userData$; + public userSelected: any; + public pacienteSelected: any; + public observaciones: string; + public errorExt = false; + public documento = { + archivos: [], + tipo: null, + fecha: null + }; + public archivos = []; + public files = []; + private filesAdd = []; + private filesDel = []; + private invalid = true; + private columns = [ + { key: 'usuario', label: 'Usuario', sorteable: false }, + { key: 'apellido', label: 'Apellido', sorteable: false }, + { key: 'nombre', label: 'Nombre/s', sorteable: false } + ]; + private profesional: any; + private foto: any; + private tieneFoto = false; + + indexEdit = -1; + extensions = ['pdf', 'doc', 'docx', 'bmp', 'jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'raw']; + IMAGENES_EXT = ['bmp', 'jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'raw']; + fileToken: string = null; + + ngOnInit() { + + this.usuarios$ = this.permisosService.organizaciones().pipe( + switchMap(() => { + this.loading = true; + return merge( + this.refresh$, + this.search$.pipe(asObject('search', t => t.length ? t : null)) + ); + }), + mergeObject(), + tap((params) => { + this.router.navigate([], { + relativeTo: this.route, + queryParams: params, + queryParamsHandling: 'merge', + replaceUrl: true + }); + }), + switchMap((query: any) => { + if (query.search?.length > 2) { + query = { ...query }; + query.search = '^' + query.search; + return this.usuariosService.find({ ...query, fields: '-password -permisosGlobales', limit: 50 }); + } + return of([]); + }), + map(response => { + this.userSelected = null; + this.loading = false; + return response; + }), + cache() + ); + + this.cd.detectChanges(); + + this.adjuntosService.generateToken().subscribe((data: any) => { + this.fileToken = data.token; + }); + } + + select(user) { + this.userSelected = user; + this.restringidos = []; + this.pacienteRestringido = []; + this.showEditarPaciente = false; + if (this.userSelected.pacienteRestringido) { + for (let i = 0; i < this.userSelected.pacienteRestringido.length; i++) { + this.pacienteRestringido.push(this.userSelected.pacienteRestringido[i]); + this.addPaciente(this.userSelected.pacienteRestringido[i].idPaciente); + } + } + const params = { documento: user.documento, habilitado: true }; + this.profesionalService.get(params).subscribe((profesional) => { + if (profesional) { + this.profesional = profesional[0]; + this.profesionalService.getFoto({ id: this.auth.profesional }).subscribe(resp => { + if (resp) { + this.foto = this.sanitizer.bypassSecurityTrustResourceUrl('data:image/jpeg;base64,' + resp); + this.tieneFoto = true; + } + }); + } + }); + } + + addPaciente(id) { + this.pacienteService.getById(id).subscribe( + paciente => { + this.restringidos.push(paciente); + } + ); + } + + selected(user) { + return this.userSelected ? this.userSelected === user : false; + } + + searchStart() { + this.loading = true; + } + + searchEnd(pacientes: IPaciente[], scan: string) { + this.loading = false; + const escaneado = scan?.length > 0; + this.pacienteCache.setScanCode(scan); + if (escaneado && pacientes.length === 1 && pacientes[0].id) { + this.onSelect(pacientes[0]); + } else if (escaneado && pacientes.length === 1 && (!pacientes[0].id || (pacientes[0].estado === 'temporal' && pacientes[0].scan))) { + this.pacienteCache.setPaciente(pacientes[0]); + this.pacienteCache.setScanCode(scan); + this.router.navigate(['/apps/mpi/paciente/con-dni/sobreturno']); // abre paciente-cru + } else { + this.resultadoBusqueda = pacientes; + } + } + + onSearchClear() { + this.resultadoBusqueda = null; + } + + @Unsubscribe() + onSelect(paciente: IPaciente) { + let select = true; + if (paciente && paciente.id) { + this.resultadoBusqueda = null; + this.pacienteService.checkFallecido(paciente); + for (const restr of this.restringidos) { + if (restr.id === paciente.id) { + select = false; + this.plex.info('warning', 'Paciente ya se encuentra en la lista de restringidos'); + } + } + if (select) { + this.pacienteSelected = paciente; + this.showBuscarPaciente = false; + this.agregarPaciente = true; + this.files = this.getArchivos(); + this.filesAdd = []; + this.filesDel = []; + } + } else { + this.plex.info('warning', 'Paciente no encontrado', '¡Error!'); + } + } + + guardar(paciente) { + const pteRestr: IPacienteRestringido = { + idPaciente: paciente.id, + observaciones: this.observaciones, + createdBy: { usuario: this.userSelected.usuario }, + createdAt: moment().toDate(), + archivos: this.archivos + }; + if (this.indexEdit > -1) { + this.pacienteRestringido[this.indexEdit] = pteRestr; + } else { + this.pacienteRestringido.push(pteRestr); + } + this.userSelected.pacienteRestringido = this.pacienteRestringido; + this.usuariosService.update(this.userSelected.usuario, this.userSelected).subscribe(() => { + if (this.indexEdit > -1) { + this.plex.toast('success', 'El paciente se editó correctamente'); + } else { + this.addPaciente(paciente.id); + this.plex.toast('success', 'El paciente se agregó correctamente'); + } + this.eliminarQuitados(); + this.cancelar(); + }); + } + + eliminarQuitados() { + if (this.filesDel) { + this.filesDel.forEach(archivo => { + this.adjuntosService.delete(archivo.id).subscribe((data: any) => { }); + }); + } + } + + eliminarAgregados() { + if (this.filesAdd) { + this.filesAdd.forEach(archivo => { + this.adjuntosService.delete(archivo.id).subscribe((data: any) => { }); + const i = this.archivos.findIndex(x => x.id === archivo.id); + this.archivos.splice(i); + }); + } + } + + checkValid() { + if (!this.documento.tipo) { + this.invalid = true; + return; + } + if (this.archivos.length === 0) { + this.invalid = true; + return; + } + this.invalid = false; + } + + editar(index) { + this.showEditarPaciente = true; + this.pacienteSelected = this.restringidos[index]; + this.indexEdit = this.pacienteRestringido.findIndex(obj => obj.idPaciente === this.pacienteSelected.id); + this.observaciones = this.pacienteRestringido[this.indexEdit].observaciones; + this.archivos = this.pacienteRestringido[this.indexEdit].archivos ? this.pacienteRestringido[this.indexEdit].archivos : []; + this.files = this.getArchivos(); + this.filesAdd = []; + this.filesDel = []; + } + + eliminar(index) { + this.plex.confirm('Elimina paciente de la restricción ?').then((resultado) => { + if (resultado) { + this.pacienteRestringido.splice(index, 1); + this.userSelected.pacienteRestringido = this.pacienteRestringido; + this.usuariosService.update(this.userSelected.usuario, this.userSelected).subscribe(() => { + this.filesDel = this.getArchivos(); + this.eliminarQuitados(); + this.showBuscarPaciente = false; + this.plex.toast('success', 'El paciente se eliminó correctamente'); + }); + this.restringidos.splice(index, 1); + } + }); + } + + buscarPaciente() { + this.showBuscarPaciente = !this.showBuscarPaciente; + this.pacienteSelected = null; + this.observaciones = null; + this.archivos = []; + } + + cerrarSideBar() { + this.userSelected = null; + this.cancelar(); + } + + onCancelar() { + this.cancelar(); + this.eliminarAgregados(); + this.backFilesDel(); + } + + private cancelar() { + this.resultadoBusqueda = null; + this.showBuscarPaciente = false; + this.showEditarPaciente = false; + this.agregarPaciente = false; + this.pacienteSelected = null; + this.indexEdit = -1; + } + + backFilesDel() { + this.filesDel.forEach(archivo => { + this.archivos.push(archivo); + }); + }; + + changeListener($event): void { + this.readThis($event.target); + } + + readThis(inputValue: any): void { + const ext = this.fileExtension(inputValue.value); + this.errorExt = false; + if (!this.extensions.find((item) => item === ext.toLowerCase())) { + this.uploadElement.nativeElement.value = ''; + this.errorExt = true; + this.plex.toast('danger', 'Tipo de archivo incorrecto'); + return; + } + const file: File = inputValue.files[0]; + const myReader: FileReader = new FileReader(); + myReader.onloadend = (e) => { + this.uploadElement.nativeElement.value = ''; + const metadata = {}; + this.adjuntosService.upload(myReader.result, metadata).subscribe((data) => { + this.archivos.push({ + ext, + id: data._id + }); + this.filesAdd.push(this.archivos[this.archivos.length - 1]); + this.files = this.getArchivos(); + }); + }; + myReader.readAsDataURL(file); + } + + fileExtension(file) { + if (file.lastIndexOf('.') >= 0) { + return file.slice((file.lastIndexOf('.') + 1)); + } else { + return ''; + } + } + + getArchivos() { + if (this.archivos) { + return this.archivos.map((doc: any) => { + doc = { ...doc }; + doc.url = this.createUrl(doc); + doc.isImage = this.esImagen(doc.ext); + return doc; + }); + } else { + return []; + } + } + + createUrl(doc) { + if (doc.id) { + return this.adjuntosService.getUrlArchivo(doc.id, this.fileToken); + } + } + + removeArchivo(archivo) { + const index = this.files.findIndex(a => a.id === archivo.id); + this.filesDel.push(this.files[index]); + this.files.splice(index, 1); + this.archivos.splice(index, 1); + } + + esImagen(extension: string) { + return !!this.IMAGENES_EXT.find(x => x === extension.toLowerCase()); + } + + openUrl(archivo) { + window.open(archivo.url); + } + + open(index: number) { + this.plexVisualizador.open(this.files, index); + } + +} diff --git a/src/app/services/adjuntos.service.ts b/src/app/services/adjuntos.service.ts new file mode 100644 index 0000000..5d704c6 --- /dev/null +++ b/src/app/services/adjuntos.service.ts @@ -0,0 +1,39 @@ +import { Injectable, Input } from '@angular/core'; +import { Auth } from '@andes/auth'; +import { Server } from '@andes/shared'; +import { environment } from 'src/environments/environment'; + +@Injectable() +export class AdjuntosService { + + @Input() url = '/modules/restriccion-huds'; + + private apiUri = environment.API; + + constructor(private server: Server, public auth: Auth) { } + + /* + * @param params.id id devuelto por el metodo post. + * @param params.estado estado para filtrar. + */ + + get(params) { + return this.server.get(this.url, { params }); + } + + delete(id) { + return this.server.delete(this.url + '/store/' + id); + } + + upload(file, metadata) { + return this.server.post(this.url + '/store', { file, metadata }); + } + + generateToken() { + return this.server.post('/auth/file-token', {}); + } + + getUrlArchivo(id, fileToken) { + return this.apiUri + this.url + '/store/' + id + '?token=' + fileToken; + } +} diff --git a/src/app/services/paciente-buscar.service.ts b/src/app/services/paciente-buscar.service.ts new file mode 100644 index 0000000..6eb4562 --- /dev/null +++ b/src/app/services/paciente-buscar.service.ts @@ -0,0 +1,243 @@ +import { Injectable } from '@angular/core'; +import { map, mergeMap } from 'rxjs/operators'; +import { of, EMPTY } from 'rxjs'; +import { PacienteService } from './paciente.service'; +import * as moment from 'moment'; + +export interface PacienteEscaneado { + documento: string; + apellido: string; + nombre: string; + sexo: string; + fechaNacimiento: Date; + scan: string; +} + +@Injectable() +export class PacienteBuscarService { + private searchText; + private skip = 0; + private limit = 10; + private scrollEnd = false; + constructor( + private pacienteService: PacienteService) { } + + /** +* Controla si se ingresó el caracter " en la primera parte del string, indicando que el scanner no está bien configurado +* +* @public +* @returns {boolean} Indica si está bien configurado +*/ + public controlarScanner(scan): boolean { + if (scan) { + const index = scan.indexOf('"'); + if (index >= 0 && index < 20 && scan.length > 5) { + /* Agregamos el control que la longitud sea mayor a 5 para incrementar la tolerancia de comillas en el input */ + return false; + } + } + return true; + } + + + /** + * Controla que el texto ingresado corresponda a un documento válido, controlando todas las expresiones regulares + * + * @returns {DocumentoEscaneado} Devuelve el documento encontrado + */ + public comprobarDocumentoEscaneado(textoLibre: string): PacienteEscaneado { + for (const key in DocumentoEscaneados) { + if (DocumentoEscaneados[key].regEx.test(textoLibre)) { + // Loggea el documento escaneado para análisis + return this.parseDocumentoEscaneado(textoLibre, DocumentoEscaneados[key]); + } + } + return null; + } + + /** + * Parsea el texto libre en un objeto paciente + * + * @param {DocumentoEscaneado} documento documento escaneado + * @returns {*} Datos del paciente + */ + public parseDocumentoEscaneado(textoLibre: string, documento: DocumentoEscaneado): PacienteEscaneado { + const datos = textoLibre.match(documento.regEx); + let sexo = ''; + if (documento.grupoSexo > 0) { + sexo = (datos[documento.grupoSexo].toUpperCase() === 'F') ? 'femenino' : 'masculino'; + } + + let fechaNacimiento = null; + if (documento.grupoFechaNacimiento > 0) { + fechaNacimiento = moment(datos[documento.grupoFechaNacimiento], 'DD/MM/YYYY').format('YYYY-MM-DD').toString(); + } + + return { + documento: datos[documento.grupoNumeroDocumento].replace(/\D/g, ''), + apellido: datos[documento.grupoApellido], + nombre: datos[documento.grupoNombre], + sexo: sexo, + fechaNacimiento: fechaNacimiento, + scan: textoLibre + }; + } + + + public findByScan(pacienteEscaneado: PacienteEscaneado) { + const textoLibre = pacienteEscaneado.scan; + // 1. Busca por documento escaneado (simplequery) + return this.pacienteService.get({ + apellido: pacienteEscaneado.apellido, + nombre: pacienteEscaneado.nombre, + documento: pacienteEscaneado.documento, + sexo: pacienteEscaneado.sexo, + activo: true + }).pipe( + map(resultado => { + return resultado.length ? { scan: textoLibre, pacientes: resultado, err: null } : null; + }), + mergeMap((resultado: any) => { + // 1.2. Si encuentra el paciente (un matcheo al 100%) finaliza la búsqueda + if (resultado) { + return of(resultado); + } + + // 1.3. Si no encontró el paciente escaneado, busca uno similar (suggest) + return this.pacienteService.match({ + apellido: pacienteEscaneado.apellido, + nombre: pacienteEscaneado.nombre, + documento: pacienteEscaneado.documento, + sexo: pacienteEscaneado.sexo, + fechaNacimiento: pacienteEscaneado.fechaNacimiento + }).pipe( + map((resultadoSuggest: any) => { + // 1.3.1. Si no encontró ninguno, retorna un array vacio + if (!resultadoSuggest.length) { + return { pacientes: [], err: null }; + } + // 1.3.2. Busca a uno con el mismo código de barras + const candidato = resultadoSuggest.find(elto => elto.paciente.scan && elto.paciente.scan === textoLibre); + if (candidato) { + return { scan: textoLibre, pacientes: [candidato], err: null }; + } else { + // 1.3.3. Busca uno con un porcentaje alto de matcheo + if (resultadoSuggest[0]._score >= 0.94) { + if (resultadoSuggest[0].estado === 'validado') { + return { pacientes: [resultadoSuggest[0]], err: null }; + } else { + // Si es un paciente temporal, actualizamos con los datos del DNI escaneado + const pacienteActualizado: any = { ...resultadoSuggest[0] }; + return { pacientes: [pacienteActualizado], err: null }; + } + } else { + // Si el matcheo es bajo, retorna un array vacio (No se encontró el paciente) + return { pacientes: [], err: null }; + } + } + }) + ); + }) + ); + } + + /** + * Busca paciente cada vez que el campo de busqueda cambia su valor + */ + public search(searchText: string, returnScannedPatient = false) { + // Inicia búsqueda + if (searchText) { + this.searchText = searchText; + this.skip = 0; + this.scrollEnd = false; + // Si matchea una expresión regular, busca inmediatamente el paciente + const pacienteEscaneado = this.comprobarDocumentoEscaneado(searchText); + if (pacienteEscaneado) { + return this.findByScan(pacienteEscaneado).pipe( + map(resultadoPacientes => { + if (resultadoPacientes.pacientes.length) { + return resultadoPacientes; + } else { + // Si el paciente no fue encontrado .. + if (returnScannedPatient) { + // Ingresa a registro de pacientes ya que es escaneado + return { pacientes: [pacienteEscaneado], escaneado: true, scan: searchText, err: null }; + } else { + return { pacientes: [], err: null }; + } + } + }) + ); + } else { + // Busca por texto libre + return this.findByText(); + } + } + } + + /** + * Busca paciente cada vez que el campo de busqueda cambia su valor + */ + public findByText() { + if (this.scrollEnd) { + return EMPTY; + } + // Busca por texto libre + return this.pacienteService.get({ search: this.searchText, activo: true, limit: this.limit, skip: this.skip }).pipe( + map((resultado: any) => { + this.skip += resultado.length; + // si vienen menos resultado que {{ limit }} significa que ya se cargaron todos + if (!resultado.length || resultado.length < this.limit) { + this.scrollEnd = true; + } + return { pacientes: resultado, err: null }; + }, err => { + return { pacientes: [], err: err }; + }), + ); + } +} + +export interface DocumentoEscaneado { + regEx: RegExp; + grupoNumeroDocumento: number; + grupoApellido: number; + grupoNombre: number; + grupoSexo: number; + grupoFechaNacimiento: number; +} + +export const DocumentoEscaneados: DocumentoEscaneado[] = [ + // DNI Argentino primera versión + // Formato: @14157955 @A@1@GUTIERREZ@ROBERTO DANIEL@ARGENTINA@31/05/1960@M@01/11/2011@00079064950@7055 @01/11/2026@421@0@ILR:2.20 C:110927.01 (GM/EXE-MOVE-HM)@UNIDAD #02 || S/N: 0040>2008>>0006 + { + regEx: /@([MF]*[A-Z0-9]+)\s*@[A-Z]+@[0-9]+@([a-zA-ZñÑáéíóúÁÉÍÓÚÜü'\-\s]+)@([a-zA-ZñÑáéíóúÁÉÍÓÚÜü'\-\s]+)@[A-Z]+@([0-9]{2}\/[0-9]{2}\/[0-9]{4})@([MF])@/i, + grupoNumeroDocumento: 1, + grupoApellido: 2, + grupoNombre: 3, + grupoSexo: 5, + grupoFechaNacimiento: 4 + }, + // DNI Argentino segunda y tercera versión + // Formato: 00327345190@GARCIA@JUAN FRANCISCO@M@23680640@A@25/08/1979@06/01/2015@209 + // Formato: 00125559991@PENA SAN JUAN@ORLANDA YUDITH@F@28765457@A@17/01/1944@28/12/2012 + { + regEx: /[0-9]+@([a-zA-ZñÑáéíóúÁÉÍÓÚÜü'\-\s]+)@([a-zA-ZñÑáéíóúÁÉÍÓÚÜü'\-\s]+)@([MF])@([MF]*[0-9]+)@[A-Z]@([0-9]{2}\/[0-9]{2}\/[0-9]{4})(.*)/i, + grupoNumeroDocumento: 4, + grupoApellido: 1, + grupoNombre: 2, + grupoSexo: 3, + grupoFechaNacimiento: 5 + }, + + // QR ACTA DE NACIMIENTO + // Formato: INOSTROZA, Ramiro Daniel DNI: 54852844Tomo: 5Folio: 88Acta: 507Año: 2015Formato de archivo de imágen no reconocido + { + regEx: /([a-zA-ZñÑáéíóúÁÉÍÓÚÜü'\-\s]+),([a-zA-ZñÑáéíóúÁÉÍÓÚÜü'\-\s]+)([DNI: ]{5})([0-9]+)(.*)/i, + grupoNumeroDocumento: 4, + grupoApellido: 1, + grupoNombre: 2, + grupoSexo: 0, + grupoFechaNacimiento: 0 + } +]; diff --git a/src/app/services/paciente.service.ts b/src/app/services/paciente.service.ts new file mode 100644 index 0000000..07cfe6a --- /dev/null +++ b/src/app/services/paciente.service.ts @@ -0,0 +1,136 @@ +import { Plex } from '@andes/plex'; +import { Server } from '@andes/shared'; +import { Injectable } from '@angular/core'; +import { combineLatest, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { IPacienteMatch } from '../interfaces/IPacienteMatch.inteface'; +import { IPaciente } from '../interfaces/IPaciente'; +import * as moment from 'moment'; + +@Injectable() +export class PacienteService { + private pacienteV2 = '/core-v2/mpi/pacientes'; + /** + * RegEx para validar nombres y apellidos. + */ + public nombreRegEx = /^[a-zA-ZàáèéìíòóùúüñÀÁÈÉÌÍÒÓÙÚÑ']+( [a-zA-ZZàáèéìíòóùúüñÀÁÈÉÌÍÒÓÙÚÑ']+)*$/; + + constructor( + private server: Server, + private plex: Plex + ) { } + + /** + * Metodo getById. Trae un objeto paciente por su Id. + * @param {String} id Busca por Id + */ + getById(id: String, options?: any): Observable { + return this.server.get(`${this.pacienteV2}/${id}`, options); + } + + /** + * TEMPORAL. Resuelve el bug de la API de pacientes, unificando la interface que devuelven los diferentes tipos + * Una vez solucionado el bug de la API, eliminar este método y reemplazarlo por get() + * @param {PacienteSearch} params + * @returns {Observable} + * @memberof PacienteService + */ + get(params: any): Observable { + return this.server.get(this.pacienteV2, { params }).pipe(map((value) => { + if (params.type === 'simplequery') { + return value.map((i) => ({ paciente: i, id: i.id, match: 100 })); + } else { + return value; + } + })); + } + + match(params: any): Observable { + return this.server.post(`${this.pacienteV2}/match`, params); + } + + /** + * Metodo post. Inserta un objeto paciente nuevo. + * @param {IPaciente} paciente Recibe IPaciente + */ + + post(paciente: IPaciente, options?: any): Observable { + return this.server.post(this.pacienteV2, paciente, options); + } + + /** + * Metodo patch. Modifica solo algunos campos del paciente. + * @param {any} cambios Recibe any + */ + patch(id: String, cambios: any): Observable { + return this.server.patch(`${this.pacienteV2}/${id}`, cambios); + } + + save(paciente: IPaciente, ignoreCheck: boolean = false): Observable { + if (paciente.id) { + return this.patch(paciente.id, paciente); + } else { + return this.post(paciente, ignoreCheck); + } + } + + // Arroja una alerta invasiva cuando un paciente dado se encuentra fallecido + checkFallecido(paciente: IPaciente) { + if (paciente.fechaFallecimiento) { + const fecha = moment(paciente.fechaFallecimiento).format('DD/MM/YYYY'); + this.plex.info('warning', `${paciente.nombreCompleto} se encuentra registrado como paciente fallecido el ${fecha}. De continuar, esta acción quedará registrada en la Historia de Salud del paciente.`, 'Paciente fallecido'); + } + } + + + // ############################ AUDITORIA ################################# + + /** + * Metodo setActivo: Actualiza dato activo (true/false) de un paciente + * @param {IPaciente} paciente + * @param {boolean} activo + */ + setActivo(paciente: IPaciente, activo: boolean) { + return this.server.patch(`${this.pacienteV2}/${paciente.id}`, { activo }); + } + + /** + * Se vinculan dos pacientes: modifica el array de identificadores del paciente. + * @param pacienteBase paciente que va a contener el vinculo de otro paciente + * @param pacienteLink paciente a ser vinculado + */ + linkPatient(pacienteBase: IPaciente, pacienteLink: IPaciente): Observable { + if (pacienteBase && pacienteBase.id && pacienteLink && pacienteLink.id) { + const dataLink = { + entidad: 'ANDES', + valor: pacienteLink.id + }; + if (pacienteBase.identificadores) { + pacienteBase.identificadores.push(dataLink); + } else { + pacienteBase.identificadores = [dataLink]; + } + pacienteLink.activo = false; + pacienteLink.idPacientePrincipal = pacienteBase.id; + return combineLatest([this.patch(pacienteBase.id, pacienteBase), this.patch(pacienteLink.id, pacienteLink)]); + } + return; + } + + /** + * Se desvinculan dos pacientes: modifica el array de identificadores del paciente. + * @param pacienteBase paciente que va a contener el vinculo de otro paciente + * @param pacienteLink paciente a ser desvinculado + */ + unlinkPatient(pacienteBase: any, pacienteLink: IPaciente) { + if (pacienteBase && pacienteBase.id && pacienteLink && pacienteLink.id) { + if (pacienteBase.identificadores) { + pacienteBase.identificadores = (pacienteBase.identificadores.filter((x) => x.valor !== pacienteLink.id)); + } + pacienteLink.idPacientePrincipal = null; + pacienteLink.activo = true; + return combineLatest([this.patch(pacienteBase.id, pacienteBase), this.patch(pacienteLink.id, pacienteLink)]); + } + return; + } +} diff --git a/src/app/services/pacienteCache.service.ts b/src/app/services/pacienteCache.service.ts new file mode 100644 index 0000000..1c0f1ba --- /dev/null +++ b/src/app/services/pacienteCache.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { Observable } from 'rxjs'; +import { IPaciente } from '../interfaces/IPaciente'; + +@Injectable() +export class PacienteCacheService { + + private pacienteCache = new BehaviorSubject(null); + private isScannedCache = new BehaviorSubject(null); + + setPaciente(paciente: IPaciente) { + this.pacienteCache.next(paciente); + } + + getPacienteValor(): IPaciente { + return this.pacienteCache.value; + } + + getPaciente(): Observable { + return this.pacienteCache.asObservable(); + } + + clearPaciente() { + this.pacienteCache.next(null); + } + + setScanCode(scan: string) { + this.isScannedCache.next(scan); + } + + getScan(): string { + return this.isScannedCache.value; + } + + clearScanState() { + this.isScannedCache.next(null); + } +} diff --git a/src/app/services/permisos.service.ts b/src/app/services/permisos.service.ts new file mode 100644 index 0000000..defebcc --- /dev/null +++ b/src/app/services/permisos.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Server, Cache, cache } from '@andes/shared'; // cacheStorage + +@Injectable() +export class PermisosService { + + private permisosUrl = '/core/tm/permisos'; // URL to web api + + constructor(private server: Server) { } + + @Cache({ key: null }) + get(params?): Observable { + return this.server.get(this.permisosUrl, { params }); + } + + // organizaciones(): Observable { + // return this.server.get('/modules/gestor-usuarios/organizaciones', {}).pipe( + // cacheStorage({ key: 'organizaciones-permisos' }) + // ); + // } + + organizaciones(): Observable { + return this.server.get('/modules/gestor-usuarios/organizaciones', {}).pipe( + cache() + ); + } + + copyPermisos = null; + copy(permisos: string[]) { + this.copyPermisos = permisos; + } + + paste() { + const cp = this.copyPermisos; + this.copyPermisos = null; + return cp; + } + + hasCopy() { + return this.copyPermisos !== null; + } +} diff --git a/src/app/services/profesional.service.ts b/src/app/services/profesional.service.ts new file mode 100644 index 0000000..4a28dd9 --- /dev/null +++ b/src/app/services/profesional.service.ts @@ -0,0 +1,119 @@ +import { BehaviorSubject, combineLatest, EMPTY, Observable } from 'rxjs'; +import { IProfesional } from './../interfaces/IProfesional'; +import { Injectable } from '@angular/core'; +import { Server } from '@andes/shared'; +// import { Options } from 'projects/shared/src/lib/server/options'; +import { auditTime, map, switchMap } from 'rxjs/operators'; + +@Injectable() +export class ProfesionalService { + + private profesionalUrl = '/core/tm/profesionales'; // URL to web api + public profesionalesFiltrados$: Observable; + public documento = new BehaviorSubject(null); + public apellido = new BehaviorSubject(null); + public nombre = new BehaviorSubject(null); + public activo = new BehaviorSubject(null); + public lastResults = new BehaviorSubject(null); + public noMatriculado = new BehaviorSubject(null); + private limit = 20; + private skip; + + constructor(private server: Server) { + + this.profesionalesFiltrados$ = combineLatest( + this.documento, + this.apellido, + this.nombre, + this.activo, + this.noMatriculado, + this.lastResults + ).pipe( + auditTime(0), + switchMap(([documento, apellido, nombre, activo, noMatriculado, lastResults]) => { + + + if (!lastResults) { + this.skip = 0; + } + if (this.skip > 0 && this.skip % this.limit !== 0) { + // si skip > 0 pero no es multiplo de 'limit' significa que no hay mas resultados + return EMPTY; + } + const params: any = { + limit: this.limit, + skip: this.skip + }; + if (documento) { + params.documento = documento; + } + if (apellido) { + params.apellido = apellido; + } + + if (nombre) { + params.nombre = nombre; + } + if (activo) { + params.habilitado = activo; + } + if (noMatriculado) { + params.profesionalMatriculado = noMatriculado; + } + + return this.get(params).pipe( + map(resultados => { + const listado = lastResults ? lastResults.concat(resultados) : resultados; + this.skip = listado.length; + return listado; + }) + ); + }) + + ); + } + + /** + * Metodo get. Devuelve profesionales + * @param {any} params Opciones de búsqueda + */ + get(params: any): Observable { + return this.server.get(this.profesionalUrl, { params: params, showError: true }); + } + + getByID(id: String): Observable { + return this.server.get(`${this.profesionalUrl}/${id}`); + } + + getFirma(params: any): Observable { + return this.server.get(this.profesionalUrl + '/firma', { params: params }); + } + + getFoto(params: any): Observable { + return this.server.get(this.profesionalUrl + '/foto/', { params: params }); + } + + /** + * Metodo post. Inserta un nuevo profesional + * @param {IProfesional} profesional + */ + // post(profesional: IProfesional): Observable { + // return this.server.post(this.profesionalUrl, profesional); // ...using post request + // } + + // saveFirma(firma) { + // return this.post(firma); + // } + + // saveProfesional(profesionalModel: any) { + // return profesionalModel.id ? this.server.patch(`${this.profesionalUrl}/${profesionalModel.id}`, profesionalModel) : this.server.post(this.profesionalUrl, { profesional: profesionalModel }); + // } + + // validarProfesional(body): Observable { + // return this.server.post(this.profesionalUrl + '/validar', body); + // } + + // actualizarProfesional(body, options?: Options): Observable { + // return this.server.put(this.profesionalUrl + '/actualizar', body, options); + // } +} diff --git a/src/app/services/usuarios.service.ts b/src/app/services/usuarios.service.ts new file mode 100644 index 0000000..3464bd7 --- /dev/null +++ b/src/app/services/usuarios.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Server, cache } from '@andes/shared'; +import { Auth } from '@andes/auth'; +import { throwError } from 'rxjs'; + +@Injectable() +export class UsuariosHttp { + private url = '/modules/gestor-usuarios/usuarios'; // URL to web api + + constructor( + private server: Server, + private auth: Auth + ) { } + + private me$: Observable; + me(): Observable { + if (this.auth.usuario) { + if (this.me$) { + return this.me$; + } + this.me$ = this.get(this.auth.usuario.documento, '-organizaciones').pipe( + cache() // Desde donde se llame, solo se va a ejecutar una vez. + ); + return this.me$; + } else { + return throwError(new Error('not_loggin')); + } + } + + get(id, fields = null): Observable { + return this.server.get(`${this.url}/${id}`, { params: { fields } }); + } + + find(query = {}): Observable { + return this.server.get(this.url, { params: query }); + } + + create(body): Observable { + return this.server.post(this.url, body); + } + + update(id, body): Observable { + return this.server.patch(`${this.url}/${id}`, body); + } + + delete(id): Observable { + return this.server.delete(`${this.url}/${id}`); + } + + ldap(id: String): Observable { + return this.server.get(`${this.url}/ldap/${id}`); + } + + addOrganizacion(id, organizacion, body) { + return this.server.post(`${this.url}/${id}/organizaciones/${organizacion}`, body); + } + + updateOrganizacion(id, organizacion, body): Observable { + return this.server.patch(`${this.url}/${id}/organizaciones/${organizacion}`, body); + } + + deleteOrganizacion(id, organizacion): Observable { + return this.server.delete(`${this.url}/${id}/organizaciones/${organizacion}`); + } + +} diff --git a/src/app/shared/enumerados.ts b/src/app/shared/enumerados.ts new file mode 100644 index 0000000..2d5f2fb --- /dev/null +++ b/src/app/shared/enumerados.ts @@ -0,0 +1,330 @@ +/** + * TODO: Document THIS + */ + +export enum Sexo { + 'femenino', + 'masculino', + 'otro' +} + +export enum Genero { + 'femenino', + 'masculino', + 'otro' +} + +export enum EstadoCivil { + 'casado', + 'separado', + 'divorciado', + 'viudo', + 'soltero', + 'otro' +} + +export enum tipoComunicacion { + 'Teléfono Fijo', + 'Teléfono Celular', + 'Email' +} + +export enum estados { + 'temporal', + 'identificado', + 'validado', + 'recienNacido', + 'extranjero' +} + +export enum relacionTutor { + 'padre', + 'madre', + 'hijo', + 'tutor' +} + +export enum UnidadEdad { + 'años', + 'meses', + 'días', + 'horas' +} + +export enum EstadosAuditorias { + 'pendiente', + 'aprobada', + 'desaprobada' +} + +export enum PrioridadesPrestacion { + 'no prioritario', + 'urgencia', + 'emergencia' +} + +export enum EstadosEspacios { + 'disponible', + 'mantenimiento', + 'clausurado', + 'baja permanente' +} + +export enum TipoIdentificacion { + 'pasaporte', + 'dni extranjero' +} + +export enum Censo { + 'Censable', + 'No Censable' +} + +export enum estadosInternacion { + 'ejecucion', + 'validada' +} + +export enum Meses { + 'enero', + 'febrero', + 'marzo', + 'abril', + 'mayo', + 'junio', + 'julio', + 'agosto', + 'septiembre', + 'octubre', + 'noviembre', + 'diciembre', +} + +export enum MotivosLiberacion { + 'Error', + 'Paciente canceló', + 'Paciente cambió turno', + 'No se pudo informar el turno', + 'Otro' +} + +export function titleCase(str) { + return str.toLowerCase().split(' ').map((word) => { + return (word.charAt(0).toUpperCase() + word.slice(1)); + }).join(' '); +} + +export function getObjeto(elemento) { + return { + 'id': elemento, + 'nombre': titleCase(elemento) + }; +} + +export function getSexo() { + let arrSexo = Object.keys(Sexo); + arrSexo = arrSexo.slice(arrSexo.length / 2); + return arrSexo; +} + +export function getObjSexos() { + let arrSexo = Object.keys(Sexo); + arrSexo = arrSexo.slice(arrSexo.length / 2); + const salida = arrSexo.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getTipoIdentificacion() { + let arrTipoId = Object.keys(TipoIdentificacion); + arrTipoId = arrTipoId.slice(arrTipoId.length / 2); + return arrTipoId; +} + +export function getObjTipoIdentificacion() { + let arrTipoId = Object.keys(TipoIdentificacion); + arrTipoId = arrTipoId.slice(arrTipoId.length / 2); + const salida = arrTipoId.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} +export function getObjCenso() { + let arrCenso = Object.keys(Censo); + arrCenso = arrCenso.slice(arrCenso.length / 2); + const salida = arrCenso.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getObjUnidadesEdad() { + let arrUnidadEdad = Object.keys(UnidadEdad); + arrUnidadEdad = arrUnidadEdad.slice(arrUnidadEdad.length / 2); + const salida = arrUnidadEdad.map(elem => { + return { + id: elem, + nombre: titleCase(elem) + }; + }); + return salida; +} + +export function getTipoComunicacion() { + let arrTC = Object.keys(tipoComunicacion); + arrTC = arrTC.slice(arrTC.length / 2); + return arrTC; +} + +export function getObjTipoComunicacion() { + let arrTC = Object.keys(tipoComunicacion); + arrTC = arrTC.slice(arrTC.length / 2); + const salida = arrTC.map(elem => { + const idEnumerado = elem.split(' ')[1] ? elem.split(' ')[1] : elem.split(' ')[0]; + return { + 'id': idEnumerado.toLowerCase(), + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getGenero() { + let arrGenero = Object.keys(Genero); + arrGenero = arrGenero.slice(arrGenero.length / 2); + return arrGenero; +} + +export function getObjGeneros() { + let arrGenero = Object.keys(Genero); + arrGenero = arrGenero.slice(arrGenero.length / 2); + const salida = arrGenero.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getEstadoCivil() { + let arrEstadoC = Object.keys(EstadoCivil); + arrEstadoC = arrEstadoC.slice(arrEstadoC.length / 2); + return arrEstadoC; +} + +export function getObjEstadoCivil() { + let arrEstadoC = Object.keys(EstadoCivil); + arrEstadoC = arrEstadoC.slice(arrEstadoC.length / 2); + const salida = arrEstadoC.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getEstados() { + let arrEstados = Object.keys(estados); + arrEstados = arrEstados.slice(arrEstados.length / 2); + return arrEstados; +} + +export function getPrioridades() { + let arrPrioridades = Object.keys(PrioridadesPrestacion); + arrPrioridades = arrPrioridades.slice(arrPrioridades.length / 2); + return arrPrioridades; +} + +export function getEstadosAuditorias() { + let arrEstados = Object.keys(EstadosAuditorias); + arrEstados = arrEstados.slice(arrEstados.length / 2); + const salida = arrEstados.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getEstadosEspacios() { + let arrEstados = Object.keys(EstadosEspacios); + arrEstados = arrEstados.slice(arrEstados.length / 2); + const salida = arrEstados.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getRelacionTutor() { + let arrRT = Object.keys(relacionTutor); + arrRT = arrRT.slice(arrRT.length / 2); + return arrRT; +} + +export function getObjRelacionTutor() { + let arrRT = Object.keys(relacionTutor); + arrRT = arrRT.slice(arrRT.length / 2); + const salida = arrRT.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getObjEstadoInternacion() { + let array = Object.keys(estadosInternacion); + array = array.slice(array.length / 2); + const salida = array.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getObjMeses() { + let arrMeses = Object.keys(Meses); + arrMeses = arrMeses.slice(arrMeses.length / 2); + const salida = arrMeses.map((elem, index) => { + return { + 'id': index + 1, + 'nombre': titleCase(elem) + }; + }); + return salida; +} + +export function getMotivosLiberacion() { + return getObj(MotivosLiberacion); +} + +function getObj(data) { + let array = Object.keys(data); + array = array.slice(array.length / 2); + const salida = array.map(elem => { + return { + 'id': elem, + 'nombre': titleCase(elem) + }; + }); + return salida; +} diff --git a/src/app/shared/galeria-archivos.component.html b/src/app/shared/galeria-archivos.component.html new file mode 100644 index 0000000..b11db3e --- /dev/null +++ b/src/app/shared/galeria-archivos.component.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/shared/galeria-archivos.component.ts b/src/app/shared/galeria-archivos.component.ts new file mode 100644 index 0000000..c528991 --- /dev/null +++ b/src/app/shared/galeria-archivos.component.ts @@ -0,0 +1,77 @@ +import { PlexVisualizadorService } from '@andes/plex'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +export interface FileObject { + id?: string; + nombre?: string; + url: string; + ext: string; + isImage?: boolean; +} + +export const IMAGENES_EXT = ['bmp', 'jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff', 'raw']; + +export const FILE_EXT = [ + // Documentos + 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'xml', 'html', 'txt', + // Audio/Video + 'mp3', 'mp4', 'm4a', 'mpeg', 'mpg', 'mov', 'flv', 'avi', 'mkv', + // Otros + 'dat' +]; + +@Component({ + selector: 'shared-galeria-archivos', + templateUrl: './galeria-archivos.component.html', +}) +export class GaleriaArchivosComponent { + private _files; + + get files(): FileObject[] { + return this._files; + } + + @Input() set files(value: FileObject[]) { + if (!value) { + this._files = []; + return; + } + this._files = value.map(file => { + return { + ...file, + isImage: this.esImagen(file.ext) + }; + }); + } + + @Input() loading = true; + + @Input() readonly = false; + + @Output() remove = new EventEmitter(); + + imagenes = IMAGENES_EXT; + + extensions = FILE_EXT; + + constructor( + private plexVisualizador: PlexVisualizadorService + ) { } + + onRemove(archivo: FileObject) { + this.remove.emit(archivo); + } + + esImagen(extension: string) { + return !!this.imagenes.find(x => x === extension.toLowerCase()); + } + + openUrl(archivo: FileObject) { + window.open(archivo.url); + } + + open(index: number) { + this.plexVisualizador.open(this.files, index); + } + +} diff --git a/src/app/shared/upload-file.component.ts b/src/app/shared/upload-file.component.ts new file mode 100644 index 0000000..2b22bf8 --- /dev/null +++ b/src/app/shared/upload-file.component.ts @@ -0,0 +1,123 @@ +import { environment } from '../../environments/environment'; +import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, OnInit } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpRequest, HttpEventType } from '@angular/common/http'; +import { Plex } from '@andes/plex'; + +export interface IProgress { + loaded: Number; + total: Number; +} + +export interface ICompleted { + status: Number; + body: Object | Array; +} + +@Component({ + selector: 'upload-file', + template: ` + + + `, +}) + +export class UploadFileComponent implements OnInit { + @Input() label = 'SUBIR'; + @Input() extensiones: string[] = null; + @Input() modulo: string = null; + + @Output() onProgress = new EventEmitter(); + @Output() onUpload = new EventEmitter(); + @ViewChild('upload', { static: true }) uploadElement: ElementRef; + + public disabled = false; + public currentFileUpload: File; + public progress = 0; + public extensionesAceptadas; + constructor( + private http: HttpClient, + private plex: Plex + ) { + + } + + ngOnInit() { + if (this.extensiones) { + this.extensionesAceptadas = this.extensiones.map(ext => '.' + ext); + } + + } + public get btnLabel() { + if (this.disabled) { + return this.progress + '%'; + } else { + return this.label; + } + } + + getExtension(file) { + if (file.lastIndexOf('.') >= 0) { + return file.slice((file.lastIndexOf('.') + 1)); + } else { + return ''; + } + } + + public onChange($event) { + $event.stopPropagation(); + this.disabled = true; + const selectedFile = $event.target.files; + this.currentFileUpload = selectedFile.item(0); + + if (this.extensiones) { + const ext = this.getExtension(this.currentFileUpload.name); + if (!this.extensiones.find(i => i === ext.toLowerCase())) { + this.disabled = false; + this.uploadElement.nativeElement.value = null; + this.plex.toast('danger', 'Tipo de archivo incorrecto'); + return; + } + + } + this.portFile(this.currentFileUpload).subscribe(event => { + if (event.type === HttpEventType.UploadProgress) { + const { loaded, total } = event; + this.onProgress.emit({ loaded, total }); + this.progress = Math.round(loaded / total * 100); + } + if (event.type === HttpEventType.Response) { + this.disabled = false; + this.uploadElement.nativeElement.value = null; + const status = event.status; + const body = JSON.parse(event.body as string); + body.ext = this.getExtension(this.currentFileUpload.name); + body.name = this.currentFileUpload.name; + if (status >= 200 && status < 300) { + this.onUpload.emit({ status, body }); + } + } + }, (error) => { + this.disabled = false; + }); + } + + portFile(file: File) { + const formdata: FormData = new FormData(); + formdata.append('file', file); + if (this.modulo) { + formdata.append('origen', this.modulo); + } + const headers: HttpHeaders = new HttpHeaders({ + 'Authorization': 'JWT ' + window.sessionStorage.getItem('jwt') + }); + + const req = new HttpRequest('POST', `${environment.API}/drive`, formdata, { + reportProgress: true, + responseType: 'text', + headers: headers + }); + return this.http.request(req); + } + + +} diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 5d5f7de..6545ab1 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,5 +1,20 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `angular-cli.json`. + +const _package = require('../../package.json'); export const environment = { production: false, - API: 'http://localhost:3002/api' + environmentName: 'development', + API: '//localhost:3002/api', + WS: '//localhost:3002', // para websocket + APIStatusCheck: false, + version: _package.version, + MAPS_KEY: '', + HOTJAR_KEY: '', + ANALYTICS_KEY: '', + PASSWORD_RECOVER: '', + SITE_KEY: '' };