├── src ├── assets │ ├── .gitkeep │ ├── images │ │ ├── Frame 2.svg │ │ ├── Dropdown.svg │ │ ├── lupa.svg │ │ ├── Voltar.svg │ │ ├── logo-card.svg │ │ ├── patient.svg │ │ ├── Logo.svg │ │ ├── calendar.svg │ │ ├── doctor.svg │ │ ├── person.svg │ │ └── Logo-icon.svg │ └── scss │ │ └── _base.scss ├── app │ ├── app.component.scss │ ├── app.component.html │ ├── shared │ │ ├── components │ │ │ ├── container │ │ │ │ ├── container.component.html │ │ │ │ ├── container.component.scss │ │ │ │ └── container.component.ts │ │ │ ├── error-message │ │ │ │ ├── error-message.component.html │ │ │ │ ├── error-message.component.scss │ │ │ │ └── error-message.component.ts │ │ │ ├── button │ │ │ │ ├── button.component.html │ │ │ │ ├── button.component.ts │ │ │ │ └── button.component.scss │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.ts │ │ │ │ └── header.component.scss │ │ │ ├── modals │ │ │ │ ├── modal-info │ │ │ │ │ ├── modal-info.component.html │ │ │ │ │ ├── modal-info.component.ts │ │ │ │ │ └── modal-info.component.scss │ │ │ │ └── modal-deactivation │ │ │ │ │ ├── modal-deactivation.component.html │ │ │ │ │ ├── modal-deactivation.component.scss │ │ │ │ │ └── modal-deactivation.component.ts │ │ │ ├── card-profile │ │ │ │ ├── card-profile.component.ts │ │ │ │ ├── card-profile.component.html │ │ │ │ └── card-profile.component.scss │ │ │ └── forms │ │ │ │ ├── contact-form │ │ │ │ ├── contact-form.component.ts │ │ │ │ ├── contact-form.component.html │ │ │ │ └── contact-form.component.scss │ │ │ │ └── address-form │ │ │ │ ├── address-form.component.ts │ │ │ │ ├── address-form.component.scss │ │ │ │ └── address-form.component.html │ │ ├── models │ │ │ ├── contact.ts │ │ │ ├── address.ts │ │ │ ├── profile-card.ts │ │ │ └── state.ts │ │ ├── pipes │ │ │ ├── cpf.pipe.ts │ │ │ ├── postal-code.pipe.ts │ │ │ ├── phone.pipe.ts │ │ │ └── filter-by-name.pipe.ts │ │ ├── services │ │ │ ├── contact-form.service.ts │ │ │ ├── address-form.service.ts │ │ │ ├── profile-deactivation.service.ts │ │ │ └── profile-listing.service.ts │ │ └── shared.module.ts │ ├── features │ │ ├── doctors │ │ │ ├── models │ │ │ │ ├── specialty.ts │ │ │ │ └── doctor.ts │ │ │ ├── services │ │ │ │ ├── doctor-listing.service.ts │ │ │ │ └── doctor.service.ts │ │ │ ├── doctors-routing.module.ts │ │ │ ├── doctors.module.ts │ │ │ └── pages │ │ │ │ ├── doctor-list │ │ │ │ ├── doctor-list.component.html │ │ │ │ ├── doctor-list.component.scss │ │ │ │ └── doctor-list.component.ts │ │ │ │ ├── doctor-edit │ │ │ │ ├── doctor-edit.component.scss │ │ │ │ ├── doctor-edit.component.html │ │ │ │ └── doctor-edit.component.ts │ │ │ │ └── doctor-register │ │ │ │ ├── doctor-register.component.scss │ │ │ │ ├── doctor-register.component.html │ │ │ │ └── doctor-register.component.ts │ │ ├── home │ │ │ ├── home-routing.module.ts │ │ │ ├── home.module.ts │ │ │ ├── home.component.ts │ │ │ ├── home.component.html │ │ │ └── home.component.scss │ │ └── patients │ │ │ ├── patients-routing.module.ts │ │ │ ├── patients.module.ts │ │ │ ├── pages │ │ │ ├── patient-list │ │ │ │ ├── patient-list.component.html │ │ │ │ ├── patient-list.component.scss │ │ │ │ └── patient-list.component.ts │ │ │ ├── patient-edit │ │ │ │ ├── patient-edit.component.scss │ │ │ │ ├── patient-edit.component.html │ │ │ │ └── patient-edit.component.ts │ │ │ └── patient-register │ │ │ │ ├── patient-register.component.scss │ │ │ │ ├── patient-register.component.html │ │ │ │ └── patient-register.component.ts │ │ │ ├── models │ │ │ └── patient.ts │ │ │ └── services │ │ │ └── patient.service.ts │ ├── app.component.ts │ ├── app.module.ts │ └── app-routing.module.ts ├── favicon.ico ├── environments │ ├── environment.ts │ └── environment.development.ts ├── main.ts ├── index.html └── styles.scss ├── commitlint.config.js ├── .lintstagedrc.json ├── .husky ├── pre-commit └── commit-msg ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── tsconfig.spec.json ├── tsconfig.app.json ├── .editorconfig ├── .prettierrc.json ├── .gitignore ├── .prettierignore ├── tsconfig.json ├── README.md ├── .eslintrc.json ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeotoni/VollMed-Web/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /src/app/shared/components/container/container.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,ts}": ["npm run lint:fix", "npm run prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiUrl: 'http://api', 3 | }; 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | npx commitlint --edit $1 4 | -------------------------------------------------------------------------------- /src/app/shared/models/contact.ts: -------------------------------------------------------------------------------- 1 | export interface Contact { 2 | email: string; 3 | phone: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/shared/components/error-message/error-message.component.html: -------------------------------------------------------------------------------- 1 | {{ text }} 2 | -------------------------------------------------------------------------------- /src/environments/environment.development.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiUrl: 'http://localhost:8080', 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/features/doctors/models/specialty.ts: -------------------------------------------------------------------------------- 1 | export enum Specialty { 2 | ORTOPEDIA, 3 | CARDIOLOGIA, 4 | GINECOLOGIA, 5 | DERMATOLOGIA, 6 | } 7 | -------------------------------------------------------------------------------- /src/app/shared/components/button/button.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/app/shared/components/container/container.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | margin: 0 auto; 4 | max-width: 1048px; 5 | width: 90%; 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /src/app/shared/components/error-message/error-message.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | .error-message { 4 | color: $red-warning; 5 | font-size: $fs-1; 6 | display: block; 7 | padding: 0 0 16px; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/shared/models/address.ts: -------------------------------------------------------------------------------- 1 | import { State } from './state'; 2 | 3 | export interface Address { 4 | street: string; 5 | postalCode: string; 6 | city: string; 7 | number: string; 8 | complement: string; 9 | state: State; 10 | } 11 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | platformBrowserDynamic() 6 | .bootstrapModule(AppModule) 7 | .catch((err) => console.error(err)); 8 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | }) 8 | export class AppComponent { 9 | title = 'VollMed'; 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/images/Frame 2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/shared/components/container/container.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-container', 5 | templateUrl: './container.component.html', 6 | styleUrls: ['./container.component.scss'], 7 | }) 8 | export class ContainerComponent {} 9 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": ["jasmine"] 7 | }, 8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": ["src/main.ts"], 9 | "include": ["src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | ícone de seta 9 |

{{ headerTitle }}

10 |
11 |
12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/app/shared/components/error-message/error-message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-error-message', 5 | templateUrl: './error-message.component.html', 6 | styleUrls: ['./error-message.component.scss'], 7 | }) 8 | export class ErrorMessageComponent { 9 | @Input() text!: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/images/Dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/shared/components/modals/modal-info/modal-info.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | símbolo de perfil 4 |

{{ title }}

5 |
6 |

{{ text }}

7 | 8 |
9 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | VollMed 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/shared/models/profile-card.ts: -------------------------------------------------------------------------------- 1 | import { Specialty } from 'app/features/doctors/models/specialty'; 2 | import { Address } from './address'; 3 | import { Contact } from './contact'; 4 | 5 | export interface ProfileCard { 6 | id: number; 7 | name: string; 8 | contact: Contact; 9 | address: Address; 10 | specialty?: Specialty; 11 | crm?: string; 12 | cpf?: string; 13 | profileType: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/shared/components/button/button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-button', 5 | templateUrl: './button.component.html', 6 | styleUrls: ['./button.component.scss'], 7 | }) 8 | export class ButtonComponent { 9 | @Input() text!: string; 10 | @Input() style: 'primary' | 'secondary' | 'disabled' = 'primary'; 11 | @Input() disabled = true; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shared/pipes/cpf.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'cpf', 5 | }) 6 | export class CpfPipe implements PipeTransform { 7 | transform(value: string): string { 8 | const cpf = value.toString(); 9 | if (cpf.length === 11) { 10 | return cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4'); 11 | } 12 | return value.toString(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/scss/_base.scss: -------------------------------------------------------------------------------- 1 | $primary-blue: #0b3b60; 2 | $secondary-blue: #85c4ff; 3 | $blue-logo: #339cff; 4 | $red-warning: #ff0000; 5 | $primary-black: #161717; 6 | $primary-white: #fff; 7 | $font-gray: #6b6e71; 8 | $secondary-gray: #90989f; 9 | $box-shadow-gray: #cad2d9; 10 | $bg-input-disable: #e7ebef; 11 | 12 | $fw-1: 400; 13 | $fw-2: 700; 14 | 15 | $fs-1: 0.875rem; 16 | $fs-2: 1rem; 17 | $fs-3: 1.125rem; 18 | $fs-4: 1.25rem; 19 | -------------------------------------------------------------------------------- /src/app/shared/pipes/postal-code.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'postalCode', 5 | }) 6 | export class PostalCodePipe implements PipeTransform { 7 | transform(value: string): string { 8 | const cep = value.toString(); 9 | if (cep.length === 8) { 10 | return cep.replace(/(\d{2})(\d{3})(\d{3})/, '$1.$2-$3'); 11 | } 12 | return value.toString(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/features/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HomeComponent } from './home.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: HomeComponent, 9 | }, 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule], 15 | }) 16 | export class HomeRoutingModule {} 17 | -------------------------------------------------------------------------------- /src/app/features/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { SharedModule } from 'app/shared/shared.module'; 4 | import { HomeRoutingModule } from './home-routing.module'; 5 | import { HomeComponent } from './home.component'; 6 | 7 | @NgModule({ 8 | declarations: [HomeComponent], 9 | imports: [CommonModule, SharedModule, HomeRoutingModule], 10 | }) 11 | export class HomeModule {} 12 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Location } from '@angular/common'; 2 | import { Component, Input } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-header', 6 | templateUrl: './header.component.html', 7 | styleUrls: ['./header.component.scss'], 8 | }) 9 | export class HeaderComponent { 10 | @Input() headerTitle!: string; 11 | 12 | constructor(private location: Location) {} 13 | 14 | goBack() { 15 | this.location.back(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": true, 4 | "bracketSpacing": true, 5 | "cursorOffset": -1, 6 | "endOfLine": "lf", 7 | "htmlWhitespaceSensitivity": "css", 8 | "insertPragma": false, 9 | "jsxSingleQuote": true, 10 | "printWidth": 80, 11 | "proseWrap": "preserve", 12 | "quoteProps": "as-needed", 13 | "requirePragma": false, 14 | "semi": true, 15 | "singleQuote": true, 16 | "trailingComma": "all", 17 | "tabWidth": 2, 18 | "useTabs": false 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/models/state.ts: -------------------------------------------------------------------------------- 1 | export enum State { 2 | AC = 'AC', 3 | AL = 'AL', 4 | AP = 'AP', 5 | AM = 'AM', 6 | BA = 'BA', 7 | CE = 'CE', 8 | DF = 'DF', 9 | ES = 'ES', 10 | GO = 'GO', 11 | MA = 'MA', 12 | MT = 'MT', 13 | MS = 'MS', 14 | MG = 'MG', 15 | PA = 'PA', 16 | PB = 'PB', 17 | PR = 'PR', 18 | PE = 'PE', 19 | PI = 'PI', 20 | RJ = 'RJ', 21 | RN = 'RN', 22 | RS = 'RS', 23 | RO = 'RO', 24 | RR = 'RR', 25 | SC = 'SC', 26 | SP = 'SP', 27 | SE = 'SE', 28 | TO = 'TO', 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/images/lupa.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap'); 4 | 5 | * { 6 | font-family: 'Poppins', sans-serif; 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: border-box; 10 | 11 | ul { 12 | list-style: none; 13 | } 14 | 15 | a { 16 | text-decoration: none; 17 | color: inherit; 18 | } 19 | 20 | a:hover, 21 | button:hover { 22 | cursor: pointer; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/features/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-home', 6 | templateUrl: './home.component.html', 7 | styleUrls: ['./home.component.scss'], 8 | }) 9 | export class HomeComponent { 10 | constructor(private router: Router) {} 11 | 12 | navigateToDoctors(): void { 13 | this.router.navigate(['doctors']); 14 | } 15 | 16 | navigateToPatients(): void { 17 | this.router.navigate(['patients']); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/pipes/phone.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'phone', 5 | }) 6 | export class PhonePipe implements PipeTransform { 7 | transform(value: string): string { 8 | const phone = value.toString(); 9 | if (phone.length === 10) { 10 | return phone.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); 11 | } else if (phone.length === 11) { 12 | return phone.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3'); 13 | } 14 | return value.toString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/images/Voltar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | 5 | import { AppRoutingModule } from './app-routing.module'; 6 | import { AppComponent } from './app.component'; 7 | import { SharedModule } from './shared/shared.module'; 8 | 9 | @NgModule({ 10 | declarations: [AppComponent], 11 | imports: [BrowserModule, AppRoutingModule, SharedModule, HttpClientModule], 12 | providers: [], 13 | bootstrap: [AppComponent], 14 | }) 15 | export class AppModule {} 16 | -------------------------------------------------------------------------------- /src/app/features/doctors/services/doctor-listing.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ProfileCard } from 'app/shared/models/profile-card'; 3 | import { BehaviorSubject, Observable } from 'rxjs'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class DoctorListingService { 9 | private listDoctorSubject = new BehaviorSubject([]); 10 | 11 | setUpdatedList(list: ProfileCard[]) { 12 | this.listDoctorSubject.next(list); 13 | } 14 | 15 | getUpdatedList(): Observable { 16 | return this.listDoctorSubject.asObservable(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/shared/components/button/button.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | button { 4 | border-radius: 8px; 5 | font-size: $fs-2; 6 | font-weight: $fw-2; 7 | padding: 12px 16px; 8 | width: 100%; 9 | border: none; 10 | background-color: $primary-white; 11 | 12 | &:hover { 13 | cursor: pointer; 14 | } 15 | 16 | &.primary { 17 | background-color: $primary-blue; 18 | color: $primary-white; 19 | } 20 | 21 | &.secondary { 22 | border: 2px solid $primary-blue; 23 | color: $primary-blue; 24 | } 25 | 26 | &.disabled { 27 | background-color: $secondary-gray; 28 | color: $primary-white; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/shared/components/modals/modal-info/modal-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-modal-info', 5 | templateUrl: './modal-info.component.html', 6 | styleUrls: ['./modal-info.component.scss'], 7 | }) 8 | export class ModalInfoComponent { 9 | @Input() text!: string; 10 | @Input() title!: string; 11 | 12 | @ViewChild('modalInfo') modal!: ElementRef; 13 | 14 | nativeElement: HTMLElement; 15 | 16 | constructor(element: ElementRef) { 17 | this.nativeElement = element.nativeElement; 18 | } 19 | 20 | close() { 21 | this.modal.nativeElement.close(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/shared/services/contact-form.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | 4 | @Injectable({ 5 | providedIn: 'root', 6 | }) 7 | export class ContactFormService { 8 | createContactForm(values?: { email?: string; phone?: string }): FormGroup { 9 | return new FormGroup({ 10 | email: new FormControl(values?.email || '', [ 11 | Validators.required, 12 | Validators.email, 13 | ]), 14 | phone: new FormControl(values?.phone || '', [ 15 | Validators.required, 16 | Validators.pattern(/^\d{10,11}$/), 17 | ]), 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/app/shared/pipes/filter-by-name.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { ProfileCard } from '../models/profile-card'; 3 | 4 | @Pipe({ name: 'filterByName' }) 5 | export class FilterByName implements PipeTransform { 6 | transform( 7 | profileList: { letter: string; profiles: ProfileCard[] }[], 8 | nameQuery: string, 9 | ) { 10 | nameQuery = nameQuery?.toLowerCase(); 11 | 12 | if (nameQuery) { 13 | return profileList.map((item) => { 14 | const filteredProfiles = item.profiles.filter((profile) => 15 | profile.name.toLowerCase().includes(nameQuery), 16 | ); 17 | 18 | return { letter: item.letter, profiles: filteredProfiles }; 19 | }); 20 | } 21 | 22 | return profileList; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/features/doctors/doctors-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { DoctorEditComponent } from './pages/doctor-edit/doctor-edit.component'; 4 | import { DoctorListComponent } from './pages/doctor-list/doctor-list.component'; 5 | import { DoctorRegisterComponent } from './pages/doctor-register/doctor-register.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: DoctorListComponent, 11 | }, 12 | { 13 | path: 'edit/:id', 14 | component: DoctorEditComponent, 15 | }, 16 | { 17 | path: 'register', 18 | component: DoctorRegisterComponent, 19 | }, 20 | ]; 21 | 22 | @NgModule({ 23 | imports: [RouterModule.forChild(routes)], 24 | exports: [RouterModule], 25 | }) 26 | export class DoctorsRoutingModule {} 27 | -------------------------------------------------------------------------------- /src/app/features/patients/patients-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { PatientEditComponent } from './pages/patient-edit/patient-edit.component'; 4 | import { PatientListComponent } from './pages/patient-list/patient-list.component'; 5 | import { PatientRegisterComponent } from './pages/patient-register/patient-register.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: PatientListComponent, 11 | }, 12 | { 13 | path: 'register', 14 | component: PatientRegisterComponent, 15 | }, 16 | { 17 | path: 'edit/:id', 18 | component: PatientEditComponent, 19 | }, 20 | ]; 21 | 22 | @NgModule({ 23 | imports: [RouterModule.forChild(routes)], 24 | exports: [RouterModule], 25 | }) 26 | export class PatientsRoutingModule {} 27 | -------------------------------------------------------------------------------- /src/app/features/doctors/doctors.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { SharedModule } from 'app/shared/shared.module'; 5 | import { DoctorsRoutingModule } from './doctors-routing.module'; 6 | import { DoctorEditComponent } from './pages/doctor-edit/doctor-edit.component'; 7 | import { DoctorListComponent } from './pages/doctor-list/doctor-list.component'; 8 | import { DoctorRegisterComponent } from './pages/doctor-register/doctor-register.component'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | DoctorListComponent, 13 | DoctorEditComponent, 14 | DoctorRegisterComponent, 15 | ], 16 | imports: [ 17 | CommonModule, 18 | SharedModule, 19 | DoctorsRoutingModule, 20 | ReactiveFormsModule, 21 | ], 22 | }) 23 | export class DoctorsModule {} 24 | -------------------------------------------------------------------------------- /src/app/shared/components/card-profile/card-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { ProfileCard } from 'app/shared/models/profile-card'; 4 | 5 | @Component({ 6 | selector: 'app-card-profile', 7 | templateUrl: './card-profile.component.html', 8 | styleUrls: ['./card-profile.component.scss'], 9 | }) 10 | export class CardProfileComponent { 11 | @Input() profile!: ProfileCard; 12 | @ViewChild('deactivationModal') modal!: ElementRef; 13 | 14 | constructor( 15 | private router: Router, 16 | private route: ActivatedRoute, 17 | ) {} 18 | 19 | edit() { 20 | this.router.navigate([`edit`, this.profile.id], { 21 | relativeTo: this.route, 22 | }); 23 | } 24 | 25 | deactivate(): void { 26 | this.modal.nativeElement.firstChild.showModal(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/features/patients/patients.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { ReactiveFormsModule } from '@angular/forms'; 5 | import { SharedModule } from 'app/shared/shared.module'; 6 | import { PatientEditComponent } from './pages/patient-edit/patient-edit.component'; 7 | import { PatientListComponent } from './pages/patient-list/patient-list.component'; 8 | import { PatientRegisterComponent } from './pages/patient-register/patient-register.component'; 9 | import { PatientsRoutingModule } from './patients-routing.module'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | PatientRegisterComponent, 14 | PatientListComponent, 15 | PatientEditComponent, 16 | ], 17 | imports: [ 18 | CommonModule, 19 | SharedModule, 20 | ReactiveFormsModule, 21 | PatientsRoutingModule, 22 | ], 23 | }) 24 | export class PatientsModule {} 25 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | header { 4 | background-color: $primary-blue; 5 | padding: 16px; 6 | display: flex; 7 | justify-content: space-between; 8 | color: $primary-white; 9 | color: var(--pb-white, #fff); 10 | font-size: $fs-3; 11 | font-weight: $fw-1; 12 | line-height: 21.9px; 13 | 14 | & .title-group { 15 | display: flex; 16 | align-items: end; 17 | gap: 16px; 18 | } 19 | } 20 | 21 | select { 22 | appearance: none; 23 | -webkit-appearance: none; 24 | -moz-appearance: none; 25 | background: transparent; 26 | border: 1px solid $primary-blue; 27 | padding: 12px 18px; 28 | background-color: $primary-white; 29 | text-align: center; 30 | color: $primary-blue; 31 | border-radius: 8px 8px 0 0; 32 | font-size: $fs-2; 33 | font-weight: $fw-1; 34 | line-height: 19.4px; 35 | outline: none; 36 | position: absolute; 37 | top: 10px; 38 | right: 12%; 39 | } 40 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | redirectTo: 'home', 8 | pathMatch: 'full', 9 | }, 10 | { 11 | path: 'home', 12 | loadChildren: () => 13 | import('./features/home/home.module').then((m) => m.HomeModule), 14 | }, 15 | { 16 | path: 'doctors', 17 | loadChildren: () => 18 | import('./features/doctors/doctors.module').then((m) => m.DoctorsModule), 19 | }, 20 | { 21 | path: 'patients', 22 | loadChildren: () => 23 | import('./features/patients/patients.module').then( 24 | (m) => m.PatientsModule, 25 | ), 26 | }, 27 | { 28 | path: '**', 29 | redirectTo: 'home', 30 | pathMatch: 'full', 31 | }, 32 | ]; 33 | 34 | @NgModule({ 35 | imports: [RouterModule.forRoot(routes)], 36 | exports: [RouterModule], 37 | }) 38 | export class AppRoutingModule {} 39 | -------------------------------------------------------------------------------- /src/app/shared/components/modals/modal-info/modal-info.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | dialog { 4 | padding: 22px; 5 | width: 100%; 6 | max-width: 360px; 7 | text-align: center; 8 | border: none; 9 | position: absolute; 10 | top: 100%; 11 | bottom: 0; 12 | border-radius: 10px 10px 0 0; 13 | margin-inline: auto; 14 | 15 | .dialog__header { 16 | display: flex; 17 | flex-direction: column; 18 | gap: 10px; 19 | margin-bottom: 18px; 20 | 21 | img { 22 | background-color: $primary-blue; 23 | border-radius: 5px; 24 | width: 110px; 25 | margin-inline: auto; 26 | } 27 | 28 | h3 { 29 | color: $primary-blue; 30 | font-size: $fs-4; 31 | font-weight: 700; 32 | } 33 | } 34 | 35 | p { 36 | color: $primary-black; 37 | font-size: $fs-3; 38 | line-height: 1.3; 39 | margin-bottom: 18px; 40 | } 41 | 42 | &::backdrop { 43 | background: rgba(0, 0, 0, 0.7); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/features/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 | 8 |
    9 |

    Escolha qual seção deseja iniciar:

    10 | 17 | 24 |
  • 25 | Icone de calendário 26 |

    Consultas

    27 |
  • 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "src/", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "useDefineForClassFields": false, 22 | "lib": ["ES2022", "dom"] 23 | }, 24 | "angularCompilerOptions": { 25 | "enableI18nLegacyMessageIdFormat": false, 26 | "strictInjectionParameters": true, 27 | "strictInputAccessModifiers": true, 28 | "strictTemplates": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/shared/services/address-form.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | 4 | @Injectable({ 5 | providedIn: 'root', 6 | }) 7 | export class AddressFormService { 8 | createAddressForm(values?: { 9 | street: string; 10 | number: string; 11 | complement: string; 12 | city: string; 13 | state: string; 14 | postalCode: string; 15 | }): FormGroup { 16 | return new FormGroup({ 17 | street: new FormControl(values?.street || '', [Validators.required]), 18 | number: new FormControl(values?.number || ''), 19 | complement: new FormControl(values?.complement || ''), 20 | city: new FormControl(values?.city || '', [Validators.required]), 21 | state: new FormControl(values?.state || '', [Validators.required]), 22 | postalCode: new FormControl(values?.postalCode || '', [ 23 | Validators.required, 24 | Validators.pattern(/^\d{8}$/), 25 | ]), 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/features/home/home.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | section { 4 | display: flex; 5 | flex-direction: column; 6 | gap: 50px; 7 | 8 | & .container-logo { 9 | padding-top: 38px; 10 | } 11 | 12 | h1 { 13 | color: $primary-blue; 14 | font-size: $fs-4; 15 | font-style: normal; 16 | font-weight: $fw-2; 17 | line-height: 19.4px; 18 | padding-top: 8px; 19 | } 20 | 21 | ul { 22 | display: flex; 23 | flex-direction: column; 24 | gap: 16px; 25 | 26 | p { 27 | color: $primary-black; 28 | font-size: $fs-2; 29 | font-style: normal; 30 | font-weight: $fw-1; 31 | line-height: 19.4px; 32 | } 33 | 34 | li { 35 | background-color: $primary-blue; 36 | border-radius: 10px; 37 | text-align: center; 38 | padding: 24px 18px; 39 | 40 | p { 41 | color: $primary-white; 42 | font-size: $fs-3; 43 | font-weight: $fw-2; 44 | padding-top: 10px; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/shared/components/card-profile/card-profile.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

{{ profile.name }}

6 |

7 | {{ profile.specialty }} | CRM {{ profile.crm }}-{{ 8 | profile.address.state 9 | }} 10 |

11 |

{{ profile.cpf | cpf }}

12 |
13 |
14 | 15 |
16 |

{{ profile.contact.email }}

17 |

{{ profile.contact.phone | phone }}

18 |
19 | 20 |
21 |

{{ profile.address.street }}, {{ profile.address.number }}

22 |

{{ profile.address.city }}/{{ profile.address.state }}

23 |

CEP: {{ profile.address.postalCode | postalCode }}

24 |
25 | 26 | 27 |
28 |
29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-list/doctor-list.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 10 |
11 |
12 |
13 |
14 | {{ item.letter }} 15 | logo da vollmed 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-list/patient-list.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 10 |
11 |
12 |
13 |
14 | {{ item.letter }} 15 | logo da vollmed 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VollMed 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.10. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /src/app/shared/services/profile-deactivation.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { PatientService } from 'app/features/patients/services/patient.service'; 3 | 4 | import { Observable, switchMap, tap } from 'rxjs'; 5 | import { ProfileCard } from '../models/profile-card'; 6 | import { ProfileListingService } from './profile-listing.service'; 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class ProfileDeactivationService { 12 | constructor( 13 | private patientService: PatientService, 14 | private profileListingService: ProfileListingService, 15 | ) {} 16 | 17 | deactivateProfile( 18 | profileId: number, 19 | profileType: string, 20 | ): Observable { 21 | switch (profileType) { 22 | case 'PATIENT': 23 | return this.patientService.deletePatient(profileId).pipe( 24 | switchMap(() => this.patientService.getPatientList()), 25 | tap((patients) => 26 | this.profileListingService.setUpdatedList(patients), 27 | ), 28 | ); 29 | 30 | default: 31 | throw new Error('Tipo de perfil não suportado'); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/images/logo-card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/app/shared/services/profile-listing.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | import { ProfileCard } from '../models/profile-card'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class ProfileListingService { 9 | private listProfileSubject = new BehaviorSubject([]); 10 | 11 | setUpdatedList(list: ProfileCard[]) { 12 | this.listProfileSubject.next(list); 13 | } 14 | 15 | getUpdatedList(): Observable { 16 | return this.listProfileSubject.asObservable(); 17 | } 18 | 19 | listJoinedByLetter(list: ProfileCard[]) { 20 | const groupedProfiles: { letter: string; profiles: ProfileCard[] }[] = []; 21 | const groupedMap = new Map(); 22 | 23 | for (const profile of list) { 24 | const letter = profile.name.charAt(0).toUpperCase(); 25 | if (!groupedMap.has(letter)) { 26 | groupedMap.set(letter, []); 27 | } 28 | groupedMap.get(letter)?.push(profile); 29 | } 30 | 31 | groupedMap.forEach((value, key) => { 32 | groupedProfiles.push({ letter: key, profiles: value }); 33 | }); 34 | 35 | return groupedProfiles; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/shared/components/forms/contact-form/contact-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject, Input, OnDestroy, OnInit } from '@angular/core'; 2 | import { ControlContainer, FormGroup } from '@angular/forms'; 3 | import { ContactFormService } from 'app/shared/services/contact-form.service'; 4 | 5 | @Component({ 6 | selector: 'app-contact-form', 7 | templateUrl: './contact-form.component.html', 8 | styleUrls: ['./contact-form.component.scss'], 9 | viewProviders: [ 10 | { 11 | provide: ControlContainer, 12 | useFactory: () => inject(ControlContainer, { skipSelf: true }), 13 | }, 14 | ], 15 | }) 16 | export class ContactFormComponent implements OnInit, OnDestroy { 17 | @Input({ required: true }) controlKey = ''; 18 | @Input() label = ''; 19 | 20 | parentContainer = inject(ControlContainer); 21 | 22 | constructor(private contactFormService: ContactFormService) {} 23 | 24 | ngOnInit(): void { 25 | const contactForm = this.contactFormService.createContactForm(); 26 | this.parentFormGroup.setControl(this.controlKey, contactForm); 27 | } 28 | 29 | ngOnDestroy() { 30 | this.parentFormGroup.removeControl(this.controlKey); 31 | } 32 | 33 | get parentFormGroup() { 34 | return this.parentContainer.control as FormGroup; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/features/patients/models/patient.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'app/shared/models/address'; 2 | import { Contact } from 'app/shared/models/contact'; 3 | 4 | export interface Patient { 5 | id?: number; 6 | name: string; 7 | cpf: string; 8 | contact: Contact; 9 | address: Address; 10 | profileType?: string; 11 | } 12 | 13 | export interface PatientList { 14 | id: number; 15 | name: string; 16 | cpf: string; 17 | contact: Contact; 18 | address: Address; 19 | profileType: string; 20 | } 21 | 22 | export interface PatientEdit { 23 | id: number; 24 | name: string; 25 | contact: { 26 | phone: string; 27 | }; 28 | address: Address; 29 | } 30 | 31 | export interface PaginatedPatientResponse { 32 | content: PatientList[]; 33 | pageable: { 34 | pageNumber: number; 35 | pageSize: number; 36 | sort: { 37 | sorted: boolean; 38 | unsorted: boolean; 39 | empty: boolean; 40 | }; 41 | offset: number; 42 | paged: boolean; 43 | unpaged: boolean; 44 | }; 45 | totalPages: number; 46 | totalElements: number; 47 | last: boolean; 48 | size: number; 49 | number: number; 50 | sort: { 51 | sorted: boolean; 52 | unsorted: boolean; 53 | empty: boolean; 54 | }; 55 | numberOfElements: number; 56 | first: boolean; 57 | empty: boolean; 58 | } 59 | -------------------------------------------------------------------------------- /src/app/features/patients/services/patient.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { environment } from 'environments/environment.development'; 4 | import { map, Observable } from 'rxjs'; 5 | import { 6 | PaginatedPatientResponse, 7 | Patient, 8 | PatientEdit, 9 | PatientList, 10 | } from '../models/patient'; 11 | 12 | @Injectable({ providedIn: 'root' }) 13 | export class PatientService { 14 | private apiUrl: string = environment.apiUrl; 15 | 16 | constructor(private http: HttpClient) {} 17 | 18 | registerPatient(data: Patient): Observable { 19 | return this.http.post(`${this.apiUrl}/patients`, data); 20 | } 21 | 22 | getPatientList(): Observable { 23 | return this.http 24 | .get(`${this.apiUrl}/patients`) 25 | .pipe(map((list) => list.content)); 26 | } 27 | 28 | getPatientById(id: number): Observable { 29 | return this.http.get(`${this.apiUrl}/patients/${id}`); 30 | } 31 | 32 | updatePatient(data: PatientEdit): Observable { 33 | return this.http.put(`${this.apiUrl}/patients`, data); 34 | } 35 | 36 | deletePatient(id: number): Observable { 37 | return this.http.delete(`${this.apiUrl}/patients/${id}`); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/images/patient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/features/doctors/services/doctor.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { 4 | Doctor, 5 | DoctorEdit, 6 | DoctorList, 7 | PaginatedDoctorResponse, 8 | } from 'app/features/doctors/models/doctor'; 9 | 10 | import { environment } from 'environments/environment.development'; 11 | import { Observable, map } from 'rxjs'; 12 | 13 | @Injectable({ 14 | providedIn: 'root', 15 | }) 16 | export class DoctorService { 17 | private apiUrl: string = environment.apiUrl; 18 | 19 | constructor(private http: HttpClient) {} 20 | 21 | registerDoctor(data: Doctor): Observable { 22 | return this.http.post(`${this.apiUrl}/doctors`, data); 23 | } 24 | 25 | getDoctorList(): Observable { 26 | return this.http 27 | .get(`${this.apiUrl}/doctors`) 28 | .pipe(map((list) => list.content)); 29 | } 30 | 31 | getDoctorById(id: number): Observable { 32 | return this.http.get(`${this.apiUrl}/doctors/${id}`); 33 | } 34 | 35 | updateDoctor(data: DoctorEdit): Observable { 36 | return this.http.put(`${this.apiUrl}/doctors`, data); 37 | } 38 | 39 | deleteDoctor(id: number | undefined): Observable { 40 | return this.http.delete(`${this.apiUrl}/doctors/${id}`); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/shared/components/forms/address-form/address-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject, Input, OnDestroy, OnInit } from '@angular/core'; 2 | import { ControlContainer, FormGroup } from '@angular/forms'; 3 | import { State } from 'app/shared/models/state'; 4 | import { AddressFormService } from './../../../services/address-form.service'; 5 | 6 | @Component({ 7 | selector: 'app-address-form', 8 | templateUrl: './address-form.component.html', 9 | styleUrls: ['./address-form.component.scss'], 10 | viewProviders: [ 11 | { 12 | provide: ControlContainer, 13 | useFactory: () => inject(ControlContainer, { skipSelf: true }), 14 | }, 15 | ], 16 | }) 17 | export class AddressFormComponent implements OnInit, OnDestroy { 18 | @Input({ required: true }) controlKey = ''; 19 | @Input() label = ''; 20 | 21 | parentContainer = inject(ControlContainer); 22 | states = Object.values(State).filter((value) => typeof value === 'string'); 23 | 24 | constructor(private addressFormService: AddressFormService) {} 25 | 26 | ngOnInit(): void { 27 | const addressForm = this.addressFormService.createAddressForm(); 28 | this.parentFormGroup.setControl(this.controlKey, addressForm); 29 | } 30 | 31 | ngOnDestroy() { 32 | this.parentFormGroup.removeControl(this.controlKey); 33 | } 34 | 35 | get parentFormGroup() { 36 | return this.parentContainer.control as FormGroup; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/features/doctors/models/doctor.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'app/shared/models/address'; 2 | import { Contact } from 'app/shared/models/contact'; 3 | import { Specialty } from './specialty'; 4 | 5 | export interface Doctor { 6 | id?: number; 7 | name: string; 8 | crm: string; 9 | specialty: Specialty; 10 | contact: Contact; 11 | address: Address; 12 | profileType?: string; 13 | } 14 | 15 | export interface DoctorList { 16 | id: number; 17 | name: string; 18 | crm: string; 19 | specialty: Specialty; 20 | contact: Contact; 21 | address: Address; 22 | profileType: string; 23 | } 24 | 25 | export interface DoctorEdit { 26 | id: number; 27 | name: string; 28 | contact: { 29 | phone: string; 30 | }; 31 | address: Address; 32 | } 33 | 34 | export interface PaginatedDoctorResponse { 35 | content: DoctorList[]; 36 | pageable: { 37 | pageNumber: number; 38 | pageSize: number; 39 | sort: { 40 | sorted: boolean; 41 | unsorted: boolean; 42 | empty: boolean; 43 | }; 44 | offset: number; 45 | paged: boolean; 46 | unpaged: boolean; 47 | }; 48 | totalPages: number; 49 | totalElements: number; 50 | last: boolean; 51 | size: number; 52 | number: number; 53 | sort: { 54 | sorted: boolean; 55 | unsorted: boolean; 56 | empty: boolean; 57 | }; 58 | numberOfElements: number; 59 | first: boolean; 60 | empty: boolean; 61 | } 62 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["projects/**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@angular-eslint/recommended", 11 | "plugin:@angular-eslint/template/process-inline-templates", 12 | "plugin:prettier/recommended" 13 | ], 14 | "rules": { 15 | "@angular-eslint/directive-selector": [ 16 | "error", 17 | { 18 | "type": "attribute", 19 | "prefix": "app", 20 | "style": "camelCase" 21 | } 22 | ], 23 | "@angular-eslint/component-selector": [ 24 | "error", 25 | { 26 | "type": "element", 27 | "prefix": "app", 28 | "style": "kebab-case" 29 | } 30 | ] 31 | } 32 | }, 33 | { 34 | "files": ["*.html"], 35 | "excludedFiles": ["*inline-template-*.component.html"], 36 | "extends": [ 37 | "plugin:@angular-eslint/template/recommended", 38 | "plugin:@angular-eslint/template/accessibility", 39 | "plugin:prettier/recommended" 40 | ], 41 | "rules": { 42 | "prettier/prettier": [ 43 | "error", 44 | { 45 | "parser": "angular" 46 | } 47 | ] 48 | } 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/app/shared/components/forms/contact-form/contact-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ label }} 4 | 5 | 11 | 17 | 18 | 22 | 28 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /src/app/shared/components/modals/modal-deactivation/modal-deactivation.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Deseja desativar este perfil?

5 |
6 |
7 |
8 | {{ profile.name }} 9 |

10 | {{ profile.specialty }} | CRM {{ profile.crm }}-{{ 11 | profile.address.state 12 | }} 13 |

14 |

{{ profile.cpf | cpf }}

15 |
16 |
17 |
18 |
19 |

20 | Ao desativar este perfil, suas informações ficarão desabilitadas para 21 | pesquisas e futuros agendamentos. 22 |

23 |

24 | Certifique-se que não há consultas agendadas, caso tenha, a desativação 25 | não poderá ser concluída. 26 |

27 |
28 |
29 | 33 | 38 |
39 |
40 | 41 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-list/doctor-list.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | section { 4 | padding: 32px 0; 5 | 6 | input { 7 | border-radius: 5px; 8 | border: 1px solid $secondary-gray; 9 | padding: 14px 16px; 10 | background-color: $primary-white; 11 | font-size: $fs-2; 12 | font-style: normal; 13 | font-weight: $fw-1; 14 | line-height: 19.4px; 15 | width: 100%; 16 | background-image: url('assets/images/lupa.svg'); 17 | background-repeat: no-repeat; 18 | background-position: 98%; 19 | 20 | &::placeholder { 21 | color: $primary-black; 22 | font-size: $fs-2; 23 | font-style: normal; 24 | font-weight: $fw-1; 25 | line-height: 19.4px; 26 | } 27 | 28 | &:focus { 29 | outline: none; 30 | } 31 | } 32 | 33 | .list { 34 | padding: 32px 0 90px; 35 | 36 | .cards { 37 | padding-bottom: 32px; 38 | 39 | &__header { 40 | span { 41 | font-size: $fs-3; 42 | font-weight: $fw-2; 43 | line-height: 21.9px; 44 | margin-right: 10px; 45 | color: $blue-logo; 46 | } 47 | } 48 | } 49 | } 50 | 51 | .footer { 52 | position: fixed; 53 | bottom: 0; 54 | width: 100%; 55 | right: 0; 56 | left: 0; 57 | padding: 20px 16px; 58 | box-shadow: 0px -4px 8px 0px $box-shadow-gray; 59 | background-color: $primary-white; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-list/patient-list.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | section { 4 | padding: 32px 0; 5 | 6 | input { 7 | border-radius: 5px; 8 | border: 1px solid $secondary-gray; 9 | padding: 14px 16px; 10 | background-color: $primary-white; 11 | font-size: $fs-2; 12 | font-style: normal; 13 | font-weight: $fw-1; 14 | line-height: 19.4px; 15 | width: 100%; 16 | background-image: url('assets/images/lupa.svg'); 17 | background-repeat: no-repeat; 18 | background-position: 98%; 19 | 20 | &::placeholder { 21 | color: $primary-black; 22 | font-size: $fs-2; 23 | font-style: normal; 24 | font-weight: $fw-1; 25 | line-height: 19.4px; 26 | } 27 | 28 | &:focus { 29 | outline: none; 30 | } 31 | } 32 | 33 | .list { 34 | padding: 32px 0 90px; 35 | 36 | .cards { 37 | padding-bottom: 32px; 38 | 39 | &__header { 40 | span { 41 | font-size: $fs-3; 42 | font-weight: $fw-2; 43 | line-height: 21.9px; 44 | margin-right: 10px; 45 | color: $blue-logo; 46 | } 47 | } 48 | } 49 | } 50 | 51 | .footer { 52 | position: fixed; 53 | bottom: 0; 54 | width: 100%; 55 | right: 0; 56 | left: 0; 57 | padding: 20px 16px; 58 | box-shadow: 0px -4px 8px 0px $box-shadow-gray; 59 | background-color: $primary-white; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/shared/components/card-profile/card-profile.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | .card { 4 | padding: 5px 8px; 5 | 6 | details { 7 | border-bottom: 1px solid $secondary-gray; 8 | 9 | summary { 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: center; 13 | padding-bottom: 5px; 14 | cursor: pointer; 15 | 16 | h3 { 17 | font-size: $fs-2; 18 | font-weight: $fw-1; 19 | color: $primary-blue; 20 | } 21 | 22 | p { 23 | font-size: $fs-1; 24 | font-weight: $fw-1; 25 | color: $font-gray; 26 | } 27 | } 28 | 29 | div { 30 | display: flex; 31 | flex-direction: column; 32 | font-size: $fs-1; 33 | font-weight: $fw-1; 34 | color: $font-gray; 35 | 36 | &.address { 37 | gap: 0; 38 | padding-top: 8px; 39 | } 40 | 41 | .actions { 42 | display: flex; 43 | flex-direction: row; 44 | gap: 8px; 45 | padding: 14px 0; 46 | 47 | button { 48 | border: 1px solid $primary-blue; 49 | border-radius: 8px; 50 | background-color: $primary-white; 51 | color: $primary-blue; 52 | width: 50%; 53 | padding: 5px 0; 54 | font-size: $fs-2; 55 | font-weight: $fw-1; 56 | } 57 | } 58 | } 59 | } 60 | 61 | details[open] summary::after { 62 | content: ' \25b2'; 63 | } 64 | 65 | details:not([open]) summary::after { 66 | content: ' \25bc'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-edit/patient-edit.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | form { 4 | padding: 20px 0; 5 | margin: 0 auto; 6 | 7 | fieldset { 8 | border: none; 9 | display: flex; 10 | flex-direction: column; 11 | gap: 10px; 12 | 13 | legend { 14 | margin-bottom: 10px; 15 | color: $primary-blue; 16 | font-size: $fs-3; 17 | font-style: normal; 18 | font-weight: $fw-2; 19 | line-height: 21.9px; 20 | } 21 | 22 | input, 23 | select { 24 | border-radius: 5px; 25 | border: 1px solid $secondary-gray; 26 | padding: 14px 16px; 27 | background-color: $primary-white; 28 | font-size: $fs-2; 29 | font-style: normal; 30 | font-weight: $fw-1; 31 | line-height: 19.4px; 32 | width: 100%; 33 | 34 | &.ng-invalid.ng-touched { 35 | border: 2px solid $red-warning; 36 | } 37 | 38 | &::placeholder { 39 | color: $primary-black; 40 | font-size: $fs-2; 41 | font-style: normal; 42 | font-weight: $fw-1; 43 | line-height: 19.4px; 44 | } 45 | 46 | &:focus { 47 | outline: none; 48 | } 49 | 50 | &:disabled { 51 | background-color: $bg-input-disable; 52 | color: $font-gray; 53 | } 54 | 55 | & option { 56 | color: $primary-black; 57 | font-size: $fs-2; 58 | font-style: normal; 59 | font-weight: $fw-1; 60 | line-height: 19.4px; 61 | } 62 | } 63 | } 64 | 65 | & .container-button { 66 | display: flex; 67 | flex-direction: column; 68 | gap: 12px; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-register/patient-register.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | form { 4 | padding: 20px 0; 5 | margin: 0 auto; 6 | 7 | fieldset { 8 | border: none; 9 | display: flex; 10 | flex-direction: column; 11 | gap: 10px; 12 | 13 | legend { 14 | margin-bottom: 10px; 15 | color: $primary-blue; 16 | font-size: $fs-3; 17 | font-style: normal; 18 | font-weight: $fw-2; 19 | line-height: 21.9px; 20 | } 21 | 22 | input, 23 | select { 24 | border-radius: 5px; 25 | border: 1px solid $secondary-gray; 26 | padding: 14px 16px; 27 | background-color: $primary-white; 28 | font-size: $fs-2; 29 | font-style: normal; 30 | font-weight: $fw-1; 31 | line-height: 19.4px; 32 | width: 100%; 33 | 34 | &.ng-invalid.ng-touched { 35 | border: 2px solid $red-warning; 36 | } 37 | 38 | &::placeholder { 39 | color: $primary-black; 40 | font-size: $fs-2; 41 | font-style: normal; 42 | font-weight: $fw-1; 43 | line-height: 19.4px; 44 | } 45 | 46 | &:focus { 47 | outline: none; 48 | } 49 | 50 | &:disabled { 51 | background-color: $bg-input-disable; 52 | color: $font-gray; 53 | } 54 | 55 | & option { 56 | color: $primary-black; 57 | font-size: $fs-2; 58 | font-style: normal; 59 | font-weight: $fw-1; 60 | line-height: 19.4px; 61 | } 62 | } 63 | } 64 | 65 | & .container-button { 66 | display: flex; 67 | flex-direction: column; 68 | gap: 12px; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-edit/patient-edit.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | Paciente 6 | 7 | 12 | 17 | 18 | 19 | 24 | 27 |
28 | 29 | 30 | 31 | 32 |
33 | 37 | 42 |
43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-register/patient-register.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | Paciente 6 | 7 | 12 | 17 | 18 | 19 | 24 | 27 |
28 | 29 | 30 | 31 | 32 |
33 | 37 | 42 |
43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /src/app/shared/components/modals/modal-deactivation/modal-deactivation.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | dialog { 4 | padding: 22px; 5 | width: 100%; 6 | max-width: 360px; 7 | text-align: center; 8 | border: none; 9 | border-radius: 10px 10px 0 0; 10 | margin-inline: auto; 11 | top: auto; 12 | 13 | &::backdrop { 14 | background: rgba(0, 0, 0, 0.7); 15 | } 16 | 17 | .dialog__header { 18 | display: flex; 19 | flex-direction: column; 20 | gap: 10px; 21 | margin-bottom: 28px; 22 | 23 | img { 24 | background-color: $primary-blue; 25 | border-radius: 5px; 26 | width: 110px; 27 | margin-inline: auto; 28 | } 29 | 30 | h3 { 31 | color: $primary-blue; 32 | font-size: $fs-4; 33 | font-weight: 700; 34 | } 35 | 36 | &-info { 37 | margin-inline: auto; 38 | text-align: initial; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | margin-left: 25px; 43 | 44 | .bar { 45 | width: 10px; 46 | background-color: $secondary-blue; 47 | height: 40px; 48 | margin-right: 10px; 49 | } 50 | 51 | span { 52 | font-weight: $fw-1; 53 | color: $primary-blue; 54 | font-size: $fs-2; 55 | } 56 | 57 | p { 58 | font-weight: $fw-1; 59 | color: $font-gray; 60 | font-size: $fs-1; 61 | } 62 | } 63 | } 64 | 65 | .dialog__text { 66 | display: flex; 67 | flex-direction: column; 68 | gap: 16px; 69 | margin-bottom: 28px; 70 | 71 | p { 72 | color: $primary-black; 73 | font-size: $fs-3; 74 | line-height: 1.3; 75 | } 76 | } 77 | 78 | .dialog__buttons { 79 | display: flex; 80 | flex-direction: column; 81 | gap: 10px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/app/shared/components/modals/modal-deactivation/modal-deactivation.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core'; 2 | import { ProfileDeactivationService } from './../../../services/profile-deactivation.service'; 3 | 4 | import { ProfileCard } from 'app/shared/models/profile-card'; 5 | 6 | @Component({ 7 | selector: 'app-modal-deactivation', 8 | templateUrl: './modal-deactivation.component.html', 9 | styleUrls: ['./modal-deactivation.component.scss'], 10 | }) 11 | export class ModalDeactivationComponent { 12 | @Input() profile!: ProfileCard; 13 | modalTitle!: string; 14 | modalText!: string; 15 | 16 | @ViewChild('modalInfo') modalInfo!: ElementRef; 17 | @ViewChild('modalDeactivation') deactivationModal!: ElementRef; 18 | 19 | nativeElement: HTMLElement; 20 | 21 | constructor( 22 | private element: ElementRef, 23 | private profileDeactivationService: ProfileDeactivationService, 24 | ) { 25 | this.nativeElement = element.nativeElement; 26 | } 27 | 28 | deactivate(): void { 29 | this.profileDeactivationService 30 | .deactivateProfile(this.profile.id, this.profile.profileType) 31 | .subscribe({ 32 | next: () => this.handleSuccess(), 33 | error: () => this.handleError(), 34 | }); 35 | } 36 | 37 | handleSuccess() { 38 | this.closeModal(); 39 | this.modalText = 'Perfil desativado com sucesso!'; 40 | this.openModalInfo(); 41 | } 42 | 43 | handleError(): void { 44 | this.modalTitle = 'Não foi possível desativar esse perfil'; 45 | this.modalText = 'Ocorreu um erro,tente novamente mais tarde!'; 46 | this.closeModal(); 47 | this.openModalInfo(); 48 | } 49 | 50 | openModalInfo(): void { 51 | this.modalInfo.nativeElement.firstChild.showModal(); 52 | } 53 | 54 | closeModal(): void { 55 | this.deactivationModal.nativeElement.close(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-register/patient-register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, ViewChild } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { Patient } from '../../models/patient'; 5 | import { PatientService } from '../../services/patient.service'; 6 | 7 | @Component({ 8 | selector: 'app-patient-register', 9 | templateUrl: './patient-register.component.html', 10 | styleUrls: ['./patient-register.component.scss'], 11 | }) 12 | export class PatientRegisterComponent { 13 | @ViewChild('modal') modalWarning!: ElementRef; 14 | modalText = 'Cadastro realizado com sucesso!'; 15 | 16 | constructor( 17 | private patientService: PatientService, 18 | private router: Router, 19 | ) {} 20 | 21 | form = new FormGroup({ 22 | name: new FormControl('', [Validators.required, Validators.minLength(5)]), 23 | cpf: new FormControl('', [ 24 | Validators.required, 25 | Validators.pattern(/^\d{11}$/), 26 | ]), 27 | }); 28 | 29 | onSubmit() { 30 | if (this.form.valid) { 31 | const data = this.form.value as Patient; 32 | this.patientService.registerPatient(data).subscribe({ 33 | next: () => { 34 | this.openModal(); 35 | setTimeout(() => { 36 | this.closeModal(); 37 | this.router.navigate(['patients']); 38 | }, 3000); 39 | }, 40 | error: (err) => { 41 | console.log(err); 42 | }, 43 | }); 44 | } 45 | 46 | console.error('Formulário inválido!'); 47 | } 48 | 49 | btnState() { 50 | return this.form.valid ? 'primary' : 'disabled'; 51 | } 52 | 53 | openModal(): void { 54 | this.modalWarning.nativeElement.firstChild.showModal(); 55 | } 56 | 57 | closeModal(): void { 58 | this.modalWarning.nativeElement.firstChild.close(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-list/patient-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { ProfileCard } from 'app/shared/models/profile-card'; 3 | import { 4 | debounceTime, 5 | map, 6 | Observable, 7 | of, 8 | Subject, 9 | Subscription, 10 | switchMap, 11 | tap, 12 | } from 'rxjs'; 13 | import { PatientService } from '../../services/patient.service'; 14 | import { ProfileListingService } from './../../../../shared/services/profile-listing.service'; 15 | 16 | @Component({ 17 | selector: 'app-patient-list', 18 | templateUrl: './patient-list.component.html', 19 | styleUrls: ['./patient-list.component.scss'], 20 | }) 21 | export class PatientListComponent implements OnInit, OnDestroy { 22 | patientList$!: Observable<{ letter: string; profiles: ProfileCard[] }[]>; 23 | filter!: string; 24 | private searchTerm$ = new Subject(); 25 | private searchTermSubscription!: Subscription; 26 | 27 | constructor( 28 | private patientService: PatientService, 29 | private profileListingService: ProfileListingService, 30 | ) { 31 | this.searchTermSubscription = this.searchTerm$ 32 | .pipe(debounceTime(300)) 33 | .subscribe((value) => { 34 | this.filter = value; 35 | }); 36 | } 37 | 38 | ngOnInit(): void { 39 | this.patientList$ = this.profileListingService.getUpdatedList().pipe( 40 | switchMap((cachedList) => 41 | cachedList.length > 0 42 | ? of(cachedList) 43 | : this.patientService 44 | .getPatientList() 45 | .pipe( 46 | tap((list) => this.profileListingService.setUpdatedList(list)), 47 | ), 48 | ), 49 | map((list) => this.profileListingService.listJoinedByLetter(list)), 50 | ); 51 | } 52 | 53 | updateFilter(filterValue: string): void { 54 | this.searchTerm$.next(filterValue); 55 | } 56 | 57 | ngOnDestroy(): void { 58 | this.searchTermSubscription.unsubscribe(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voll-med", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "lint:fix": "eslint --fix", 12 | "prettier": "npx prettier --write .", 13 | "prepare": "husky install" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/animations": "^16.2.0", 18 | "@angular/common": "^16.2.0", 19 | "@angular/compiler": "^16.2.0", 20 | "@angular/core": "^16.2.0", 21 | "@angular/forms": "^16.2.0", 22 | "@angular/platform-browser": "^16.2.0", 23 | "@angular/platform-browser-dynamic": "^16.2.0", 24 | "@angular/router": "^16.2.0", 25 | "rxjs": "~7.8.0", 26 | "tslib": "^2.3.0", 27 | "zone.js": "~0.13.0" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^16.2.10", 31 | "@angular-eslint/builder": "16.3.1", 32 | "@angular-eslint/eslint-plugin": "16.3.1", 33 | "@angular-eslint/eslint-plugin-template": "16.3.1", 34 | "@angular-eslint/schematics": "16.3.1", 35 | "@angular-eslint/template-parser": "16.3.1", 36 | "@angular/cli": "^16.2.10", 37 | "@angular/compiler-cli": "^16.2.0", 38 | "@commitlint/cli": "^18.4.3", 39 | "@commitlint/config-conventional": "^18.4.3", 40 | "@types/jasmine": "~4.3.0", 41 | "@typescript-eslint/eslint-plugin": "5.62.0", 42 | "@typescript-eslint/parser": "5.62.0", 43 | "eslint": "^8.51.0", 44 | "eslint-config-prettier": "^9.0.0", 45 | "eslint-plugin-prettier": "^5.0.1", 46 | "husky": "^8.0.3", 47 | "jasmine-core": "~4.6.0", 48 | "karma": "~6.4.0", 49 | "karma-chrome-launcher": "~3.2.0", 50 | "karma-coverage": "~2.2.0", 51 | "karma-jasmine": "~5.1.0", 52 | "karma-jasmine-html-reporter": "~2.1.0", 53 | "lint-staged": "^15.1.0", 54 | "prettier": "^3.1.0", 55 | "prettier-eslint": "^16.1.2", 56 | "typescript": "~5.1.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-list/doctor-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | 3 | import { ProfileCard } from 'app/shared/models/profile-card'; 4 | import { ProfileListingService } from 'app/shared/services/profile-listing.service'; 5 | import { 6 | Observable, 7 | Subject, 8 | Subscription, 9 | debounceTime, 10 | map, 11 | of, 12 | switchMap, 13 | tap, 14 | } from 'rxjs'; 15 | import { DoctorListingService } from '../../services/doctor-listing.service'; 16 | import { DoctorService } from '../../services/doctor.service'; 17 | 18 | @Component({ 19 | selector: 'app-doctor-list', 20 | templateUrl: './doctor-list.component.html', 21 | styleUrls: ['./doctor-list.component.scss'], 22 | }) 23 | export class DoctorListComponent implements OnInit, OnDestroy { 24 | doctorList$!: Observable<{ letter: string; profiles: ProfileCard[] }[]>; 25 | filter!: string; 26 | private searchTerm$ = new Subject(); 27 | private searchTermSubscription!: Subscription; 28 | 29 | constructor( 30 | private doctorService: DoctorService, 31 | private doctorListingService: DoctorListingService, 32 | private profileListingService: ProfileListingService, 33 | ) { 34 | this.searchTermSubscription = this.searchTerm$ 35 | .pipe(debounceTime(300)) 36 | .subscribe((value) => { 37 | this.filter = value; 38 | }); 39 | } 40 | 41 | ngOnInit(): void { 42 | this.doctorList$ = this.doctorListingService.getUpdatedList().pipe( 43 | switchMap((cachedList) => 44 | cachedList.length > 0 45 | ? of(cachedList) 46 | : this.doctorService 47 | .getDoctorList() 48 | .pipe( 49 | tap((list) => this.doctorListingService.setUpdatedList(list)), 50 | ), 51 | ), 52 | map((list) => this.profileListingService.listJoinedByLetter(list)), 53 | ); 54 | } 55 | 56 | updateFilter(filterValue: string): void { 57 | this.searchTerm$.next(filterValue); 58 | } 59 | 60 | ngOnDestroy(): void { 61 | this.searchTermSubscription.unsubscribe(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { ReactiveFormsModule } from '@angular/forms'; 5 | import { RouterModule } from '@angular/router'; 6 | import { ButtonComponent } from './components/button/button.component'; 7 | import { CardProfileComponent } from './components/card-profile/card-profile.component'; 8 | import { ContainerComponent } from './components/container/container.component'; 9 | import { ErrorMessageComponent } from './components/error-message/error-message.component'; 10 | import { AddressFormComponent } from './components/forms/address-form/address-form.component'; 11 | import { ContactFormComponent } from './components/forms/contact-form/contact-form.component'; 12 | import { HeaderComponent } from './components/header/header.component'; 13 | import { ModalDeactivationComponent } from './components/modals/modal-deactivation/modal-deactivation.component'; 14 | import { ModalInfoComponent } from './components/modals/modal-info/modal-info.component'; 15 | import { CpfPipe } from './pipes/cpf.pipe'; 16 | import { FilterByName } from './pipes/filter-by-name.pipe'; 17 | import { PhonePipe } from './pipes/phone.pipe'; 18 | import { PostalCodePipe } from './pipes/postal-code.pipe'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | ButtonComponent, 23 | ContainerComponent, 24 | HeaderComponent, 25 | ErrorMessageComponent, 26 | ModalInfoComponent, 27 | ModalDeactivationComponent, 28 | ContactFormComponent, 29 | AddressFormComponent, 30 | CardProfileComponent, 31 | CpfPipe, 32 | PhonePipe, 33 | PostalCodePipe, 34 | FilterByName, 35 | ], 36 | imports: [CommonModule, RouterModule, ReactiveFormsModule], 37 | exports: [ 38 | ButtonComponent, 39 | ContainerComponent, 40 | HeaderComponent, 41 | ErrorMessageComponent, 42 | ModalInfoComponent, 43 | ModalDeactivationComponent, 44 | ContactFormComponent, 45 | AddressFormComponent, 46 | CardProfileComponent, 47 | CpfPipe, 48 | PhonePipe, 49 | PostalCodePipe, 50 | FilterByName, 51 | ], 52 | }) 53 | export class SharedModule {} 54 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-edit/doctor-edit.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | form { 4 | padding: 20px 0; 5 | margin: 0 auto; 6 | 7 | fieldset { 8 | border: none; 9 | display: flex; 10 | flex-direction: column; 11 | gap: 10px; 12 | 13 | legend { 14 | margin-bottom: 10px; 15 | color: $primary-blue; 16 | font-size: $fs-3; 17 | font-style: normal; 18 | font-weight: $fw-2; 19 | line-height: 21.9px; 20 | } 21 | 22 | input, 23 | select { 24 | border-radius: 5px; 25 | border: 1px solid $secondary-gray; 26 | padding: 14px 16px; 27 | background-color: $primary-white; 28 | font-size: $fs-2; 29 | font-style: normal; 30 | font-weight: $fw-1; 31 | line-height: 19.4px; 32 | width: 100%; 33 | 34 | &.ng-invalid.ng-touched { 35 | border: 2px solid $red-warning; 36 | } 37 | 38 | &::placeholder { 39 | color: $primary-black; 40 | font-size: $fs-2; 41 | font-style: normal; 42 | font-weight: $fw-1; 43 | line-height: 19.4px; 44 | } 45 | 46 | &:focus { 47 | outline: none; 48 | } 49 | 50 | &:disabled { 51 | background-color: $bg-input-disable; 52 | color: $font-gray; 53 | } 54 | 55 | & option { 56 | color: $primary-black; 57 | font-size: $fs-2; 58 | font-style: normal; 59 | font-weight: $fw-1; 60 | line-height: 19.4px; 61 | } 62 | } 63 | 64 | select { 65 | appearance: none; 66 | -webkit-appearance: none; 67 | -moz-appearance: none; 68 | background: transparent; 69 | background: url('assets/images/Dropdown.svg'); 70 | background-repeat: no-repeat; 71 | background-position-x: 95%; 72 | background-position-y: 50%; 73 | } 74 | 75 | & .container-input { 76 | display: flex; 77 | justify-content: center; 78 | gap: 4%; 79 | 80 | & > * { 81 | width: 50%; 82 | } 83 | 84 | & > div { 85 | display: flex; 86 | flex-direction: column; 87 | gap: 8px; 88 | } 89 | } 90 | } 91 | 92 | & .container-button { 93 | display: flex; 94 | flex-direction: column; 95 | gap: 12px; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-register/doctor-register.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | form { 4 | padding: 20px 0; 5 | margin: 0 auto; 6 | 7 | fieldset { 8 | border: none; 9 | display: flex; 10 | flex-direction: column; 11 | gap: 10px; 12 | 13 | legend { 14 | margin-bottom: 10px; 15 | color: $primary-blue; 16 | font-size: $fs-3; 17 | font-style: normal; 18 | font-weight: $fw-2; 19 | line-height: 21.9px; 20 | } 21 | 22 | input, 23 | select { 24 | border-radius: 5px; 25 | border: 1px solid $secondary-gray; 26 | padding: 14px 16px; 27 | background-color: $primary-white; 28 | font-size: $fs-2; 29 | font-style: normal; 30 | font-weight: $fw-1; 31 | line-height: 19.4px; 32 | width: 100%; 33 | 34 | &.ng-invalid.ng-touched { 35 | border: 2px solid $red-warning; 36 | } 37 | 38 | &::placeholder { 39 | color: $primary-black; 40 | font-size: $fs-2; 41 | font-style: normal; 42 | font-weight: $fw-1; 43 | line-height: 19.4px; 44 | } 45 | 46 | &:focus { 47 | outline: none; 48 | } 49 | 50 | &:disabled { 51 | background-color: $bg-input-disable; 52 | color: $font-gray; 53 | } 54 | 55 | & option { 56 | color: $primary-black; 57 | font-size: $fs-2; 58 | font-style: normal; 59 | font-weight: $fw-1; 60 | line-height: 19.4px; 61 | } 62 | } 63 | 64 | select { 65 | appearance: none; 66 | -webkit-appearance: none; 67 | -moz-appearance: none; 68 | background: transparent; 69 | background: url('assets/images/Dropdown.svg'); 70 | background-repeat: no-repeat; 71 | background-position-x: 95%; 72 | background-position-y: 50%; 73 | } 74 | 75 | & .container-input { 76 | display: flex; 77 | justify-content: center; 78 | gap: 4%; 79 | 80 | & > * { 81 | width: 50%; 82 | } 83 | 84 | & > div { 85 | display: flex; 86 | flex-direction: column; 87 | gap: 8px; 88 | } 89 | } 90 | } 91 | 92 | & .container-button { 93 | display: flex; 94 | flex-direction: column; 95 | gap: 12px; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/app/shared/components/forms/address-form/address-form.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | section { 4 | padding: 20px 0; 5 | margin: 0 auto; 6 | display: flex; 7 | flex-direction: column; 8 | gap: 24px; 9 | 10 | fieldset { 11 | border: none; 12 | display: flex; 13 | flex-direction: column; 14 | gap: 10px; 15 | 16 | legend { 17 | margin-bottom: 10px; 18 | color: $primary-blue; 19 | font-size: $fs-3; 20 | font-style: normal; 21 | font-weight: $fw-2; 22 | line-height: 21.9px; 23 | } 24 | 25 | input, 26 | select { 27 | border-radius: 5px; 28 | border: 1px solid $secondary-gray; 29 | padding: 14px 16px; 30 | background-color: $primary-white; 31 | font-size: $fs-2; 32 | font-style: normal; 33 | font-weight: $fw-1; 34 | line-height: 19.4px; 35 | width: 100%; 36 | 37 | &.ng-invalid.ng-touched { 38 | border: 2px solid $red-warning; 39 | } 40 | 41 | &::placeholder { 42 | color: $primary-black; 43 | font-size: $fs-2; 44 | font-style: normal; 45 | font-weight: $fw-1; 46 | line-height: 19.4px; 47 | } 48 | 49 | &:focus { 50 | outline: none; 51 | } 52 | 53 | &:disabled { 54 | background-color: $bg-input-disable; 55 | color: $font-gray; 56 | } 57 | 58 | & option { 59 | color: $primary-black; 60 | font-size: $fs-2; 61 | font-style: normal; 62 | font-weight: $fw-1; 63 | line-height: 19.4px; 64 | } 65 | } 66 | 67 | select { 68 | appearance: none; 69 | -webkit-appearance: none; 70 | -moz-appearance: none; 71 | background: transparent; 72 | background: url('assets/images/Dropdown.svg'); 73 | background-repeat: no-repeat; 74 | background-position-x: 95%; 75 | background-position-y: 50%; 76 | } 77 | 78 | & .container-input { 79 | display: flex; 80 | justify-content: center; 81 | gap: 4%; 82 | 83 | & > * { 84 | width: 50%; 85 | } 86 | 87 | & > div { 88 | display: flex; 89 | flex-direction: column; 90 | gap: 8px; 91 | } 92 | } 93 | } 94 | 95 | & .container-button { 96 | display: flex; 97 | flex-direction: column; 98 | gap: 12px; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/app/shared/components/forms/contact-form/contact-form.component.scss: -------------------------------------------------------------------------------- 1 | @import 'assets/scss/_base.scss'; 2 | 3 | section { 4 | padding: 20px 0 0; 5 | margin: 0 auto; 6 | display: flex; 7 | flex-direction: column; 8 | gap: 24px; 9 | 10 | fieldset { 11 | border: none; 12 | display: flex; 13 | flex-direction: column; 14 | gap: 10px; 15 | 16 | legend { 17 | margin-bottom: 10px; 18 | color: $primary-blue; 19 | font-size: $fs-3; 20 | font-style: normal; 21 | font-weight: $fw-2; 22 | line-height: 21.9px; 23 | } 24 | 25 | input, 26 | select { 27 | border-radius: 5px; 28 | border: 1px solid $secondary-gray; 29 | padding: 14px 16px; 30 | background-color: $primary-white; 31 | font-size: $fs-2; 32 | font-style: normal; 33 | font-weight: $fw-1; 34 | line-height: 19.4px; 35 | width: 100%; 36 | 37 | &.ng-invalid.ng-touched { 38 | border: 2px solid $red-warning; 39 | } 40 | 41 | &::placeholder { 42 | color: $primary-black; 43 | font-size: $fs-2; 44 | font-style: normal; 45 | font-weight: $fw-1; 46 | line-height: 19.4px; 47 | } 48 | 49 | &:focus { 50 | outline: none; 51 | } 52 | 53 | &:disabled { 54 | background-color: $bg-input-disable; 55 | color: $font-gray; 56 | } 57 | 58 | & option { 59 | color: $primary-black; 60 | font-size: $fs-2; 61 | font-style: normal; 62 | font-weight: $fw-1; 63 | line-height: 19.4px; 64 | } 65 | } 66 | 67 | select { 68 | appearance: none; 69 | -webkit-appearance: none; 70 | -moz-appearance: none; 71 | background: transparent; 72 | background: url('assets/images/Dropdown.svg'); 73 | background-repeat: no-repeat; 74 | background-position-x: 95%; 75 | background-position-y: 50%; 76 | } 77 | 78 | & .container-input { 79 | display: flex; 80 | justify-content: center; 81 | gap: 4%; 82 | 83 | & > * { 84 | width: 50%; 85 | } 86 | 87 | & > div { 88 | display: flex; 89 | flex-direction: column; 90 | gap: 8px; 91 | } 92 | } 93 | } 94 | 95 | & .container-button { 96 | display: flex; 97 | flex-direction: column; 98 | gap: 12px; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/app/shared/components/forms/address-form/address-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ label }} 4 | 5 | 11 | 12 |
13 | 14 | 18 |
19 | 20 | 21 | 27 | 28 |
29 |
30 | 36 | 43 |
44 | 45 |
46 | 47 | 54 | 61 |
62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-edit/doctor-edit.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | Profissional 6 | 7 | 12 | 17 | 18 |
19 |
20 | 26 | 32 |
33 | 34 |
35 | 36 | 41 | 46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 |
54 | 58 | 63 |
64 | 65 |
66 | 67 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-register/doctor-register.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | Profissional 7 | 8 | 13 | 18 | 19 |
20 |
21 | 27 | 33 |
34 | 35 |
36 | 37 | 42 | 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 |
55 | 59 | 64 |
65 | 66 |
67 | 68 | -------------------------------------------------------------------------------- /src/assets/images/Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-register/doctor-register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, ViewChild } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { switchMap, tap } from 'rxjs'; 5 | import { Doctor } from '../../models/doctor'; 6 | import { Specialty } from '../../models/specialty'; 7 | import { DoctorListingService } from '../../services/doctor-listing.service'; 8 | import { DoctorService } from '../../services/doctor.service'; 9 | 10 | @Component({ 11 | selector: 'app-doctor-register', 12 | templateUrl: './doctor-register.component.html', 13 | styleUrls: ['./doctor-register.component.scss'], 14 | }) 15 | export class DoctorRegisterComponent { 16 | modalText = 'Cadastro realizado com sucesso!'; 17 | @ViewChild('modal') modalInfo!: ElementRef; 18 | 19 | specialties = Object.values(Specialty).filter( 20 | (value) => typeof value === 'string', 21 | ); 22 | 23 | constructor( 24 | private doctorService: DoctorService, 25 | private doctorListingService: DoctorListingService, 26 | private router: Router, 27 | ) {} 28 | 29 | form = new FormGroup({ 30 | name: new FormControl('', [Validators.required, Validators.minLength(5)]), 31 | specialty: new FormControl('', [Validators.required]), 32 | crm: new FormControl('', [ 33 | Validators.required, 34 | Validators.pattern(/^\d{4,6}$/), 35 | ]), 36 | }); 37 | 38 | onSubmit() { 39 | const data = this.form.value as unknown as Doctor; 40 | 41 | this.doctorService 42 | .registerDoctor(data) 43 | .pipe( 44 | switchMap(() => this.doctorService.getDoctorList()), 45 | tap((doctors) => this.doctorListingService.setUpdatedList(doctors)), 46 | ) 47 | .subscribe({ 48 | next: () => { 49 | this.openModal(); 50 | setTimeout(() => { 51 | this.closeModal(); 52 | }, 3000); 53 | this.form.reset({ specialty: '' }); 54 | }, 55 | error: (error) => { 56 | this.modalText = 'Não foi possível realizar o cadastro!'; 57 | this.openModal(); 58 | console.log('Erro ao cadastrar', error); 59 | }, 60 | }); 61 | } 62 | 63 | openModal(): void { 64 | this.modalInfo.nativeElement.firstChild.showModal(); 65 | } 66 | 67 | closeModal(): void { 68 | this.modalInfo.nativeElement.firstChild.close(); 69 | } 70 | 71 | btnState(): 'primary' | 'secondary' | 'disabled' { 72 | return this.form.valid ? 'primary' : 'disabled'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/assets/images/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/doctor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/person.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/images/Logo-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/features/patients/pages/patient-edit/patient-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { AddressFormService } from 'app/shared/services/address-form.service'; 5 | import { ProfileListingService } from 'app/shared/services/profile-listing.service'; 6 | import { switchMap, tap } from 'rxjs'; 7 | import { PatientEdit } from '../../models/patient'; 8 | import { ContactFormService } from './../../../../shared/services/contact-form.service'; 9 | import { PatientService } from './../../services/patient.service'; 10 | 11 | @Component({ 12 | selector: 'app-patient-edit', 13 | templateUrl: './patient-edit.component.html', 14 | styleUrls: ['./patient-edit.component.scss'], 15 | }) 16 | export class PatientEditComponent implements OnInit { 17 | modalTitle!: string; 18 | modalText!: string; 19 | patientId!: number; 20 | 21 | form: FormGroup = new FormGroup({ 22 | name: new FormControl(), 23 | cpf: new FormControl(), 24 | }); 25 | 26 | @ViewChild('modal') modalInfo!: ElementRef; 27 | 28 | constructor( 29 | private patientService: PatientService, 30 | private profileListingService: ProfileListingService, 31 | private contactFormService: ContactFormService, 32 | private addressFormService: AddressFormService, 33 | private route: ActivatedRoute, 34 | ) {} 35 | 36 | ngOnInit(): void { 37 | this.patientId = this.route.snapshot.params['id']; 38 | 39 | this.patientService.getPatientById(this.patientId).subscribe((patient) => { 40 | this.form = new FormGroup({ 41 | name: new FormControl(patient.name, [ 42 | Validators.required, 43 | Validators.minLength(5), 44 | ]), 45 | cpf: new FormControl(patient.cpf, [ 46 | Validators.required, 47 | Validators.pattern(/^\d{11}$/), 48 | ]), 49 | contact: this.contactFormService.createContactForm(patient.contact), 50 | address: this.addressFormService.createAddressForm(patient.address), 51 | }); 52 | this.form.get('cpf')?.disable(); 53 | this.form.get('contact')?.get('email')?.disable(); 54 | }); 55 | } 56 | 57 | onSubmit() { 58 | if (this.form.valid && this.form.dirty) { 59 | const updatedData: PatientEdit = { 60 | id: this.patientId, 61 | ...this.form.value, 62 | }; 63 | 64 | this.patientService 65 | .updatePatient(updatedData) 66 | .pipe( 67 | switchMap(() => this.patientService.getPatientList()), 68 | tap((patients) => 69 | this.profileListingService.setUpdatedList(patients), 70 | ), 71 | ) 72 | .subscribe({ 73 | next: () => { 74 | this.modalText = 'Dados atualizados com sucesso!'; 75 | this.openModal(); 76 | }, 77 | error: () => { 78 | this.modalTitle = 'Não foi possível atualizar esse perfil'; 79 | this.modalText = 'Ocorreu um erro,tente novamente mais tarde!'; 80 | this.openModal(); 81 | }, 82 | }); 83 | } 84 | } 85 | 86 | openModal(): void { 87 | this.modalInfo.nativeElement.firstChild.showModal(); 88 | } 89 | 90 | closeModal(): void { 91 | this.modalInfo.nativeElement.firstChild.close(); 92 | } 93 | 94 | btnState(): 'primary' | 'disabled' { 95 | return this.form.valid ? 'primary' : 'disabled'; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "VollMed": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser-esbuild", 19 | "options": { 20 | "outputPath": "dist/voll-med", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": ["zone.js"], 24 | "tsConfig": "tsconfig.app.json", 25 | "inlineStyleLanguage": "scss", 26 | "assets": ["src/favicon.ico", "src/assets"], 27 | "styles": ["src/styles.scss"], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "budgets": [ 33 | { 34 | "type": "initial", 35 | "maximumWarning": "500kb", 36 | "maximumError": "1mb" 37 | }, 38 | { 39 | "type": "anyComponentStyle", 40 | "maximumWarning": "2kb", 41 | "maximumError": "4kb" 42 | } 43 | ], 44 | "outputHashing": "all" 45 | }, 46 | "development": { 47 | "buildOptimizer": false, 48 | "optimization": false, 49 | "vendorChunk": true, 50 | "extractLicenses": false, 51 | "sourceMap": true, 52 | "namedChunks": true, 53 | "fileReplacements": [ 54 | { 55 | "replace": "src/environments/environment.ts", 56 | "with": "src/environments/environment.development.ts" 57 | } 58 | ] 59 | } 60 | }, 61 | "defaultConfiguration": "production" 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "configurations": { 66 | "production": { 67 | "browserTarget": "VollMed:build:production" 68 | }, 69 | "development": { 70 | "browserTarget": "VollMed:build:development" 71 | } 72 | }, 73 | "defaultConfiguration": "development" 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "VollMed:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "polyfills": ["zone.js", "zone.js/testing"], 85 | "tsConfig": "tsconfig.spec.json", 86 | "inlineStyleLanguage": "scss", 87 | "assets": ["src/favicon.ico", "src/assets"], 88 | "styles": ["src/styles.scss"], 89 | "scripts": [] 90 | } 91 | }, 92 | "lint": { 93 | "builder": "@angular-eslint/builder:lint", 94 | "options": { 95 | "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] 96 | } 97 | } 98 | } 99 | } 100 | }, 101 | "cli": { 102 | "schematicCollections": ["@angular-eslint/schematics"] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/app/features/doctors/pages/doctor-edit/doctor-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { AddressFormService } from 'app/shared/services/address-form.service'; 5 | import { ContactFormService } from 'app/shared/services/contact-form.service'; 6 | import { switchMap, tap } from 'rxjs'; 7 | import { DoctorEdit } from '../../models/doctor'; 8 | import { Specialty } from '../../models/specialty'; 9 | import { DoctorService } from '../../services/doctor.service'; 10 | import { DoctorListingService } from './../../services/doctor-listing.service'; 11 | 12 | @Component({ 13 | selector: 'app-doctor-edit', 14 | templateUrl: './doctor-edit.component.html', 15 | styleUrls: ['./doctor-edit.component.scss'], 16 | }) 17 | export class DoctorEditComponent implements OnInit { 18 | modalTitle!: string; 19 | modalText!: string; 20 | doctorId!: number; 21 | 22 | specialties = Object.values(Specialty).filter( 23 | (value) => typeof value === 'string', 24 | ); 25 | 26 | form: FormGroup = new FormGroup({ 27 | name: new FormControl(), 28 | specialty: new FormControl(), 29 | crm: new FormControl(), 30 | }); 31 | 32 | @ViewChild('modal') modalInfo!: ElementRef; 33 | 34 | constructor( 35 | private doctorService: DoctorService, 36 | private route: ActivatedRoute, 37 | private contactFormService: ContactFormService, 38 | private addressFormService: AddressFormService, 39 | private doctorListingService: DoctorListingService, 40 | ) {} 41 | 42 | ngOnInit(): void { 43 | this.doctorId = this.route.snapshot.params['id']; 44 | 45 | this.doctorService.getDoctorById(this.doctorId).subscribe((doctor) => { 46 | this.form = new FormGroup({ 47 | name: new FormControl(doctor.name, [ 48 | Validators.required, 49 | Validators.minLength(5), 50 | ]), 51 | specialty: new FormControl(doctor.specialty, [Validators.required]), 52 | crm: new FormControl(doctor.crm, [ 53 | Validators.required, 54 | Validators.pattern(/^\d{4,6}$/), 55 | ]), 56 | contact: this.contactFormService.createContactForm(doctor.contact), 57 | address: this.addressFormService.createAddressForm(doctor.address), 58 | }); 59 | this.form.get('specialty')?.disable(); 60 | this.form.get('crm')?.disable(); 61 | this.form.get('contact')?.get('email')?.disable(); 62 | }); 63 | } 64 | 65 | onSubmit() { 66 | if (this.form.valid && this.form.dirty) { 67 | const updateData: DoctorEdit = { 68 | id: this.doctorId, 69 | ...this.form.value, 70 | }; 71 | 72 | this.doctorService 73 | .updateDoctor(updateData) 74 | .pipe( 75 | switchMap(() => this.doctorService.getDoctorList()), 76 | tap((doctorsList) => 77 | this.doctorListingService.setUpdatedList(doctorsList), 78 | ), 79 | ) 80 | .subscribe({ 81 | next: () => { 82 | this.modalText = 'Dados atualizados com sucesso!'; 83 | this.openModal(); 84 | }, 85 | error: () => { 86 | this.modalTitle = 'Não foi possível atualizar esse perfil'; 87 | this.modalText = 'Ocorreu um erro,tente novamente mais tarde!'; 88 | this.openModal(); 89 | }, 90 | }); 91 | } 92 | } 93 | 94 | openModal(): void { 95 | this.modalInfo.nativeElement.firstChild.showModal(); 96 | } 97 | 98 | closeModal(): void { 99 | this.modalInfo.nativeElement.firstChild.close(); 100 | } 101 | 102 | btnState(): 'primary' | 'disabled' { 103 | return this.form.valid ? 'primary' : 'disabled'; 104 | } 105 | } 106 | --------------------------------------------------------------------------------