Commit 03d7bc2d authored by Almouhannad's avatar Almouhannad

(F) Add create visit

parent 85e98ac3
...@@ -10,7 +10,7 @@ import { HeaderComponent } from './components/shared/template/header/header.comp ...@@ -10,7 +10,7 @@ import { HeaderComponent } from './components/shared/template/header/header.comp
import { FooterComponent } from './components/shared/template/footer/footer.component'; import { FooterComponent } from './components/shared/template/footer/footer.component';
import { HomeComponent } from './components/shared/home/home.component'; import { HomeComponent } from './components/shared/home/home.component';
import { LoginFormComponent } from './components/authentication/login-form/login-form.component'; import { LoginFormComponent } from './components/authentication/login-form/login-form.component';
import { FormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { AuthenticationService } from './services/authentication/authentication.service'; import { AuthenticationService } from './services/authentication/authentication.service';
...@@ -56,6 +56,8 @@ import { AccordionDirective } from './directives/accordion.directive'; ...@@ -56,6 +56,8 @@ import { AccordionDirective } from './directives/accordion.directive';
import { PatientIsComingNotificationComponent } from './components/doctor/patient-is-coming-notification/patient-is-coming-notification.component'; import { PatientIsComingNotificationComponent } from './components/doctor/patient-is-coming-notification/patient-is-coming-notification.component';
import { VisitsService } from './services/visits/visits.service'; import { VisitsService } from './services/visits/visits.service';
import { ArabicDatePipe } from './pipes/arabic-date.pipe'; import { ArabicDatePipe } from './pipes/arabic-date.pipe';
import { MedicinesService } from './services/medicines/medicines.service';
import { SearchForMedicineComponent } from './components/doctor/search-for-medicine/search-for-medicine.component';
@NgModule({ @NgModule({
...@@ -69,6 +71,8 @@ import { ArabicDatePipe } from './pipes/arabic-date.pipe'; ...@@ -69,6 +71,8 @@ import { ArabicDatePipe } from './pipes/arabic-date.pipe';
}), }),
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
FormsModule,
ReactiveFormsModule
], ],
// creators of services that this module contributes to the // creators of services that this module contributes to the
...@@ -84,6 +88,7 @@ import { ArabicDatePipe } from './pipes/arabic-date.pipe'; ...@@ -84,6 +88,7 @@ import { ArabicDatePipe } from './pipes/arabic-date.pipe';
EmployeesDataService, EmployeesDataService,
DoctorNotificationsService, DoctorNotificationsService,
VisitsService, VisitsService,
MedicinesService,
], ],
// components and directives that belong to this module // components and directives that belong to this module
...@@ -130,6 +135,7 @@ import { ArabicDatePipe } from './pipes/arabic-date.pipe'; ...@@ -130,6 +135,7 @@ import { ArabicDatePipe } from './pipes/arabic-date.pipe';
AccordionDirective, AccordionDirective,
PatientIsComingNotificationComponent, PatientIsComingNotificationComponent,
ArabicDatePipe, ArabicDatePipe,
SearchForMedicineComponent,
], ],
// identifies the root component that Angular should // identifies the root component that Angular should
......
export class MedicineSearchResult {
public id: number;
public name: string;
public form: 'شراب' | 'حبوب';
public amount: number;
public dosage: number;
}
import { MedicineSearchResult } from "./medicine-search-result";
export class VisitMedicine {
public id: number;
public name: string;
public form: 'شراب' | 'حبوب';
public amount: number;
public dosage: number;
public number: number;
constructor(id: number = 0, name: string = '',
form: 'شراب' | 'حبوب' = 'حبوب', amount: number = 0, dosage: number = 0, number: number = 0) {
this.id = id;
this.name = name;
this.form = form;
this.amount = amount;
this.dosage = dosage;
this.number = number;
}
}
/* #region Accordion*/
.accordion {
margin-bottom: 1em;
}
.accordion-header {
border: none;
}
.accordion-header .accordion-button {
border: none;
}
.accordion-item {
border: none;
}
.custom-accordion-header .btn-primary {
width: 100%;
background-color: var(--heading-color);
border-color: var(--heading-color);
font-weight: 700;
}
/* #endregion */
/* #region Form card*/
.custom-form {
width: 60%;
margin: auto;
padding: 1em;
border: 1px solid var(--accent-color);
border-radius: 3%;
}
/* #endregion */
/* #region Title*/
.custom-form .custom-form-title h3 {
width: 50%;
margin: auto;
padding: 0.5em;
border: 1px solid var(--accent-color);
border-radius: 10em;
}
/* #endregion */
/* #region Server error message*/
.custom-form .custom-server-error-message .btn {
width: 100%;
font-size: 1.2em;
font-weight: 700;
cursor: auto;
}
/* #endregion */
/* #region Buttons*/
.custom-buttons .btn {
font-weight: 700;
width: 50%;
margin: auto;
font-size: 1.2em;
}
.custom-buttons .btn i {
font-weight: 900;
font-size: 1.2em;
margin-right: 0.5em;
}
.custom-buttons .btn-outline-primary {
color: white;
background-color: var(--heading-color);
border-color: var(--heading-color);
}
.custom-buttons .btn-outline-primary:hover {
background-color: white;
color: var(--heading-color);
box-shadow: 0 0 0 0.1em var(--heading-color);
}
/* #endregion */
/* #region Back-button*/
.custom-back-button {
margin-bottom: 1em;
}
.custom-back-button a {
width: 50%;
}
.custom-back-button a .btn {
width: 50%;
background-color: var(--accent-color);
color: white;
border-color: var(--accent-color);
font-weight: 700;
}
.custom-back-button a button:hover {
background-color: white;
border-color: var(--accent-color);
color: var(--accent-color);
}
.custom-back-button a .btn i {
margin-right: 0.5em;
}
/* #endregion */
/* #region Field*/
.custom-field {
width: 50%;
margin: auto;
color: var(--heading-color);
}
.custom-field .custom-label {
font-weight: 800;
margin-bottom: 0.3em;
}
.custom-field .custom-label span {
margin-right: 0.1em;
}
.custom-field .custom-input {
border: 1px solid var(--heading-color);
color: var(--heading-color);
margin-bottom: 0.3em;
}
.custom-field .custom-input:focus {
box-shadow: 0 0 0 0.1em var(--heading-color);
}
.custom-field .custom-error-message {
font-weight: 700;
}
.custom-form-buttons {
margin-top: 2em;
}
.custom-select select {
border: 1px solid var(--heading-color);
color: var(--heading-color);
margin-bottom: 0.3em;
/* For arrow */
appearance: none;
background-image: linear-gradient(45deg, transparent 50%, var(--heading-color) 50%), linear-gradient(135deg, var(--heading-color) 50%, transparent 50%);
background-position: calc(100% - 20px) calc(1em + 2px), calc(100% - 15px) calc(1em + 2px), calc(100% - 2.5em) 0.5em;
background-size: 5px 5px, 5px 5px, 1px 1.5em;
background-repeat: no-repeat;
}
.custom-select select:focus {
box-shadow: 0 0 0 0.1em var(--heading-color);
}
.custom-select option {
color: var(--heading-color);
background-color: white;
}
/* #endregion */
.table {
width: 90%;
margin: auto;
--mdb-table-bg: var(--heading-color);
--mdb-table-accent-bg: var(--accent-color);
font-weight: 800;
}
.table thead th {
background-color: var(--heading-color);
color: #fff;
}
.table tbody tr td, .table tbody tr th{
color: var(--heading-color);
}
\ No newline at end of file
<p>create-visit works!</p> <div class="cutom-child" dir="rtl">
<section class="section">
<div class="custom-title mb-4">
<h2>تسجيل زيارة</h2>
</div>
<div dir="rtl" class="mb-5 custom-form">
<!-- #region Title-->
<div class="text-center custom-form-title mb-4">
<h3 style="font-weight: 800;">{{getEmployeeFullName()}}</h3>
</div>
<!-- #endregion -->
<form #form="ngForm" (ngSubmit)="onSubmit()" class="text-center custom-form-container" autocomplete="off">
<!-- #region diagnosis-->
<div class="form-group mb-3 custom-field">
<label for="diagnosis" class="col-form-label custom-label">
التشخيص
<span class="text-danger">*</span>
</label>
<input type="text" class="form-control text-center custom-input" placeholder="التشخيص" dir="rtl"
[(ngModel)]="diagnosis" name="diagnosis" #diagnosisField="ngModel" required maxlength="50">
<div *ngIf="(diagnosisField.touched || diagnosisField.dirty) && (diagnosisField.errors)"
class="custom-error-message">
<p class="text-danger">
{{
diagnosisField.errors['required'] ? 'هذا الحقل مطلوب'
: ''
}}
</p>
</div>
</div>
<!-- #endregion -->
<!-- #region Medicines-->
<div class="accordion">
<div class="accordion-item">
<div class="custom-accordion-header">
<button class="btn btn-primary" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne"
(click)="isMedicinesSectionOpen = !isMedicinesSectionOpen">
الأدوية
<i *ngIf="!isMedicinesSectionOpen" class="fas fa-chevron-down"></i>
<i *ngIf="isMedicinesSectionOpen" class="fas fa-chevron-up"></i>
</button>
</div>
<div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne"
data-bs-parent="#accordionExample">
<div class="accordion-body">
<div class="custom-ok-button mb-3">
<button type="button" class="btn btn-outline-primary"
(click)="openModal(selectMedicineModal)">إضافة دواء +</button>
</div>
<div class="custom-child" dir="rtl" *ngIf="medicines.length !== 0">
<div class="container text-center">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">اسم الدواء</th>
<th scope="col">الشكل الدوائي</th>
<th scope="col">سعة العلبة</th>
<th scope="col">العيار</th>
<th scope="col">الكمية</th>
<th scope="col"></th> <!-- Add a new column for the delete icon -->
</tr>
</thead>
<tbody>
<tr *ngFor="let medicine of medicines; let i = index">
<th scope="row">{{i+1}}</th>
<td>{{medicine.name}}</td>
<td>{{medicine.form}}</td>
<td *ngIf="medicine.form==='حبوب'">{{medicine.amount}} حبة</td>
<td *ngIf="medicine.form==='شراب'">{{medicine.amount}} مل</td>
<td>{{medicine.dosage}}</td>
<td>{{medicine.number}}</td>
<td>
<button type="button" class="btn btn-danger btn-sm">
<i class="fas fa-trash-alt"
(click)="onDeleteMedicine(i)"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- #endregion -->
<!-- #region Options-->
<div class="accordion">
<div class="accordion-item">
<div class="custom-accordion-header">
<button class="btn btn-primary" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour"
(click)="isOptionsSectionOpen = !isOptionsSectionOpen">
خيارات
<i *ngIf="!isOptionsSectionOpen" class="fas fa-chevron-down"></i>
<i *ngIf="isOptionsSectionOpen" class="fas fa-chevron-up"></i>
</button>
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingOne"
data-bs-parent="#accordionExample">
<div class="accordion-body">
<div class="d-grid gap-3 mb-5 custom-buttons custom-form-buttons">
<button type="submit" class="btn btn-outline-primary"
[disabled]="!form.dirty || form.invalid">
حفظ
<i class="bi bi-save"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- #endregion -->
</form>
</div>
</section>
</div>
<ng-template #selectMedicineModal let-modal>
<div class="modal-body">
<app-search-for-medicine [parentModal]="modal" (created)="onAddMedicine($event)"></app-search-for-medicine>
</div>
</ng-template>
\ No newline at end of file
import { Component } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { EmployeesDataService } from '../../../services/employees/employees-data.service';
import { ActivatedRoute, Router } from '@angular/router';
import { EmployeeData } from '../../../classes/employeeData/employee-data';
import { ToastrService } from 'ngx-toastr';
import { MedicineView } from '../../../classes/medicine/medicine-view';
import { VisitMedicine } from '../../../classes/medicine/visit-medicine';
import { VisitsService } from '../../../services/visits/visits.service';
import { AuthenticationService } from '../../../services/authentication/authentication.service';
import { DoctorsService } from '../../../services/doctors/doctors.service';
@Component({ @Component({
selector: 'app-create-visit', selector: 'app-create-visit',
templateUrl: './create-visit.component.html', templateUrl: './create-visit.component.html',
styleUrl: './create-visit.component.css' styleUrl: './create-visit.component.css'
}) })
export class CreateVisitComponent { export class CreateVisitComponent implements OnInit {
constructor(private modalService: NgbModal,
private employeesDataService: EmployeesDataService,
private activatedRoute: ActivatedRoute,
private toastr: ToastrService,
private router: Router,
private doctorsService: DoctorsService,
private authenticationService: AuthenticationService,
private visitsService: VisitsService
) {}
ngOnInit(): void {
this.listenToRouteChanges();
}
employeeId: number;
listenToRouteChanges(): void {
this.activatedRoute.params.subscribe(params => {
this.employeeId = params['id'];
if (isNaN(this.employeeId)) {
this.toastr.error("حدثت مشكلة، يرجى إعادة المحاولة");
this.router.navigateByUrl('doctor/waitinglist');
}
this.setEmployee();
})
}
employee: EmployeeData = new EmployeeData;
setEmployee(): void {
this.employeesDataService.getById(this.employeeId)
.subscribe(result => {
if (result.status === false) {
this.toastr.error("حدثت مشكلة، يرجى إعادة المحاولة");
this.router.navigateByUrl('doctor/waitinglist');
}
else {
this.employee = result.employeeData!;
}
})
}
getEmployeeFullName(): string {
return `${this.employee.firstName} ${this.employee.middleName} ${this.employee.lastName}`
}
openModal(modal: any): void {
this.modalService.open(modal, {
centered: true,
size: 'md'
});
}
isMedicinesSectionOpen: boolean = false;
isOptionsSectionOpen: boolean = false;
diagnosis: string;
medicines: VisitMedicine[] = [];
onAddMedicine(visitMedicine: VisitMedicine){
this.medicines.push(visitMedicine);
}
onDeleteMedicine(index: number) {
this.medicines.splice(index, 1);
}
onSubmit(): void {
var userId = this.authenticationService.getUserData()!.id;
this.visitsService.create(userId,
this.employeeId, this.diagnosis, this.medicines)
.subscribe(result => {
if(result.status === false) {
this.toastr.error("حدثت مشكلة، يرجى إعادة المحاولة");
this.router.navigateByUrl('doctor/waitinglist');
}
else {
this.doctorsService.changeStatusByUserId(userId, 'متاح')
.subscribe(_ => {});
this.toastr.success('تم تسجيل الزيارة بنجاح ✔');
this.router.navigateByUrl(`doctor/history/${this.employeeId}`);
}
})
}
} }
.custom-field {
width: 100%;
margin: auto;
color: var(--heading-color);
}
.custom-field .custom-input {
border: 1px solid var(--heading-color);
color: var(--heading-color);
margin-bottom: 0.8em;
text-align: center;
}
.custom-field .custom-input:focus {
box-shadow: 0 0 0 0.1em var(--heading-color);
}
.custom-no-button {
width: 45%;
margin: auto;
}
.custom-ok-button {
width: 45%;
margin: auto;
}
.custom-medicine-button .btn{
width: 100%;
font-weight: 700;
margin: auto;
color: var(--heading-color);
border-color: var(--heading-color);
background-color: white;
}
.custom-medicine-button .btn:hover{
margin: auto;
color: white;
border-color: var(--heading-color);
background-color: var(--heading-color);
}
.custom-selected-button .btn{
width: 100%;
font-weight: 700;
margin: auto;
color: var(--accent-color);
border-color: var(--accent-color);
background-color: white;
}
.custom-selected-button .btn:hover{
margin: auto;
color: white;
border-color: var(--accent-color);
background-color: var(--accent-color);
}
<div class="custom-child" dir="rtl">
<!-- To avoid first field auto focus -->
<div class="form-group">
<input type="text" autofocus="autofocus" style="display:none" />
</div>
<div class="custom-title mb-3">
<h2>إضافة دواء</h2>
<p *ngIf="!isSelected">يرجى اختيار الدواء</p>
<p *ngIf="isSelected">يرجى تحديد الكمية</p>
</div>
<div *ngIf="!isSelected">
<div class="custom-field">
<input #prefixInput class="form-control custom-input" type="text"
placeholder="ادخل الأحرف الأولى من اسم الدواء" [formControl]="prefix" list="medicines" dir="ltr">
</div>
<div class="container" style="width:45%; margin:auto">
<div *ngFor="let medicine of medicines; let i = index;" class="custom-medicine-button mb-2">
<button class="btn btn-outline-primary" (click)="onSelect(i);">{{medicine.name}}</button>
</div>
</div>
</div>
<div *ngIf="isSelected" class="text-center">
<h2 style="font-size:1em; font-weight: 900">الدواء: {{selectedMedicine.name}}</h2>
</div>
<div *ngIf="isSelected">
<div class="custom-field">
<input class="form-control custom-input" type="number" placeholder="يرجى ادخال الكمية" dir="ltr"
[(ngModel)]="number">
</div>
<div class="custom-ok-button mt-3" style="width:100%">
<button class="btn btn-outline-primary" (click)="onSave(); parentModal.dismiss()"
[disabled]="number === null">حفظ</button>
</div>
</div>
<div class="custom-no-button mt-3" style="width:100%">
<button class="btn btn-outline-primary" (click)="parentModal.dismiss()">إلغاء</button>
</div>
</div>
\ No newline at end of file
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { MedicinesService } from '../../../services/medicines/medicines.service';
import { MedicineSearchResult } from '../../../classes/medicine/medicine-search-result';
import { ToastrService } from 'ngx-toastr';
import { Router } from '@angular/router';
import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged, filter, switchMap } from 'rxjs';
import { VisitMedicine } from '../../../classes/medicine/visit-medicine';
@Component({
selector: 'app-search-for-medicine',
templateUrl: './search-for-medicine.component.html',
styleUrls: ['./search-for-medicine.component.css']
})
export class SearchForMedicineComponent implements OnInit {
constructor(private medicinesService: MedicinesService,
private toastrService: ToastrService,
private router: Router
) {}
ngOnInit(): void {
// Listen to input changes with delay
this.prefix.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
filter(value => value.trim().length >= 2), // igonre less than 2 letters
switchMap(value => this.medicinesService.getAll(value))
)
.subscribe(
value => {
if(value.status === false) {
this.toastrService.error('حدثت مشكلة، يرجى إعادة المحاولة');
this.router.navigateByUrl('doctor/waitinglist');
}
else {
this.medicines = value.medicines!;
}
}
)
}
prefix: FormControl = new FormControl('');
medicines: MedicineSearchResult[] = [];
@Input("parentModal") parentModal: any;
@Output("created") created: EventEmitter<VisitMedicine> = new EventEmitter();
isSelected: boolean = false;
selectedMedicine: MedicineSearchResult = new MedicineSearchResult();
onSelect(index: number): void {
this.isSelected = true;
this.selectedMedicine = this.medicines.at(index)!;
}
number: number | null = null;
onSave(): void {
var result: VisitMedicine = new VisitMedicine(
this.selectedMedicine.id, this.selectedMedicine.name, this.selectedMedicine.form,
this.selectedMedicine.amount, this.selectedMedicine.dosage, this.number!);
this.created.emit(result);
}
}
\ No newline at end of file
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import * as config from '../../../../config';
import { catchError, map, Observable, of } from 'rxjs';
import { MedicineSearchResult } from '../../classes/medicine/medicine-search-result';
@Injectable({
providedIn: 'root'
})
export class MedicinesService {
constructor(private http: HttpClient) { }
private readonly MEDICINES_ENDPOINT: string = `${config.apiUrl}/Medicines`;
getAll(prefix: string) :
Observable<{status: boolean, errorMessage: string | null, medicines: MedicineSearchResult[] | null}> {
return this.http.get<{medicines: MedicineSearchResult[]}>(`${this.MEDICINES_ENDPOINT}?prefix=${prefix}`)
.pipe(
map((result: {medicines: MedicineSearchResult[]}) => {
return {status: true, errorMessage: null, medicines: result.medicines};
}),
catchError ((error: HttpErrorResponse) => {
console.error(error.error.detail);
return of({status: false, errorMessage: error.error.detail, medicines: null});
})
);
}
}
...@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'; ...@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import * as config from '../../../../config'; import * as config from '../../../../config';
import { catchError, map, Observable, of } from 'rxjs'; import { catchError, map, Observable, of } from 'rxjs';
import { VisitView } from '../../classes/visit/visit-view'; import { VisitView } from '../../classes/visit/visit-view';
import { VisitMedicine } from '../../classes/medicine/visit-medicine';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
...@@ -25,4 +26,28 @@ export class VisitsService { ...@@ -25,4 +26,28 @@ export class VisitsService {
}) })
) )
} }
create(doctorUserId: number, patientId: number, diagnosis: string, visitMedicines: VisitMedicine[]):
Observable<{status: boolean, errorMessage: string | null}> {
var medicines: {medicineId: number, number: number} [] = [];
visitMedicines.forEach(visitMedicine => {
medicines.push({medicineId: visitMedicine.id, number: visitMedicine.number});
});
var body = {
doctorUserId: doctorUserId,
patientId: patientId,
diagnosis: diagnosis,
medicines: medicines
};
return this.http.post(this.VISITS_ENDPOINT, body)
.pipe(
map(_ => {
return {status: true, errorMessage: null};
}),
catchError((error: HttpErrorResponse) => {
console.error(error.error.detail);
return of ({status: false, errorMessage: error.error.detail})
})
)
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment