diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 544936e..0a1ed18 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -16,7 +16,7 @@ const routes: Routes = [ // 'login' // 'register' - { path: '**', pathMatch: 'full', redirectTo: '/dashboard' }, + { path: '', pathMatch: 'full', redirectTo: '/dashboard' }, { path: '**', component: NopagefoundComponent }, ]; diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index 650f382..9a994ca 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -7,10 +7,11 @@ import { ChartsModule } from 'ng2-charts'; import { IncrementadorComponent } from './incrementador/incrementador.component'; import { DonutComponent } from './donut/donut.component'; +import { ModalImageComponent } from './modal-image/modal-image.component'; @NgModule({ - declarations: [IncrementadorComponent, DonutComponent], - exports: [IncrementadorComponent, DonutComponent], + declarations: [IncrementadorComponent, DonutComponent, ModalImageComponent], + exports: [IncrementadorComponent, DonutComponent, ModalImageComponent], imports: [CommonModule, FormsModule, ChartsModule], }) export class ComponentsModule {} diff --git a/src/app/components/modal-image/modal-image.component.html b/src/app/components/modal-image/modal-image.component.html new file mode 100644 index 0000000..d138b9f --- /dev/null +++ b/src/app/components/modal-image/modal-image.component.html @@ -0,0 +1,32 @@ +
+ + + +
\ No newline at end of file diff --git a/src/app/components/modal-image/modal-image.component.ts b/src/app/components/modal-image/modal-image.component.ts new file mode 100644 index 0000000..cdb2499 --- /dev/null +++ b/src/app/components/modal-image/modal-image.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core'; +import { FileUploadService } from 'src/app/services/file-upload.service'; +import { ModalImageService } from 'src/app/services/modal-image.service'; +import Swal from 'sweetalert2'; + +@Component({ + selector: 'app-modal-image', + templateUrl: './modal-image.component.html', + styles: [], +}) +export class ModalImageComponent implements OnInit { + imageToUpload: File; + imageUrl: string | ArrayBuffer; + + constructor( + public modalService: ModalImageService, + private fileService: FileUploadService + ) {} + + ngOnInit(): void {} + + closeModal() { + this.modalService.closeModal(); + } + + changeImage(file: File) { + this.imageToUpload = file; + + if (!file) return; + + const reader = new FileReader(); + const url64 = reader.readAsDataURL(file); + reader.onload = () => { + this.imageUrl = reader.result; + this.modalService.img = reader.result; + }; + // console.log(file); + } + + updateImage() { + const id = this.modalService.id; + const type = this.modalService.type; + this.fileService + .updateImage(this.imageToUpload, type, id) + .then((img) => { + Swal.fire('Guardado', 'Se cambió la imagen de perfíl', 'success'); + this.modalService.newImg.emit(img); + this.closeModal(); + }) + .catch((err) => { + Swal.fire('Error', 'No se pudo actulizar la imagen', 'error'); + }); + } +} diff --git a/src/app/interfaces/repsonse-interface.ts b/src/app/interfaces/repsonse-interface.ts index 26ed2a3..f024fd4 100644 --- a/src/app/interfaces/repsonse-interface.ts +++ b/src/app/interfaces/repsonse-interface.ts @@ -1 +1,32 @@ +import { Doctor } from '../models/doctor.model'; +import { Hospital } from '../models/hospital.model'; +import { User } from '../models/user.model'; + export interface updateUserResponse {} + +export interface getUsersResponse { + ok: boolean; + users: User[]; + uid: string; + total: number; +} + +export interface UserByTermResponse { + ok: boolean; + results: User[]; +} + +export interface HospByTermResponse { + ok: boolean; + results: Hospital[]; +} + +export interface DocByTermResponse { + ok: boolean; + results: Doctor[]; +} + +export interface searchByTermResponse { + ok: boolean; + results: []; +} diff --git a/src/app/models/doctor.model.ts b/src/app/models/doctor.model.ts new file mode 100644 index 0000000..4bee595 --- /dev/null +++ b/src/app/models/doctor.model.ts @@ -0,0 +1,3 @@ +export class Doctor { + constructor(public name: string) {} +} diff --git a/src/app/models/hospital.model.ts b/src/app/models/hospital.model.ts new file mode 100644 index 0000000..8ad4eb5 --- /dev/null +++ b/src/app/models/hospital.model.ts @@ -0,0 +1,3 @@ +export class Hospital { + constructor(public name: string) {} +} diff --git a/src/app/pages/maintenance/initialTemplate.txt b/src/app/pages/maintenance/initialTemplate.txt new file mode 100644 index 0000000..da9623f --- /dev/null +++ b/src/app/pages/maintenance/initialTemplate.txt @@ -0,0 +1,67 @@ +
+
+
+
+ + + +
+
+
+
+ + +
+
+
+

Cargando...

+ +

Espere un momento.

+
+
+
+ + +
+
+
+
+

Usuarios

+
Usuarios de la aplicación.
+
+ + + + + + + + + + + + + + + + + + + + + +
ImagenEmailNombreRolAutenticaciónAcciones
Lunar probe projectLunar probe projectLunar probe projectLunar probe projectLunar probe project + + +
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/app/pages/maintenance/users/users.component.html b/src/app/pages/maintenance/users/users.component.html new file mode 100644 index 0000000..bfdc314 --- /dev/null +++ b/src/app/pages/maintenance/users/users.component.html @@ -0,0 +1,92 @@ +
+
+
+
+ + + +
+
+
+
+ + +
+
+
+

Cargando...

+ +

Espere un momento.

+
+
+
+ + +
+
+
+
+

Usuarios

+
Se encontraron {{totalUsers}} usuarios de la aplicación.
+
+ + + + + + + + + + + + + + + + + + + + + + +
ImagenEmailNombreRolAutenticaciónAcciones
+
+ user +
+
{{user.email}}{{user.name}} + oogle + Email + + + + + + +
+
+
+ +

{{(page +1)}}/{{totalPages}}

+ +
+
+
+
+
\ No newline at end of file diff --git a/src/app/pages/maintenance/users/users.component.ts b/src/app/pages/maintenance/users/users.component.ts new file mode 100644 index 0000000..a1136e5 --- /dev/null +++ b/src/app/pages/maintenance/users/users.component.ts @@ -0,0 +1,148 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import Swal from 'sweetalert2'; + +import { + DocByTermResponse, + HospByTermResponse, + UserByTermResponse, +} from 'src/app/interfaces/repsonse-interface'; +import { User } from 'src/app/models/user.model'; + +import { SearchService } from 'src/app/services/search.service'; +import { UserService } from 'src/app/services/user.service'; +import { ModalImageService } from 'src/app/services/modal-image.service'; +import { delay } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-users', + templateUrl: './users.component.html', + styles: [], +}) +export class UsersComponent implements OnInit, OnDestroy { + users: User[] = []; + totalUsers = 0; + page = 0; + isLoading = false; + imgSubs: Subscription; + + constructor( + private searchService: SearchService, + private userService: UserService, + private modalService: ModalImageService + ) {} + + ngOnDestroy(): void { + this.imgSubs.unsubscribe(); + } + + ngOnInit(): void { + this.getUsers(); + this.imgSubs = this.modalService.newImg + .pipe(delay(200)) + .subscribe((img) => this.getUsers()); + } + + // TODO: Refactorizar + get totalPages() { + return Math.ceil(this.totalUsers / 5); + } + + changePage(value: number) { + this.page += value; + if (this.page < 0) { + this.page = 0; + return; + } else if (this.page * 5 >= this.totalUsers) { + this.page -= value; + return; + } + this.getUsers(); + } + + getUsers() { + this.isLoading = true; + this.userService.getUsers(this.page * 5).subscribe(({ total, users }) => { + // console.log(resp); + this.users = users; + this.totalUsers = total; + this.isLoading = false; + }); + } + + getUserByName(name) { + if (name == '') { + this.getUsers(); + return; + } + + this.isLoading = true; + this.searchService.searchByTerm(name, 'users').subscribe((resp: User[]) => { + // console.log(resp); + this.users = resp; + this.totalUsers = this.users.length; + this.isLoading = false; + }); + } + + deleteUser(user: User) { + if (user.uid === this.userService.uid) { + return Swal.fire('Error', 'No puede borrar su propio usuario', 'error'); + } + console.log(user); + Swal.fire({ + title: 'Eliminar Usuario', + text: `Desea eliminar al usuario ${user.name}?`, + icon: 'question', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Si, Eliminar', + }).then((result) => { + if (result.isConfirmed) { + this.userService.deleteUser(user).subscribe((resp) => { + this.getUsers(); + Swal.fire( + 'Eliminado!', + '${user.name} fue eliminado de los usuarios.', + 'success' + ); + }); + } + }); + } + + editUser(user: User) {} + + changeRole(user: User) { + // console.log(user); + this.userService.changeUserRole(user).subscribe((resp) => {}); + } + + openModal(user: User) { + this.modalService.openModal('users', user.uid, user.img); + } + + // Mi implementacion vieja con 2 funciones + // nextPage() { + // if ((this.page + 1) * 5 > this.totalUsers) return; + // this.page++; + // console.log(this.page); + // this.getUsers(this.page * 5); + // } + // prevPage() { + // if (this.page == 0) return; + // this.page--; + // this.getUsers(this.page * 5); + // } + + // Implementacion del profesor, el tenia una prop de la clase llamada this.from + // changePage(value: number) { + // this.from = value; + // if (this.from < 0) { + // this.form= 0 + // } else if (this.from > this.totalUsers) { + // this.from -= value + // } + // } +} diff --git a/src/app/pages/pages.component.html b/src/app/pages/pages.component.html index 4cc7d66..7122865 100644 --- a/src/app/pages/pages.component.html +++ b/src/app/pages/pages.component.html @@ -20,14 +20,16 @@ --> - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/app/pages/pages.module.ts b/src/app/pages/pages.module.ts index 8c06f42..5eea876 100644 --- a/src/app/pages/pages.module.ts +++ b/src/app/pages/pages.module.ts @@ -17,6 +17,7 @@ import { AccountSettingsComponent } from './account-settings/account-settings.co import { PromisesComponent } from './promises/promises.component'; import { RxjsComponent } from './rxjs/rxjs.component'; import { ProfileComponent } from './profile/profile.component'; +import { UsersComponent } from './maintenance/users/users.component'; @NgModule({ declarations: [ @@ -28,6 +29,7 @@ import { ProfileComponent } from './profile/profile.component'; PromisesComponent, RxjsComponent, ProfileComponent, + UsersComponent, ], exports: [ DashboardComponent, diff --git a/src/app/pages/pages.routing.ts b/src/app/pages/pages.routing.ts index 450be9b..64801df 100644 --- a/src/app/pages/pages.routing.ts +++ b/src/app/pages/pages.routing.ts @@ -12,6 +12,8 @@ import { PromisesComponent } from './promises/promises.component'; import { RxjsComponent } from './rxjs/rxjs.component'; import { ProfileComponent } from './profile/profile.component'; +import { UsersComponent } from './maintenance/users/users.component'; + const routes: Routes = [ { path: 'dashboard', @@ -45,6 +47,13 @@ const routes: Routes = [ data: { title: 'Pormesas' }, }, { path: 'rxjs', component: RxjsComponent, data: { title: 'RXJS' } }, + + // Mantenimientos + { + path: 'users', + component: UsersComponent, + data: { title: 'Usuarios de App' }, + }, ], }, ]; diff --git a/src/app/services/file-upload.service.ts b/src/app/services/file-upload.service.ts index 3e2d580..ba26c63 100644 --- a/src/app/services/file-upload.service.ts +++ b/src/app/services/file-upload.service.ts @@ -10,7 +10,7 @@ export class FileUploadService { async updateImage( file: File, - type: 'users' | 'doctors' | 'hospitals', + type: 'users' | 'hospitals' | 'doctors', id: string ) { try { diff --git a/src/app/services/modal-image.service.ts b/src/app/services/modal-image.service.ts new file mode 100644 index 0000000..bfed754 --- /dev/null +++ b/src/app/services/modal-image.service.ts @@ -0,0 +1,46 @@ +import { EventEmitter, Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; + +const base_url = environment.base_url; + +@Injectable({ + providedIn: 'root', +}) +export class ModalImageService { + private _hideModal = true; + type: 'users' | 'hospitals' | 'doctors'; + id: string; + img: string | ArrayBuffer; + newImg = new EventEmitter(); + + constructor() {} + + get hideModal() { + // console.log('hidemodal'); + return this._hideModal; + } + + openModal( + type: 'users' | 'hospitals' | 'doctors', + id: string, + img: string = 'no-img' + ) { + this._hideModal = false; + this.type = type; + this.id = id; + this.img = img; + + if (img.includes('https')) { + // console.log('url img:', this.img); + this.img = img; + } else if (img) { + // console.log('url img:', this.img); + this.img = `${base_url}/upload/${type}/${img}`; + } + this.img = `${base_url}/upload/users/no-image`; + } + + closeModal() { + this._hideModal = true; + } +} diff --git a/src/app/services/search.service.ts b/src/app/services/search.service.ts new file mode 100644 index 0000000..8f2fe92 --- /dev/null +++ b/src/app/services/search.service.ts @@ -0,0 +1,80 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { environment } from 'src/environments/environment'; + +import { + DocByTermResponse, + HospByTermResponse, + searchByTermResponse, + UserByTermResponse, +} from '../interfaces/repsonse-interface'; +import { User } from '../models/user.model'; + +const base_url = environment.base_url; + +@Injectable({ + providedIn: 'root', +}) +export class SearchService { + constructor(private http: HttpClient) {} + + get token(): string { + return localStorage.getItem('token') || ''; + } + + get headers() { + return { headers: { 'x-token': this.token } }; + } + + tranformInUsers(users: User[]) { + return users.map( + (user) => + new User( + user.name, + user.email, + '', + user.img, + user.google, + user.role, + user.uid + ) + ); + } + + searchByTerm( + term: string = '', + type: 'users' | 'hospitals' | 'doctors' + ): Observable { + const url = `${base_url}/search/collection/${type}/${term}`; + return this.http + .get( + url, + this.headers + ) + .pipe( + map((resp) => { + if (type === 'users') { + return this.tranformInUsers(resp.results as User[]); + } else if (type === 'hospitals') { + return resp; + } else if (type === 'doctors') { + return resp; + } + }) + ); + } + + searchByTermProfe( + term: string = '', + type: 'users' | 'hospitals' | 'doctors' + ) { + const url = `${base_url}/search/collection/${type}/${term}`; + return this.http.get(url, this.headers).pipe( + map((resp) => { + resp.results; + }) + ); + } +} diff --git a/src/app/services/sidebar.service.ts b/src/app/services/sidebar.service.ts index 399af87..40a075f 100644 --- a/src/app/services/sidebar.service.ts +++ b/src/app/services/sidebar.service.ts @@ -17,6 +17,16 @@ export class SidebarService { { title: 'Rxjs', url: 'rxjs' }, ], }, + + { + title: 'Mantenimiento', + icon: 'mdi mdi-tooltip-edit', + submenu: [ + { title: 'Usuarios', url: 'users' }, + { title: 'Hospitales', url: 'hospitals' }, + { title: 'Medicos', url: 'doctors' }, + ], + }, ]; constructor() {} diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index 45fe86e..7d3a9d1 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -1,17 +1,18 @@ import { HttpClient } from '@angular/common/http'; import { Injectable, NgZone } from '@angular/core'; +import { Router } from '@angular/router'; + import { catchError, map, tap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; import { environment } from 'src/environments/environment'; -import { Observable, of } from 'rxjs'; -import { Router } from '@angular/router'; - import { LoginForm, ProfileForm, RegisterForm, } from '../interfaces/forms.interface'; +import { getUsersResponse } from '../interfaces/repsonse-interface'; import { User } from '../models/user.model'; const base_url = environment.base_url; @@ -40,6 +41,10 @@ export class UserService { return this.user.uid || ''; } + get headers() { + return { headers: { 'x-token': this.token } }; + } + googleInit() { return new Promise((resolve) => { gapi.load('auth2', () => { @@ -73,23 +78,6 @@ export class UserService { }) ); } - - createUser(formData: RegisterForm) { - return this.http.post(`${base_url}/users`, formData).pipe( - tap((res: any) => { - localStorage.setItem('token', res.token); - }) - ); - } - - // updateUser(formData: {name:string,email:string}) - updateUser(formData: ProfileForm) { - formData = { ...formData, role: this.user.role }; - return this.http.put(`${base_url}/users/${this.uid}`, formData, { - headers: { 'x-token': this.token }, - }); - } - // TODO: Crear y aplicar interfaces para las respuestas de las peticiones login(formData: LoginForm) { try { @@ -118,4 +106,54 @@ export class UserService { this.ngZone.run(() => this.router.navigate(['/login'])); }); } + + createUser(formData: RegisterForm) { + return this.http.post(`${base_url}/users`, formData).pipe( + tap((res: any) => { + localStorage.setItem('token', res.token); + }) + ); + } + + // updateUser(formData: {name:string,email:string}) + updateUser(formData: ProfileForm) { + formData = { ...formData, role: this.user.role }; + return this.http.put( + `${base_url}/users/${this.uid}`, + formData, + this.headers + ); + } + + changeUserRole(user: User) { + // console.log(user); + + return this.http.put(`${base_url}/users/${user.uid}`, user, this.headers); + } + + getUsers(from: number = 0) { + const url = `${base_url}/users?from=${from}`; + return this.http.get(url, this.headers).pipe( + map((resp) => { + resp.users = resp.users.map( + (user) => + new User( + user.name, + user.email, + '', + user.img, + user.google, + user.role, + user.uid + ) + ); + return resp; + }) + ); + } + + deleteUser(user: User) { + const url = `${base_url}/users/${user.uid}`; + return this.http.delete(url, this.headers); + } } diff --git a/src/app/shared/sidebar/sidebar.component.html b/src/app/shared/sidebar/sidebar.component.html index b6ba9ef..1cc3fc1 100644 --- a/src/app/shared/sidebar/sidebar.component.html +++ b/src/app/shared/sidebar/sidebar.component.html @@ -23,7 +23,7 @@