Compare commits

..

37 Commits

Author SHA1 Message Date
666d66a339 Added Localization 2025-02-19 15:27:08 +05:30
62a1030bd4 Merge branch 'User_Role_Override' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified
# Conflicts:
#	angular/package.json
2025-02-18 19:28:05 +05:30
0e14f4e199 Modified Doctor Module ,add shift management 2025-02-18 19:21:07 +05:30
74c18e7b07 Modified Doctors Module & Add Room Management component. 2025-02-14 20:22:43 +05:30
2b5b7be681 Added Doctor Module 2025-02-12 17:52:30 +05:30
8545975107 Created Department Module 2025-02-11 19:02:38 +05:30
fa1430fb17 Added Appointment through Calender. 2025-02-11 11:23:45 +05:30
6a241003db Modified Appointment module 2025-02-07 17:52:09 +05:30
33421a3d2a Merge branch 'AppointmentModule_Modified' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified
# Conflicts:
#	angular/src/app/proxy/doctors/dto/models.ts
2025-02-07 17:43:29 +05:30
9c376a64b9 Modified 2025-02-07 17:42:13 +05:30
aec173f9f4 dto modified changes 2025-02-07 17:37:35 +05:30
79cadbf9b7 Merge branch 'AppointmentModule_Modified' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified 2025-02-07 17:16:23 +05:30
49799abf29 doctor dto and shared service added 2025-02-07 17:16:01 +05:30
d975c46a6f Merge branch 'AppointmentModule_Modified' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified 2025-02-07 17:10:08 +05:30
5b86b58104 Modified Appointment Module . 2025-02-07 17:10:00 +05:30
b2ede1c9c7 patient and patient record changes 2025-02-07 17:05:15 +05:30
003bfea125 Merge branch 'patient_module' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified 2025-02-06 18:42:58 +05:30
4876806860 Modified Appointment Module 2025-02-06 18:42:32 +05:30
79d1e38f26 misc fixes 2025-02-06 18:00:35 +05:30
91bfc622d1 Merged PatientModule 2025-02-06 16:09:20 +05:30
b5ef24a736 Merge branch 'patient_module' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified 2025-02-06 15:47:14 +05:30
6394ece8d3 Fixing Issue 2025-02-06 15:45:39 +05:30
48d6a3fae8 design changes 2025-02-06 15:33:27 +05:30
bcd8aa205a worked on upload loading progress bar 2025-02-05 18:01:28 +05:30
c5df13a8f2 Issue Fixes 2025-02-05 12:46:14 +05:30
a84ddd9f03 View and add Appointment Fixes 2025-02-04 19:04:22 +05:30
54278bb753 Merge branch 'AppointmentModule_Modified' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified 2025-02-04 16:13:48 +05:30
291df2525b Issue Fixes 2025-02-04 16:13:04 +05:30
21e3f97891 worked for appointment api 2025-02-04 16:10:40 +05:30
71dd119efe Merge branch 'patient_module' of https://git.sentientgeeks.us/ranjit/Hospital_Management into AppointmentModule_Modified
# Conflicts:
#	angular/src/app/proxy/generate-proxy.json
#	angular/src/app/proxy/index.ts
#	angular/src/app/proxy/volo/abp/domain/entities/auditing/models.ts
#	angular/src/app/proxy/volo/abp/domain/entities/models.ts
#	aspnet-core/src/HospitalManagementSystem.EntityFrameworkCore/EntityFrameworkCore/HospitalManagementSystemDbContext.cs
#	aspnet-core/src/HospitalManagementSystem.EntityFrameworkCore/Migrations/HospitalManagementSystemDbContextModelSnapshot.cs
2025-02-04 13:16:44 +05:30
ba2f370300 Added View Appointment,prime ng. 2025-02-04 11:06:42 +05:30
9ca3ab6eba patient module done with file upload 2025-02-04 10:15:27 +05:30
14eb61b75d Fixed issue. 2025-01-31 18:50:30 +05:30
ec91f9ee4f User & Role Override 2025-01-30 19:02:44 +05:30
team.net
2fefbe934c Commit Department module, Doctor module and Appointment module 2025-01-30 15:28:26 +05:30
e65b1f5870 Patient module added with patient record 2025-01-30 11:34:05 +05:30
69a0b38b41 added libs , and removed blazor 2025-01-07 15:24:48 +05:30
747 changed files with 154861 additions and 776 deletions

View File

@ -30,6 +30,31 @@
"allowedCommonJsDependencies": ["chart.js", "js-sha256"],
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
{
"input": "node_modules/primeng/resources/themes/saga-blue/theme.css",
"inject": true,
"bundleName": "saga-blue"
},
{
"input": "node_modules/primeflex/themes/primeone-dark.css",
"inject": true,
"bundleName": "primeone-dark"
},
{
"input": "node_modules/primeng/resources/primeng.min.css",
"inject": true,
"bundleName": "primeng.min"
},
{
"input": "node_modules/primeicons/primeicons.css",
"inject": true,
"bundleName": "primeicons"
},
{
"input": "node_modules/primeflex/primeflex.min.css",
"inject": true,
"bundleName": "primeflex.min"
},
{
"input": "node_modules/@volo/ngx-lepton-x.lite/assets/css/bootstrap-dim.css",
"inject": false,

View File

@ -26,7 +26,14 @@
"@angular/platform-browser": "~18.1.0",
"@angular/platform-browser-dynamic": "~18.1.0",
"@angular/router": "~18.1.0",
"@fullcalendar/angular": "^6.1.16",
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"bootstrap-icons": "~1.8.0",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primeng": "^17.18.0",
"rxjs": "~7.8.0",
"tslib": "^2.0.0",
"zone.js": "~0.14.0"
@ -3500,6 +3507,47 @@
"node": ">=6"
}
},
"node_modules/@fullcalendar/angular": {
"version": "6.1.16",
"resolved": "https://registry.npmjs.org/@fullcalendar/angular/-/angular-6.1.16.tgz",
"integrity": "sha512-Qqs0MZPlIDretmWgtOr0H+uiLO6DKeqxqH1Y2oeWufwEbQTaAxhH7mivdTmxL596JkiuJ/1dhCm+v4tVbFb40w==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": "12 - 19",
"@angular/core": "12 - 19",
"@fullcalendar/core": "~6.1.15"
}
},
"node_modules/@fullcalendar/core": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
"integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==",
"license": "MIT",
"dependencies": {
"preact": "~10.12.1"
}
},
"node_modules/@fullcalendar/daygrid": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz",
"integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.15"
}
},
"node_modules/@fullcalendar/interaction": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz",
"integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.15"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@ -12977,6 +13025,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/preact": {
"version": "10.12.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -13015,6 +13073,34 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/primeflex": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/primeflex/-/primeflex-3.3.1.tgz",
"integrity": "sha512-zaOq3YvcOYytbAmKv3zYc+0VNS9Wg5d37dfxZnveKBFPr7vEIwfV5ydrpiouTft8MVW6qNjfkaQphHSnvgQbpQ==",
"license": "MIT"
},
"node_modules/primeicons": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz",
"integrity": "sha512-KDeO94CbWI4pKsPnYpA1FPjo79EsY9I+M8ywoPBSf9XMXoe/0crjbUK7jcQEDHuc0ZMRIZsxH3TYLv4TUtHmAA==",
"license": "MIT"
},
"node_modules/primeng": {
"version": "17.18.0",
"resolved": "https://registry.npmjs.org/primeng/-/primeng-17.18.0.tgz",
"integrity": "sha512-EcvU/0Ex9QoBR6g6db9fDTCTAmzokW70TV5Oroy2gdvXRr3eqlflnOBoArQsmxTaw1oxSsu68YVj3RvcKYWhTg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": "^17.0.0 || ^18.0.0",
"@angular/core": "^17.0.0 || ^18.0.0",
"@angular/forms": "^17.0.0 || ^18.0.0",
"rxjs": "^6.0.0 || ^7.8.1",
"zone.js": "~0.14.0"
}
},
"node_modules/proc-log": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz",

View File

@ -20,7 +20,7 @@
"@abp/ng.setting-management": "~9.0.2",
"@abp/ng.tenant-management": "~9.0.2",
"@abp/ng.theme.lepton-x": "~4.0.3",
"@abp/ng.theme.shared": "~9.0.2",
"@abp/ng.theme.shared": "^9.0.4",
"@angular/animations": "~18.1.0",
"@angular/common": "~18.1.0",
"@angular/compiler": "~18.1.0",
@ -30,7 +30,14 @@
"@angular/platform-browser": "~18.1.0",
"@angular/platform-browser-dynamic": "~18.1.0",
"@angular/router": "~18.1.0",
"bootstrap-icons": "~1.8.0",
"@fullcalendar/angular": "^6.1.16",
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"@swimlane/ngx-datatable": "^20.1.0", "bootstrap-icons": "~1.8.0",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primeng": "^17.18.0",
"rxjs": "~7.8.0",
"tslib": "^2.0.0",
"zone.js": "~0.14.0"

View File

@ -1,5 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomUsersComponent } from './modules/custom-identity/custom-users/custom-users.component';
import { CustomRolesComponent } from './modules/custom-identity/custom-roles/custom-roles.component';
const routes: Routes = [
{
@ -11,10 +13,12 @@ const routes: Routes = [
path: 'account',
loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),
},
{ path: 'identity/users', component: CustomUsersComponent },
{ path: 'identity/roles', component: CustomRolesComponent },
// {
// path: 'identity',
// loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),
// },
{
path: 'tenant-management',
loadChildren: () =>
@ -25,12 +29,37 @@ const routes: Routes = [
loadChildren: () =>
import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()),
},
{ path: 'appointment/appointment-calendar',
loadChildren: () =>
import('./appointment/appointment-calendar/appointment-calendar.module').then(m => m.AppointmentCalendarModule) },
{ path: 'appointment/view-appointment', loadChildren: () => import('./appointment/view-appointment/view-appointment.module').then(m => m.ViewAppointmentModule) },
{ path: 'appointment/book-appointment', loadChildren: () => import('./appointment/book-appointment/book-appointment.module').then(m => m.BookAppointmentModule) },
{ path: 'appointment/edit-appointment', loadChildren: () => import('./appointment/edit-appointment/edit-appointment.module').then(m => m.EditAppointmentModule) },
{
path: 'appointment/appointment-calendar',
loadChildren: () =>
import('./appointment/appointment-calendar/appointment-calendar.module').then(
m => m.AppointmentCalendarModule
),
},
{
path: 'appointment/view-appointment',
loadChildren: () =>
import('./appointment/view-appointment/view-appointment.module').then(
m => m.ViewAppointmentModule
),
},
{
path: 'patients',
loadChildren: () => import('./patients/patients.module').then(m => m.PatientsModule),
},
{
path: 'departments',
loadChildren: () => import('./departments/departments.module').then(m => m.DepartmentsModule),
},
{
path: 'doctors',
loadChildren: () => import('./doctors/doctors.module').then(m => m.DoctorsModule),
},
{
path: 'rooms',
loadChildren: () => import('./room-management/room-management.module').then(m => m.RoomManagementModule),
},
];
@NgModule({

View File

@ -29,13 +29,11 @@ import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account';
AppRoutingModule,
CoreModule,
ThemeSharedModule,
InternetConnectionStatusComponent,
ThemeLeptonXModule.forRoot(),
SideMenuLayoutModule.forRoot(),
AccountLayoutModule.forRoot(),
ThemeLeptonXModule.forRoot(),
SideMenuLayoutModule.forRoot(),
AccountLayoutModule.forRoot(),
],
declarations: [AppComponent],
providers: [

View File

@ -1 +1,6 @@
<p>appointment-calendar works!</p>
<full-calendar [options]="calendarOptions" style="cursor: pointer;text-decoration:none;"></full-calendar>
<app-appointment-dialog [selectedDate]="selectedDate"
[appointmentId]="appointmentIdToEdit"[isEditMode]="isEditMode"
[visible]="isModalVisible"*ngIf="isModalVisible" [name]="'Appointment'" (close)="closeDialog()"></app-appointment-dialog>

View File

@ -0,0 +1,8 @@
.fc.fc-theme-standard .fc-view-harness .fc-event.fc-daygrid-block-event {
color: #ffffff;
background: #2563eb;
border-color: #2563eb;
}
.fc-daygrid-day-number {
text-decoration: none !important;
}

View File

@ -1,10 +1,109 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { CalendarOptions } from '@fullcalendar/core'; // useful for typechecking
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import { AppointmentService } from '@proxy/appointments';
import { PagingSortResultDto } from '@proxy/dto';
@Component({
selector: 'app-appointment-calendar',
templateUrl: './appointment-calendar.component.html',
styleUrl: './appointment-calendar.component.scss'
styleUrl: './appointment-calendar.component.scss',
})
export class AppointmentCalendarComponent {
export class AppointmentCalendarComponent implements OnInit {
appointments: any[] = [];
params: PagingSortResultDto;
selectedDate: string;
isModalVisible: boolean = false;
appointmentIdToEdit: string;
isEditMode: boolean = false;
constructor(private appointmentService: AppointmentService) {}
ngOnInit() {
this.loadAppointments({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
loadAppointments(event: any) {
let order = event.sortOrder == 1 ? ' asc' : ' desc';
this.params = {
skipCount: event.first,
maxResultCount: event.rows,
sorting: event.sortField + order,
search: event.globalFilter == null ? '' : event.globalFilter,
};
this.appointmentService.getAppointmentList(this.params).subscribe(data => {
this.appointments = data.items;
this.updateCalendarEvents();
});
}
updateCalendarEvents() {
this.calendarOptions = {
initialView: 'dayGridMonth',
plugins: [dayGridPlugin, interactionPlugin],
events: this.appointments.map(appointment => ({
id: appointment.id,
title: `${appointment.firstName} ${appointment.lastName}`,
date: this.combineDateTime(appointment.dateOfAppointment, appointment.timeOfAppointment),
})),
dateClick: arg => this.handleDateClick(arg),
eventClick: info => this.onEventClick(info),
eventContent: function(arg) {
return {
html: `<div style="background-color: #3788d8; color: white; padding: 5px;cursor: pointer; border-radius: 5px;">
${arg.event.title}
</div>`
};
}
};
}
combineDateTime(dateStr: string, timeStr: string): string {
if (!timeStr) return dateStr;
const date = new Date(dateStr);
const [hours, minutes] = timeStr.split(':');
date.setHours(parseInt(hours, 10), parseInt(minutes, 10), 0);
return date.toISOString();
}
calendarOptions: CalendarOptions = {
initialView: 'dayGridMonth',
plugins: [dayGridPlugin, interactionPlugin],
dateClick: arg => this.handleDateClick(arg),
events: [
{ title: 'event 1', date: '2025-01-01' },
],
};
closeDialog() {
this.isModalVisible = false;
this.loadAppointments({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
handleDateClick(arg) {
this.selectedDate = arg.dateStr;
this.isModalVisible = true;
this.isEditMode = false;
}
onModalClose() {
this.isModalVisible = false;
this.isEditMode = false;
this.appointmentIdToEdit = "";
}
onEventClick(info: any) {
this.appointmentIdToEdit = info.event.id;
this.isEditMode = true;
this.isModalVisible = true;
}
}

View File

@ -1,8 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { FullCalendarModule } from '@fullcalendar/angular';
import { AppointmentCalendarRoutingModule } from './appointment-calendar-routing.module';
import { AppointmentCalendarComponent } from './appointment-calendar.component';
import { AppointmentDialogComponent } from '../appointment-dialog/appointment-dialog.component';
@NgModule({
@ -11,7 +14,10 @@ import { AppointmentCalendarComponent } from './appointment-calendar.component';
],
imports: [
CommonModule,
AppointmentCalendarRoutingModule
AppointmentCalendarRoutingModule,
RouterOutlet,
FullCalendarModule,
AppointmentDialogComponent
]
})
export class AppointmentCalendarModule { }

View File

@ -0,0 +1,446 @@
<div
class="modal fade show d-block"
tabindex="-1"
role="dialog"
style="background: rgba(0, 0, 0, 0.5)"
*ngIf="visible"
aria-label="l('name')"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header py-4">
<h4 class="text-success mb-0 fs-3 fw-normal">
{{ isEditMode ? ('::edit' | abpLocalization) : ('::create' | abpLocalization) }}
{{ '::appointmentStatus' | abpLocalization }}
</h4>
<button
tabindex="0"
type="button"
(click)="onClose()"
class="btn-close"
aria-label="Close"
></button>
</div>
<form #appointmentForm="ngForm" (ngSubmit)="saveAppointment(appointmentForm)">
<div class="p-fluid grid justify-content-center">
<div class="field col-md-5">
<label for="fname"
>{{ '::firstName' | abpLocalization }}<span class="text-danger">*</span></label
>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-user"></i>
<input
autofocus
pInputText
id="fname"
name="fname"
[(ngModel)]="appointment.firstName"
#fnameCtrl="ngModel"
required
minlength="2"
maxlength="30"
[ngClass]="{
'is-valid': fnameCtrl.valid && fnameCtrl.touched,
'is-invalid': fnameCtrl.invalid && fnameCtrl.touched
}"
/>
<i *ngIf="fnameCtrl.valid && fnameCtrl.touched" class="pi pi-check text-success"></i>
<i *ngIf="fnameCtrl.invalid && fnameCtrl.touched" class="pi pi-times text-danger"></i>
</span>
<small class="text-danger" *ngIf="fnameCtrl.invalid && fnameCtrl.touched">
<span *ngIf="fnameCtrl.errors?.required">{{
'::firstNameRequired' | abpLocalization
}}</span>
<span *ngIf="fnameCtrl.errors?.minlength">
{{
'::minLength'
| abpLocalization : { length: fnameCtrl.errors?.minlength.requiredLength }
}}
</span>
<span *ngIf="fnameCtrl.errors?.maxlength">{{
'::maxLength'
| abpLocalization : { length: fnameCtrl.errors?.maxlength.requiredLength }
}}</span>
</small>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="lname"
>{{ '::lastName' | abpLocalization }}<span class="text-danger">*</span></label
>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-user"></i>
<input
pInputText
id="lname"
name="lname"
[(ngModel)]="appointment.lastName"
#lnameCtrl="ngModel"
required
minlength="2"
maxlength="30"
[ngClass]="{
'is-valid': lnameCtrl.valid && lnameCtrl.touched,
'is-invalid': lnameCtrl.invalid && lnameCtrl.touched
}"
/>
<i *ngIf="lnameCtrl.valid && lnameCtrl.touched" class="pi pi-check text-success"></i>
<i *ngIf="lnameCtrl.invalid && lnameCtrl.touched" class="pi pi-times text-danger"></i>
</span>
<small class="text-danger" *ngIf="lnameCtrl.invalid && lnameCtrl.touched">
<span *ngIf="lnameCtrl.errors?.required">{{
'::lastNameRequired' | abpLocalization
}}</span>
<span *ngIf="lnameCtrl.errors?.minlength">
{{
'::minLength'
| abpLocalization : { length: lnameCtrl.errors?.minlength.requiredLength }
}}
</span>
<span *ngIf="lnameCtrl.errors?.maxlength">
{{
'::maxLength'
| abpLocalization : { length: lnameCtrl.errors?.maxlength.requiredLength }
}}
</span>
</small>
</div>
<div class="field col-md-5">
<label>{{ '::gender' | abpLocalization }} <span class="text-danger">*</span></label>
<div class="flex align-items-center p-input-icon-right">
<p-radioButton
name="gender"
value="1"
[(ngModel)]="appointment.gender"
inputId="male"
#genderCtrl="ngModel"
required
></p-radioButton>
<label for="male" class="ml-2 mr-3">{{ '::male' | abpLocalization }}</label>
<p-radioButton
name="gender"
value="2"
[(ngModel)]="appointment.gender"
inputId="female"
required
></p-radioButton>
<label for="female" class="ml-2">{{ '::female' | abpLocalization }}</label>
<i
*ngIf="genderCtrl.valid && genderCtrl.touched"
class="pi pi-check text-success ml-2"
></i>
<i
*ngIf="genderCtrl.invalid && genderCtrl.touched"
class="pi pi-times text-danger ml-2"
></i>
</div>
<small class="text-danger d-block" *ngIf="genderCtrl.invalid && genderCtrl.touched">
{{ '::genderRequired' | abpLocalization }}
</small>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="mobile"
>{{ '::mobileNo' | abpLocalization }} <span class="text-danger">*</span></label
>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-phone"></i>
<input
pInputText
id="mobile"
name="mobile"
[(ngModel)]="appointment.mobile"
#mobileCtrl="ngModel"
required
pattern="^[0-9]{10}$"
[ngClass]="{
'is-valid': mobileCtrl.valid && mobileCtrl.touched,
'is-invalid': mobileCtrl.invalid && mobileCtrl.touched
}"
/>
<i
*ngIf="mobileCtrl.valid && mobileCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="mobileCtrl.invalid && mobileCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger d-block" *ngIf="mobileCtrl.invalid && mobileCtrl.touched">
<span *ngIf="mobileCtrl.errors?.required">{{
'::mobileNoRequired' | abpLocalization
}}</span>
<span *ngIf="mobileCtrl.errors?.pattern">{{
'::mobileNoInvalid' | abpLocalization
}}</span>
</small>
</div>
<div class="field col-md-11">
{{ '::address' | abpLocalization }} <span class="text-danger">*</span>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-map-marker"></i>
<input
pInputText
id="address"
name="address"
[(ngModel)]="appointment.address"
#addressCtrl="ngModel"
required
minlength="5"
maxlength="100"
[ngClass]="{
'is-valid': addressCtrl.valid && addressCtrl.touched,
'is-invalid': addressCtrl.invalid && addressCtrl.touched
}"
/>
<i
*ngIf="addressCtrl.valid && addressCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="addressCtrl.invalid && addressCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger d-block" *ngIf="addressCtrl.invalid && addressCtrl.touched">
<span *ngIf="addressCtrl.errors?.required">{{
'::addressRequired' | abpLocalization
}}</span>
<span *ngIf="addressCtrl.errors?.minlength">
{{
'::minLength'
| abpLocalization : { length: addressCtrl.errors?.minlength.requiredLength }
}}
</span>
<span *ngIf="addressCtrl.errors?.maxlength">
{{
'::maxLength'
| abpLocalization : { length: addressCtrl.errors?.maxlength.requiredLength }
}}
</span>
</small>
</div>
<div class="field col-md-5">
<label for="email"
>{{ '::emailId' | abpLocalization }} <span class="text-danger">*</span></label
>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-envelope"></i>
<input
pInputText
id="email"
name="email"
[(ngModel)]="appointment.email"
#emailCtrl="ngModel"
required
email
[ngClass]="{
'is-valid': emailCtrl.valid && emailCtrl.touched,
'is-invalid': emailCtrl.invalid && emailCtrl.touched
}"
/>
<i *ngIf="emailCtrl.valid && emailCtrl.touched" class="pi pi-check text-success"></i>
<i *ngIf="emailCtrl.invalid && emailCtrl.touched" class="pi pi-times text-danger"></i>
</span>
<small class="text-danger d-block" *ngIf="emailCtrl.invalid && emailCtrl.touched">
<span *ngIf="emailCtrl.errors?.required">{{
'::emailRequired' | abpLocalization
}}</span>
<span *ngIf="emailCtrl.errors?.email">{{ '::emailInvalid' | abpLocalization }}</span>
</small>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="dob"
>{{ '::dateOfBirth' | abpLocalization }} <span class="text-danger">*</span></label
>
<p-calendar
id="dob"
required
name="dob"
[(ngModel)]="Dateofbirth"
[showIcon]="true"
required
></p-calendar>
<small
class="p-error"
*ngIf="appointmentForm.controls.dob?.invalid && appointmentForm.controls.dob?.touched"
>
{{ '::dobRequired' | abpLocalization }}
</small>
</div>
<div class="field col-md-5">
<label for="doctor"
>{{ '::consultingDoctor' | abpLocalization }}
<span class="text-danger">*</span></label
>
<p-dropdown
id="doctor"
name="doctor"
[(ngModel)]="appointment.doctorId"
[options]="doctorOptions"
placeholder="Select Doctor"
optionLabel="label"
optionValue="value"
required
></p-dropdown>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="date"
>{{ '::dateOfAppointment' | abpLocalization }}
<span class="text-danger">*</span></label
>
<p-calendar
id="date"
name="date"
[(ngModel)]="AppointmentDate"
[showIcon]="true"
required
></p-calendar>
</div>
<div class="field col-md-5">
<label for="time"
>{{ '::timeOfAppointment' | abpLocalization }}
<span class="text-danger">*</span></label
>
<span class="p-input-icon-left">
<i class="pi pi-clock"></i>
<input
pInputText
id="time"
name="time"
type="time"
[(ngModel)]="appointment.timeOfAppointment"
required
/>
</span>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="injury">{{ '::injuryCondition' | abpLocalization }}</label>
<span class="p-input-icon-left">
<i class="pi pi-exclamation-triangle"></i>
<input
pInputText
id="injury"
name="injury"
[(ngModel)]="appointment.injuryORContion"
/>
</span>
</div>
<div class="field col-md-5">
<label for="insurance">
{{ '::insuranceProvider' | abpLocalization }}
</label>
<span class="p-input-icon-left">
<i class="pi pi-credit-card"></i>
<input
pInputText
id="insurance"
name="insuranceProvider"
[(ngModel)]="appointment.insuranceProvider"
/>
</span>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="status">
{{ '::appointmentStatus' | abpLocalization }}
</label>
<p-dropdown
id="status"
name="status"
[(ngModel)]="appointment.appointmentStatus"
[options]="appointmentStatuses"
optionLabel="label"
optionValue="value"
placeholder="Select Status"
></p-dropdown>
</div>
<div class="field col-md-5">
<label for="visitType">
{{ '::visitType' | abpLocalization }}
</label>
<p-dropdown
id="visitType"
name="visitType"
[(ngModel)]="appointment.visitType"
[options]="visitTypes"
optionLabel="label"
optionValue="value"
placeholder="Select Visit Type"
></p-dropdown>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="paymentStatus">
{{ '::paymentStatus' | abpLocalization }}
</label>
<p-dropdown
id="paymentStatus"
name="paymentStatus"
[(ngModel)]="appointment.paymentStatus"
[options]="paymentStatuses"
optionLabel="label"
optionValue="value"
placeholder="Select Payment Status"
></p-dropdown>
</div>
<div class="field col-11">
<label for="notes">
{{ '::notes' | abpLocalization }}
</label>
<textarea
id="notes"
name="notes"
[(ngModel)]="appointment.note"
rows="5"
cols="30"
pInputTextarea
></textarea>
</div>
<div class="field col-11 flex justify-content-end">
<button
pButton
type="submit"
[label]="'::save' | abpLocalization"
class="p-button-success"
[disabled]="appointmentForm.invalid"
></button>
<button
pButton
type="button"
[label]="'::cancel' | abpLocalization"
class="p-button-secondary ml-2"
(click)="onClose()"
></button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
.hide_body{
scrollbar-width: auto !important;
min-height: 150px;
max-height: calc(100vh - 13rem);
overflow-y: auto;
}
.is-valid {
border-color: green !important;
}
.is-invalid {
border-color: red !important;
}
.p-calendar .p-datepicker {
z-index: 1050 !important;
}
.modal {
z-index: 1040;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppointmentDialogComponent } from './appointment-dialog.component';
describe('AppointmentDialogComponent', () => {
let component: AppointmentDialogComponent;
let fixture: ComponentFixture<AppointmentDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppointmentDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AppointmentDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,187 @@
import {ToasterService } from '@abp/ng.theme.shared';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { CreateOrUpdateAppointmentDto } from '@proxy/appoinments/dto';
import { Gender, appointmentStatus, visitType, paymentStatus } from '@proxy/global-enum';
import { DoctorService } from '@proxy/doctors';
import { AppointmentService } from '@proxy/appointments';
import { ButtonModule } from 'primeng/button';
import { CalendarModule } from 'primeng/calendar';
import { ChipModule } from 'primeng/chip';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { RadioButtonModule } from 'primeng/radiobutton';
import { TableModule } from 'primeng/table';
import { ViewAppointmentRoutingModule } from '../view-appointment/view-appointment-routing.module';
import { CommonModule } from '@angular/common';
import { SharedModule } from 'src/app/shared/shared.module';
@Component({
selector: 'app-appointment-dialog',
standalone: true,
imports: [
ViewAppointmentRoutingModule,
TableModule,
DialogModule,
FormsModule,
TableModule,
ButtonModule,
DialogModule,
InputTextModule,
CalendarModule,
DropdownModule,
RadioButtonModule,
InputTextareaModule,
ChipModule,
CommonModule,
SharedModule,
],
templateUrl: './appointment-dialog.component.html',
styleUrl: './appointment-dialog.component.scss',
})
export class AppointmentDialogComponent implements OnInit {
@Input() visible: boolean = false;
@Input() name: string;
@Input() isEditMode: boolean = false;
@Output() save = new EventEmitter<any>();
@Output() close = new EventEmitter<void>();
@Input() appointmentId: string;
@Input() selectedDate: string;
appointmentsForDate: any[] = [];
loading: boolean = false;
AppointmentDialogTitle: string = '';
AppointmentDialog: boolean = false;
genders = Gender;
Dateofbirth: Date = null;
AppointmentDate: Date = null;
doctors = [];
doctorOptions = [];
constructor(
private DoctorService: DoctorService,
private AppointmentService: AppointmentService,
private toaster: ToasterService
) {}
ngOnInit(): void {
this.getdoctorlist();
if (!this.isEditMode) {
this.appointmentId = '';
this.appointment = {
firstName: '',
lastName: '',
email: '',
gender: Gender.Male,
dateOfAppointment: '',
dob: '',
timeOfAppointment: '',
mobile: '',
injuryORContion: '',
note: '',
doctorId: '',
address: '',
appointmentStatus: null,
visitType: null,
paymentStatus: null,
insuranceProvider: '',
};
} else {
this.fetchAppointmentData();
}
}
fetchAppointmentData() {
this.AppointmentService.getAppointmentById(this.appointmentId).subscribe(result => {
this.appointment = result;
this.AppointmentDate = new Date(result.dateOfAppointment);
this.Dateofbirth = new Date(result.dob);
});
}
appointment: CreateOrUpdateAppointmentDto = {
id: '',
firstName: '',
lastName: '',
email: '',
gender: Gender.Male,
mobile: '',
address: '',
dob: '',
doctorId: '',
dateOfAppointment: '',
timeOfAppointment: '',
injuryORContion: '',
note: '',
appointmentStatus: appointmentStatus.Scheduled,
visitType: visitType.NewPatient,
paymentStatus: paymentStatus.Unpaid,
insuranceProvider: '',
};
saveAppointment(form: NgForm) {
if (form.invalid) {
Object.values(form.controls).forEach(control => control.markAsTouched());
return;
}
this.appointment.dob = this.Dateofbirth.toDateString();
this.appointment.dateOfAppointment = this.AppointmentDate.toDateString();
if (this.isEditMode) {
this.AppointmentService.updateAppointment(this.appointment).subscribe(
() => {
this.toaster.success('Appointment updated successfully', 'Success');
this.AppointmentDialog = false;
this.onClose();
},
error => {
this.toaster.error(error, 'Error');
}
);
} else {
this.AppointmentService.createAppointment(this.appointment).subscribe(
() => {
this.toaster.success('Appointment created successfully', 'Success');
this.AppointmentDialog = false;
this.onClose();
},
error => {
this.toaster.error(error, 'Error');
}
);
}
}
getdoctorlist() {
this.DoctorService.get().subscribe(result => {
this.doctors = result;
this.doctorOptions = this.doctors.map(doctor => ({
label: `Dr. ${doctor.firstName} ${doctor.lastName}`,
value: doctor.id,
}));
});
}
appointmentStatuses = Object.keys(appointmentStatus)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: appointmentStatus[key as unknown as keyof typeof appointmentStatus],
value: Number(key),
}));
visitTypes = Object.keys(visitType)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: visitType[key as unknown as keyof typeof visitType],
value: Number(key),
}));
paymentStatuses = Object.keys(paymentStatus)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: paymentStatus[key as unknown as keyof typeof paymentStatus],
value: Number(key),
}));
onClose() {
this.AppointmentDialog = false;
this.appointmentId = '';
this.close.emit();
}
}

View File

@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: '',
children: [
{
path: 'appointment-calendar',
loadChildren: () => import('./appointment-calendar/appointment-calendar.module').then(m => m.AppointmentCalendarModule)
},
{
path: 'view-appointment:date',
loadChildren: () => import('./view-appointment/view-appointment.module').then(m => m.ViewAppointmentModule)
},
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AppointmentRoutingModule { }

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppointmentRoutingModule } from './appointment-routing.module';
import { AppointmentDialogComponent } from './appointment-dialog/appointment-dialog.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
SharedModule,
AppointmentRoutingModule,
AppointmentDialogComponent
]
})
export class AppointmentModule { }

View File

@ -1,11 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BookAppointmentComponent } from './book-appointment.component';
const routes: Routes = [{ path: '', component: BookAppointmentComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BookAppointmentRoutingModule { }

View File

@ -1 +0,0 @@
<p>book-appointment works!</p>

View File

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BookAppointmentComponent } from './book-appointment.component';
describe('BookAppointmentComponent', () => {
let component: BookAppointmentComponent;
let fixture: ComponentFixture<BookAppointmentComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [BookAppointmentComponent]
})
.compileComponents();
fixture = TestBed.createComponent(BookAppointmentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,10 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-book-appointment',
templateUrl: './book-appointment.component.html',
styleUrl: './book-appointment.component.scss'
})
export class BookAppointmentComponent {
}

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BookAppointmentRoutingModule } from './book-appointment-routing.module';
import { BookAppointmentComponent } from './book-appointment.component';
@NgModule({
declarations: [
BookAppointmentComponent
],
imports: [
CommonModule,
BookAppointmentRoutingModule
]
})
export class BookAppointmentModule { }

View File

@ -1,11 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EditAppointmentComponent } from './edit-appointment.component';
const routes: Routes = [{ path: '', component: EditAppointmentComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class EditAppointmentRoutingModule { }

View File

@ -1 +0,0 @@
<p>edit-appointment works!</p>

View File

@ -1,10 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-edit-appointment',
templateUrl: './edit-appointment.component.html',
styleUrl: './edit-appointment.component.scss'
})
export class EditAppointmentComponent {
}

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EditAppointmentRoutingModule } from './edit-appointment-routing.module';
import { EditAppointmentComponent } from './edit-appointment.component';
@NgModule({
declarations: [
EditAppointmentComponent
],
imports: [
CommonModule,
EditAppointmentRoutingModule
]
})
export class EditAppointmentModule { }

View File

@ -1 +1,144 @@
<p>view-appointment works!</p>
<div>
<p-table
#dt2
dataKey="id"
[value]="appointments"
[paginator]="true"
[rows]="10"
[totalRecords]="totalRecords"
[lazy]="true"
(onLazyLoad)="loadappointments($event)"
[rowsPerPageOptions]="[10, 20, 50]"
[responsiveLayout]="'scroll'"
[globalFilterFields]="['id', 'name', 'status']"
[filters]="{ global: { value: '', matchMode: 'contains' } }"
class="table table-striped"
>
<ng-template pTemplate="caption">
<div class="flex align-items-center justify-content-between mb-3 gap-3">
<h4 class="m-0">{{ '::appointmentList' | abpLocalization }}</h4>
<div class="flex-grow-1 flex justify-content-center">
<div class="input-group">
<span class="input-group-text"><i class="pi pi-search"></i></span>
<input
pInputText
type="text"
class="form-control"
(input)="dt2.filterGlobal($event.target.value, 'contains')"
[(ngModel)]="globalFilter"
placeholder="{{ '::searchKeyword' | abpLocalization }}"
/>
</div>
</div>
<div>
<button
pButton
class="p-button-rounded p-button-secondary ml-2"
(click)="loadappointments($event)"
[title]="'::refresh' | abpLocalization"
>
<i class="pi pi-refresh"></i>
</button>
<button
*ngIf="createPermission"
pButton
class="p-button-rounded p-button-success ml-2"
(click)="openNewAppointmentDialog()"
pTooltip="{{ '::addAppointment' | abpLocalization }}"
tooltipPosition="left"
>
<i class="pi pi-plus-circle"></i>
</button>
<button
pButton
class="p-button-rounded p-button-warning ml-2"
(click)="exportAppointments()"
[title]="'::export' | abpLocalization"
>
<i class="pi pi-download"></i>
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="firstName">
{{ '::name' | abpLocalization }} <p-sortIcon field="firstName" />
</th>
<th pSortableColumn="doctor">
{{ '::doctor' | abpLocalization }} <p-sortIcon field="doctor" />
</th>
<th pSortableColumn="gender">
{{ '::gender' | abpLocalization }} <p-sortIcon field="gender" />
</th>
<th pSortableColumn="dateOfAppointment">
{{ '::date' | abpLocalization }} <p-sortIcon field="dateOfAppointment" />
</th>
<th pSortableColumn="timeOfAppointment">
{{ '::time' | abpLocalization }} <p-sortIcon field="timeOfAppointment" />
</th>
<th>{{ '::mobileNo' | abpLocalization }}</th>
<th>{{ '::email' | abpLocalization }}</th>
<th pSortableColumn="appointmentStatus">
{{ '::appointmentStatus' | abpLocalization }} <p-sortIcon field="appointmentStatus" />
</th>
<th pSortableColumn="visitType">
{{ '::visitType' | abpLocalization }} <p-sortIcon field="visitType" />
</th>
<th>{{ '::actions' | abpLocalization }}</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-appointment>
<tr>
<td>{{ appointment.firstName }} {{ appointment.lastName }}</td>
<td>{{ '::doctorPrefix' | abpLocalization }} {{ appointment.doctor.firstName }} {{ appointment.doctor.lastName }}</td>
<td>
<span class="badge" [ngClass]="appointment.gender === 1 ? 'bg-primary' : 'bg-pink'">
{{ getGenderLabel(appointment.gender) }}
</span>
</td>
<td>{{ appointment.dateOfAppointment | date }}</td>
<td>{{ appointment.timeOfAppointment }}</td>
<td>{{ appointment.mobile }}</td>
<td>{{ appointment.email }}</td>
<td>
<span
class="badge"
[ngClass]="
appointment.appointmentStatus === 1
? 'bg-success'
: appointment.appointmentStatus === 2
? 'bg-primary'
: 'bg-danger'
"
>
{{ getStatusLabel(appointment.appointmentStatus) }}
</span>
</td>
<td>
<span class="badge" [ngClass]="appointment.visitType === 1 ? 'bg-success' : 'bg-danger'">
{{ getVisitTypeLabel(appointment.visitType) }}
</span>
</td>
<td class="d-flex">
<button class="btn btn-warning btn-sm ml-1" (click)="editAppointment(appointment)">
<i class="pi pi-pencil"></i>
</button>
<button class="btn btn-danger btn-sm ml-1" (click)="deleteAppointment(appointment.id)">
<i class="pi pi-trash"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
<app-appointment-dialog [appointmentId]="appointmentIdToEdit"[isEditMode]="isEditMode"
[visible]="isModalVisible"*ngIf="isModalVisible"
[name]="'::appointment' | abpLocalization"
(close)="closeDialog()"></app-appointment-dialog>

View File

@ -0,0 +1,44 @@
.is-valid {
border-color: green !important;
}
.is-invalid {
border-color: red !important;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.badge {
display: inline-block;
padding: 0.2rem 0.6rem;
border-radius: 0.5rem;
color: white;
font-weight: bold;
font-size: 0.8rem;
}
.male {
background-color: green;
}
.female {
background-color: purple;
}
.pdf-icon {
color: red;
font-size: 1.2rem;
}
.bg-pink {
background-color: #ff4081 !important;
color: white;
}
.gap-1 {
gap: 5px;
}

View File

@ -1,10 +1,192 @@
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { CreateOrUpdateAppointmentDto } from '@proxy/appoinments/dto';
import { AppointmentService } from '@proxy/appointments';
import { DoctorService } from '@proxy/doctors';
import { PagingSortResultDto } from '@proxy/dto';
import { appointmentStatus, Gender, paymentStatus, visitType } from '@proxy/global-enum';
@Component({
selector: 'app-view-appointment',
templateUrl: './view-appointment.component.html',
styleUrl: './view-appointment.component.scss'
styleUrl: './view-appointment.component.scss',
})
export class ViewAppointmentComponent {
totalRecords: number = 0;
appointmentIdToEdit: string;
AppointmentDialogTitle: string = '';
AppointmentDialog: boolean = false;
patients: [];
genders = Gender;
Dateofbirth: Date = new Date();
AppointmentDate: Date = new Date();
isModalVisible:boolean=false;
appointmentStatuses = Object.keys(appointmentStatus)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: appointmentStatus[key as unknown as keyof typeof appointmentStatus],
value: Number(key),
}));
visitTypes = Object.keys(visitType)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: visitType[key as unknown as keyof typeof visitType],
value: Number(key),
}));
paymentStatuses = Object.keys(paymentStatus)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: paymentStatus[key as unknown as keyof typeof paymentStatus],
value: Number(key),
}));
isEditMode: boolean = false;
loading: boolean = false;
params: PagingSortResultDto;
appointment: CreateOrUpdateAppointmentDto = {
id: '',
firstName: '',
lastName: '',
email: '',
gender: Gender.Male,
mobile: '',
address: '',
dob: '',
doctorId: '',
dateOfAppointment: '',
timeOfAppointment: '',
injuryORContion: '',
note: '',
appointmentStatus: appointmentStatus.Scheduled,
visitType: visitType.NewPatient,
paymentStatus: paymentStatus.Unpaid,
insuranceProvider: '',
};
appointments = [];
doctors = [];
doctorOptions = [];
createPermission = true;
editPermission = true;
deletePermission = true;
constructor(
private DoctorService: DoctorService,
private AppointmentService: AppointmentService,
private confirmation: ConfirmationService,
private toaster: ToasterService
) {}
ngOnInit(): void {
this.getdoctorlist();
this.loadappointments({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
getGenderLabel(gender: number): string {
return Gender[gender] ?? 'Unknown';
}
getStatusLabel(Status: number): string {
return appointmentStatus[Status] ?? 'Unknown';
}
getVisitTypeLabel(VisitType: number): string {
return visitType[VisitType] ?? 'Unknown';
}
loadappointments(event: any) {
this.loading = true;
let order = event.sortOrder == 1 ? ' asc' : ' desc';
event.sortField = event.sortField == undefined ? 'id' : event.sortField;
this.params = {
skipCount: event.first,
maxResultCount: event.rows,
sorting: event.sortField + order,
search: event.globalFilter == null ? '' : event.globalFilter,
};
this.AppointmentService.getAppointmentList(this.params).subscribe(data => {
this.appointments = data.items;
this.totalRecords = data.totalCount;
this.loading = false;
});
}
openNewAppointmentDialog() {
this.isModalVisible=true;
this.isEditMode = false;
}
exportAppointments() {
this.AppointmentService.getExportAppointmentRecord().subscribe(result => {
const binary = atob(result.fileContent);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'application/xlsx' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = result.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
});
}
editAppointment(appointment: any) {
this.isEditMode = true;
this.appointmentIdToEdit = appointment.id;
this.isModalVisible=true;
}
deleteAppointment(id: string) {
this.confirmation
.warn('Do you really want to delete this Appointment?', {
key: '::AreYouSure',
defaultValue: 'Are you sure?',
})
.subscribe((status: Confirmation.Status) => {
if (status == 'confirm') {
this.AppointmentService.deleteAppointmentRecord(id).subscribe(() => {
this.toaster.success('Appointment deleted successfully', 'Success');
this.loadappointments(this.params);
});
}
});
}
closeDialog() {
this.isModalVisible = false;
this.loadappointments({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
getdoctorlist() {
this.DoctorService.get().subscribe(result => {
this.doctors = result;
this.doctorOptions = this.doctors.map(doctor => ({
label: `Dr. ${doctor.firstName} ${doctor.lastName}`,
value: doctor.id,
}));
});
}
saveAppointment(appointmentData: any) {
this.closeDialog();
}
}

View File

@ -3,15 +3,40 @@ import { CommonModule } from '@angular/common';
import { ViewAppointmentRoutingModule } from './view-appointment-routing.module';
import { ViewAppointmentComponent } from './view-appointment.component';
import { TableModule } from 'primeng/table';
import { DialogModule } from 'primeng/dialog';
import { FormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { CalendarModule } from 'primeng/calendar';
import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext';
import { RadioButtonModule } from 'primeng/radiobutton';
import { DoctorService } from '@proxy/doctors';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { AppointmentDialogComponent } from "../appointment-dialog/appointment-dialog.component";
import { SharedModule } from 'src/app/shared/shared.module';
@NgModule({
declarations: [
ViewAppointmentComponent
],
declarations: [ViewAppointmentComponent],
imports: [
CommonModule,
ViewAppointmentRoutingModule
]
ViewAppointmentRoutingModule,
TableModule,
DialogModule,
FormsModule,
TableModule,
ButtonModule,
DialogModule,
InputTextModule,
CalendarModule,
DropdownModule,
RadioButtonModule,
InputTextareaModule,
ChipModule,
SharedModule,
AppointmentDialogComponent
],
providers:[DoctorService]
})
export class ViewAppointmentModule { }
export class ViewAppointmentModule {}

View File

@ -0,0 +1,176 @@
<div
class="modal fade show d-block"
tabindex="-1"
role="dialog"
style="background: rgba(0, 0, 0, 0.5)"
*ngIf="visible"
aria-label="{{ '::department' | abpLocalization }}"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header py-4">
<h4 class="text-success mb-0 fs-3 fw-normal">
{{ isEditMode ? ('::edit' | abpLocalization) : ('::create' | abpLocalization) }}
{{ '::department' | abpLocalization }}
</h4>
<button
tabindex="0"
type="button"
(click)="onClose()"
class="btn-close"
aria-label="{{ '::close' | abpLocalization }}"
></button>
</div>
<form #departmentForm="ngForm" (ngSubmit)="saveDepartment(departmentForm)">
<div class="p-fluid grid justify-content-center">
<div class="field col-md-5">
<label for="departmentNo">
{{ '::departmentNo' | abpLocalization }} <span class="text-danger">*</span>
</label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-hashtag"></i>
<input
pInputText
id="departmentNo"
name="departmentNo"
[(ngModel)]="department.departmentNo"
#departmentNoCtrl="ngModel"
required
[ngClass]="{
'is-valid': departmentNoCtrl.valid && departmentNoCtrl.touched,
'is-invalid': departmentNoCtrl.invalid && departmentNoCtrl.touched
}"
/>
<i
*ngIf="departmentNoCtrl.valid && departmentNoCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="departmentNoCtrl.invalid && departmentNoCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger" *ngIf="departmentNoCtrl.invalid && departmentNoCtrl.touched">
<span *ngIf="departmentNoCtrl.errors?.required">
{{ '::departmentNoRequired' | abpLocalization }}
</span>
</small>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="departmentName">
{{ '::departmentName' | abpLocalization }} <span class="text-danger">*</span>
</label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-building"></i>
<input
pInputText
id="departmentName"
name="departmentName"
[(ngModel)]="department.departmentName"
#departmentNameCtrl="ngModel"
required
/>
</span>
<small
class="text-danger"
*ngIf="departmentNameCtrl.invalid && departmentNameCtrl.touched"
>
<span *ngIf="departmentNameCtrl.errors?.required">
{{ '::departmentNameRequired' | abpLocalization }}
</span>
</small>
</div>
<div class="field col-md-5">
<label for="departmentDate">
{{ '::departmentDate' | abpLocalization }}
</label>
<p-calendar
id="departmentDate"
name="departmentDate"
[(ngModel)]="Departmentdate"
[showIcon]="true"
>
</p-calendar>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="departmentHead">
{{ '::departmentHead' | abpLocalization }}
</label>
<span class="p-input-icon-left">
<i class="pi pi-user"></i>
<input
pInputText
id="departmentHead"
name="departmentHead"
[(ngModel)]="department.departmentHead"
/>
</span>
</div>
<div class="field col-md-5">
<label>
{{ '::status' | abpLocalization }}
</label>
<div class="flex align-items-center p-input-icon-right">
<p-radioButton
name="status"
value="Active"
[(ngModel)]="department.status"
inputId="active"
></p-radioButton>
<label for="active" class="ml-2 mr-3">
{{ '::active' | abpLocalization }}
</label>
<p-radioButton
name="status"
value="Inactive"
[(ngModel)]="department.status"
inputId="inactive"
></p-radioButton>
<label for="inactive" class="ml-2">
{{ '::inactive' | abpLocalization }}
</label>
</div>
</div>
<div class="field col-md-6"></div>
<div class="field col-11">
<label for="description">
{{ '::description' | abpLocalization }}
</label>
<textarea
id="description"
name="description"
[(ngModel)]="department.description"
rows="5"
cols="30"
pInputTextarea
>
</textarea>
</div>
<div class="field col-11 flex justify-content-end">
<button
pButton
type="submit"
[label]="'::save' | abpLocalization"
class="p-button-success"
[disabled]="departmentForm.invalid"
></button>
<button
pButton
type="button"
[label]="'::cancel' | abpLocalization"
class="p-button-secondary ml-2"
(click)="onClose()"
></button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
.hide_body{
scrollbar-width: auto !important;
min-height: 150px;
max-height: calc(100vh - 13rem);
overflow-y: auto;
}
.is-valid {
border-color: green !important;
}
.is-invalid {
border-color: red !important;
}
.p-calendar .p-datepicker {
z-index: 1050 !important;
}
.modal {
z-index: 1040;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DepartmentDialogComponent } from './department-dialog.component';
describe('DepartmentDialogComponent', () => {
let component: DepartmentDialogComponent;
let fixture: ComponentFixture<DepartmentDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DepartmentDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DepartmentDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,107 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, NgForm } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { CalendarModule } from 'primeng/calendar';
import { ChipModule } from 'primeng/chip';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { RadioButtonModule } from 'primeng/radiobutton';
import { TableModule } from 'primeng/table';
import { CreateDepartmentDto } from '@proxy/dtos';
import { DepartmentService } from '@proxy/departments';
import { ToasterService } from '@abp/ng.theme.shared';
import { SharedModule } from '../shared/shared.module';
@Component({
selector: 'app-department-dialog',
standalone: true,
imports: [
TableModule,
DialogModule,
FormsModule,
TableModule,
ButtonModule,
DialogModule,
InputTextModule,
CalendarModule,
DropdownModule,
RadioButtonModule,
InputTextareaModule,
ChipModule,
CommonModule,
SharedModule
],
templateUrl: './department-dialog.component.html',
styleUrl: './department-dialog.component.scss',
})
export class DepartmentDialogComponent implements OnInit {
@Input() visible: boolean = false;
@Input() name: string;
@Input() isEditMode: boolean = false;
@Output() save = new EventEmitter<any>();
@Output() close = new EventEmitter<void>();
@Input() Id: string;
DepartmentDialog: boolean;
Departmentdate: Date;
constructor(
private DepartmentService: DepartmentService,
private toaster: ToasterService
) {}
ngOnInit(): void {
if(this.isEditMode){
this.fetchDepartmentData();
}
}
department: CreateDepartmentDto = {
departmentNo: '',
departmentName: '',
departmentDate: '',
departmentHead: '',
description: '',
status: '',
};
fetchDepartmentData() {
this.DepartmentService.getDepartmentById(this.Id).subscribe(result => {
this.department = result;
this.Departmentdate = new Date(result.departmentDate);
});
}
saveDepartment(form: NgForm) {
if (form.invalid) {
Object.values(form.controls).forEach(control => control.markAsTouched());
return;
}
this.department.departmentDate = this.Departmentdate.toDateString();
if (this.isEditMode) {
this.DepartmentService.updateDepartment(this.department).subscribe(
() => {
this.toaster.success('Department updated successfully', 'Success');
this.onClose();
},
error => {
this.toaster.error(error, 'Error');
}
);
} else {
this.DepartmentService.createDepartment(this.department).subscribe(
() => {
this.toaster.success('Department created successfully', 'Success');
this.onClose();
},
error => {
this.toaster.error(error, 'Error');
}
);
}
}
onClose() {
this.Id = '';
this.close.emit();
}
}

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DepartmentsComponent } from './departments.component';
const routes: Routes = [
{ path: '', component: DepartmentsComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DepartmentsRoutingModule { }

View File

@ -0,0 +1,122 @@
<div>
<p-table
#dt2
dataKey="id"
[value]="departments"
[paginator]="true"
[rows]="10"
[totalRecords]="totalRecords"
[lazy]="true"
(onLazyLoad)="loaddepartments($event)"
[rowsPerPageOptions]="[10, 20, 50]"
[responsiveLayout]="'scroll'"
[globalFilterFields]="['id', 'name', 'status']"
[filters]="{ global: { value: '', matchMode: 'contains' } }"
class="table table-striped"
>
<ng-template pTemplate="caption">
<div class="flex align-items-center justify-content-between mb-3 gap-3">
<h4 class="m-0">{{ '::departmentList' | abpLocalization }}</h4>
<div class="flex-grow-1 flex justify-content-center">
<div class="input-group">
<span class="input-group-text"><i class="pi pi-search"></i></span>
<input
pInputText
type="text"
class="form-control"
(input)="dt2.filterGlobal($event.target.value, 'contains')"
[(ngModel)]="globalFilter"
placeholder="{{ '::searchKeyword' | abpLocalization }}"
/>
</div>
</div>
<div>
<button
pButton
class="p-button-rounded p-button-secondary ml-2"
(click)="loaddepartments($event)"
[title]="'::refresh' | abpLocalization"
>
<i class="pi pi-refresh"></i>
</button>
<button
pButton
class="p-button-rounded p-button-success ml-2"
(click)="openNewDepartmentDialog()"
pTooltip="{{ '::addDepartment' | abpLocalization }}"
tooltipPosition="left"
>
<i class="pi pi-plus-circle"></i>
</button>
<button
pButton
class="p-button-rounded p-button-warning ml-2"
(click)="exportDepartments()"
[title]="'::export' | abpLocalization"
>
<i class="pi pi-download"></i>
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="departmentNo">
{{ '::departmentNumber' | abpLocalization }}
<p-sortIcon field="departmentNo" />
</th>
<th pSortableColumn="departmentName">
{{ '::departmentName' | abpLocalization }}
<p-sortIcon field="departmentName" />
</th>
<th pSortableColumn="description">
{{ '::description' | abpLocalization }}
<p-sortIcon field="description" />
</th>
<th pSortableColumn="departmentDate">
{{ '::date' | abpLocalization }}
<p-sortIcon field="departmentDate" />
</th>
<th pSortableColumn="departmentHead">
{{ '::departmentHead' | abpLocalization }}
<p-sortIcon field="departmentHead" />
</th>
<th pSortableColumn="status">
{{ '::status' | abpLocalization }}
<p-sortIcon field="status" />
</th>
<th>{{ '::actions' | abpLocalization }}</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-department>
<tr>
<td>{{ department.departmentNo }}</td>
<td>{{ department.departmentName }}</td>
<td>{{ department.description }}</td>
<td>{{ department.departmentDate | date }}</td>
<td>{{ department.departmentHead }}</td>
<td>
<span class="badge" [ngClass]="department.status === 'Active' ? 'bg-primary' : 'bg-danger'">
{{ department.status }}
</span>
</td>
<td class="d-flex">
<button class="btn btn-warning btn-sm ml-1" (click)="editDepartment(department)">
<i class="pi pi-pencil"></i>
</button>
<button class="btn btn-danger btn-sm ml-1" (click)="deletedepartment(department.id)">
<i class="pi pi-trash"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
<app-department-dialog [Id]="DepartmentIdToEdit"[isEditMode]="isEditMode"
[visible]="isModalVisible"*ngIf="isModalVisible"
[name]="'::department' | abpLocalization"
(close)="closeDialog()"></app-department-dialog >

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DepartmentsComponent } from './departments.component';
describe('DepartmentsComponent', () => {
let component: DepartmentsComponent;
let fixture: ComponentFixture<DepartmentsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DepartmentsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DepartmentsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,113 @@
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { DepartmentService } from '@proxy/departments';
import { PagingSortResultDto } from '@proxy/dto';
@Component({
selector: 'app-departments',
templateUrl: './departments.component.html',
styleUrl: './departments.component.scss',
})
export class DepartmentsComponent implements OnInit {
totalRecords: number = 0;
departments= [];
loading: boolean = false;
params: PagingSortResultDto;
isModalVisible: boolean;
isEditMode: boolean = false;
DepartmentIdToEdit: string;
ngOnInit(): void {
this.loaddepartments({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
constructor(
private DepartmentService: DepartmentService,
private confirmation: ConfirmationService,
private toaster: ToasterService
) {}
loaddepartments(event: any) {
this.loading = true;
let order = event.sortOrder == 1 ? ' asc' : ' desc';
event.sortField = event.sortField == undefined ? 'id' : event.sortField;
this.params = {
skipCount: event.first,
maxResultCount: event.rows,
sorting: event.sortField + order,
search: event.globalFilter == null ? '' : event.globalFilter,
};
this.DepartmentService.getDepartmentList(this.params).subscribe(data => {
this.departments = data.items;
this.totalRecords = data.totalCount;
this.loading = false;
});
}
openNewDepartmentDialog() {
this.isModalVisible=true;
this.isEditMode = false;
}
editDepartment(Department: any) {
this.isEditMode = true;
this.DepartmentIdToEdit = Department.id;
this.isModalVisible=true;
}
deletedepartment(id: string) {
this.confirmation
.warn('Do you really want to delete this Department?', {
key: '::AreYouSure',
defaultValue: 'Are you sure?',
})
.subscribe((status: Confirmation.Status) => {
if (status == 'confirm') {
this.DepartmentService.deleteDepartmentRecord(id).subscribe(() => {
this.toaster.success('Deleted successfully', 'Success');
this.loaddepartments(this.params);
});
}
});
}
closeDialog() {
this.isModalVisible = false;
this.loaddepartments({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
exportDepartments() {
this.DepartmentService.getExportDepartmentRecord().subscribe(result => {
const binary = atob(result.fileContent);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'application/xlsx' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = result.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
});
}
}

View File

@ -0,0 +1,41 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DepartmentsRoutingModule } from './departments-routing.module';
import { DepartmentsComponent } from './departments.component';
import { FormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { CalendarModule } from 'primeng/calendar';
import { ChipModule } from 'primeng/chip';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { RadioButtonModule } from 'primeng/radiobutton';
import { TableModule } from 'primeng/table';
import { DepartmentDialogComponent } from "./department-dialog.component";
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [DepartmentsComponent],
imports: [
CommonModule,
SharedModule,
DepartmentsRoutingModule,
TableModule,
DialogModule,
FormsModule,
TableModule,
ButtonModule,
DialogModule,
InputTextModule,
CalendarModule,
DropdownModule,
RadioButtonModule,
InputTextareaModule,
ChipModule,
DepartmentDialogComponent
]
})
export class DepartmentsModule { }

View File

@ -0,0 +1,393 @@
<div
class="modal fade show d-block"
tabindex="-1"
role="dialog"
style="background: rgba(0, 0, 0, 0.5)"
*ngIf="visible"
aria-label="l('Doctor')"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header py-4">
<h4 class="text-success mb-0 fs-3 fw-normal">
{{ isEditMode ? 'Edit ' : 'Create ' }} Doctor
</h4>
<button
tabindex="0"
type="button"
(click)="onClose()"
class="btn-close"
aria-label="Close"
></button>
</div>
<form #doctorForm="ngForm" (ngSubmit)="saveDoctor(doctorForm)">
<div class="p-fluid grid justify-content-center">
<!-- First Name -->
<div class="field col-md-5">
<label for="firstName">First Name <span class="text-danger">*</span></label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-user"></i>
<input
pInputText
id="firstName"
name="firstName"
[(ngModel)]="doctor.firstName"
#firstNameCtrl="ngModel"
required
minlength="2"
maxlength="30"
[ngClass]="{
'is-valid': firstNameCtrl.valid && firstNameCtrl.touched,
'is-invalid': firstNameCtrl.invalid && firstNameCtrl.touched
}"
/>
<i
*ngIf="firstNameCtrl.valid && firstNameCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="firstNameCtrl.invalid && firstNameCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger" *ngIf="firstNameCtrl.invalid && firstNameCtrl.touched">
<span *ngIf="firstNameCtrl.errors?.required">First Name is required.</span>
<span *ngIf="firstNameCtrl.errors?.minlength">Minimum 2 characters required.</span>
<span *ngIf="firstNameCtrl.errors?.maxlength">Maximum 30 characters allowed.</span>
</small>
</div>
<div class="field col-md-1"></div>
<!-- Last Name -->
<div class="field col-md-5">
<label for="lastName">Last Name <span class="text-danger">*</span></label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-user"></i>
<input
pInputText
id="lastName"
name="lastName"
[(ngModel)]="doctor.lastName"
#lastNameCtrl="ngModel"
required
minlength="2"
maxlength="30"
[ngClass]="{
'is-valid': lastNameCtrl.valid && lastNameCtrl.touched,
'is-invalid': lastNameCtrl.invalid && lastNameCtrl.touched
}"
/>
<i
*ngIf="lastNameCtrl.valid && lastNameCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="lastNameCtrl.invalid && lastNameCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger" *ngIf="lastNameCtrl.invalid && lastNameCtrl.touched">
<span *ngIf="lastNameCtrl.errors?.required">Last Name is required.</span>
</small>
</div>
<!-- Mobile -->
<div class="field col-md-5">
<label for="mobile">Mobile <span class="text-danger">*</span></label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-phone"></i>
<input
pInputText
id="mobile"
name="mobile"
[(ngModel)]="doctor.mobile"
#mobileCtrl="ngModel"
required
pattern="^[0-9]{10}$"
[ngClass]="{
'is-valid': mobileCtrl.valid && mobileCtrl.touched,
'is-invalid': mobileCtrl.invalid && mobileCtrl.touched
}"
/>
<i
*ngIf="mobileCtrl.valid && mobileCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="mobileCtrl.invalid && mobileCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger" *ngIf="mobileCtrl.invalid && mobileCtrl.touched">
<span *ngIf="mobileCtrl.errors?.required">Mobile is required.</span>
<span *ngIf="mobileCtrl.errors?.pattern">Enter a valid 10-digit number.</span>
</small>
</div>
<div class="field col-md-1"></div>
<!-- Email -->
<div class="field col-md-5">
<label for="email">Email <span class="text-danger">*</span></label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-envelope"></i>
<input
pInputText
id="email"
name="email"
[(ngModel)]="doctor.email"
#emailCtrl="ngModel"
required
type="email"
pattern="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
[ngClass]="{
'is-valid': emailCtrl.valid && emailCtrl.touched,
'is-invalid': emailCtrl.invalid && emailCtrl.touched
}"
/>
<i *ngIf="emailCtrl.valid && emailCtrl.touched" class="pi pi-check text-success"></i>
<i *ngIf="emailCtrl.invalid && emailCtrl.touched" class="pi pi-times text-danger"></i>
</span>
<small class="text-danger" *ngIf="emailCtrl.invalid && emailCtrl.touched">
<span *ngIf="emailCtrl.errors?.required">Email is required.</span>
<span *ngIf="emailCtrl.errors?.pattern">Enter a valid email address.</span>
</small>
</div>
<!-- Specialization -->
<div class="field col-md-5">
<label for="specialization">Specialization</label>
<input
pInputText
id="specialization"
name="specialization"
[(ngModel)]="doctor.specialization"
/>
</div>
<div class="field col-md-1"></div>
<!-- Experience -->
<div class="field col-md-5">
<label for="experience">Experience (Years)</label>
<input
type="number"
pInputText
id="experience"
name="experience"
[(ngModel)]="doctor.experience"
min="0"
/>
</div>
<!-- Consultation Fee -->
<div class="field col-md-5">
<label for="consultationFee">Consultation Fee</label>
<input
type="number"
pInputText
id="consultationFee"
name="consultationFee"
[(ngModel)]="doctor.consultationFee"
min="0"
/>
</div>
<div class="field col-md-1"></div>
<!-- Clinic Location -->
<div class="field col-md-5">
<label for="clinicLocation">Clinic Location</label>
<input
pInputText
id="clinicLocation"
name="clinicLocation"
[(ngModel)]="doctor.clinicLocation"
/>
</div>
<div class="field col-md-5">
<label for="dateofbirth">Date Of Birth <span class="text-danger">*</span></label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-calendar"></i>
<p-calendar
id="dateofbirth"
name="dateofbirth"
[(ngModel)]="dateofbirth"
#dateofbirthCtrl="ngModel"
required
[showIcon]="true"
dateFormat="dd/mm/yy"
[ngClass]="{
'is-valid': dateofbirthCtrl.valid && dateofbirthCtrl.touched,
'is-invalid': dateofbirthCtrl.invalid && dateofbirthCtrl.touched
}"
>
</p-calendar>
<i
*ngIf="dateofbirthCtrl.valid && dateofbirthCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="dateofbirthCtrl.invalid && dateofbirthCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger" *ngIf="dateofbirthCtrl.invalid && dateofbirthCtrl.touched">
<span *ngIf="dateofbirthCtrl.errors?.required">DOB is required.</span>
</small>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<label for="joiningDate">Joining Date <span class="text-danger">*</span></label>
<span class="p-input-icon-left p-input-icon-right">
<i class="pi pi-calendar"></i>
<p-calendar
id="joiningDate"
name="joiningDate"
[(ngModel)]="JoiningDate"
#joiningDateCtrl="ngModel"
required
[showIcon]="true"
dateFormat="dd/mm/yy"
[ngClass]="{
'is-valid': joiningDateCtrl.valid && joiningDateCtrl.touched,
'is-invalid': joiningDateCtrl.invalid && joiningDateCtrl.touched
}"
>
</p-calendar>
<i
*ngIf="joiningDateCtrl.valid && joiningDateCtrl.touched"
class="pi pi-check text-success"
></i>
<i
*ngIf="joiningDateCtrl.invalid && joiningDateCtrl.touched"
class="pi pi-times text-danger"
></i>
</span>
<small class="text-danger" *ngIf="joiningDateCtrl.invalid && joiningDateCtrl.touched">
<span *ngIf="joiningDateCtrl.errors?.required">Joining Date is required.</span>
</small>
</div>
<div class="field col-md-5">
<label for="rating">Rating</label><br />
<p-rating
[(ngModel)]="doctor.rating"
[stars]="5"
[cancel]="false"
name="rating"
></p-rating>
</div>
<div class="field col-md-1"></div>
<!-- Button to open the popup -->
<div class="col-md-5">
<div class="button-container">
<button
pButton
type="button"
label="Select Availability"
icon="pi pi-calendar"
(click)="openDialog()"
class="p-mr-2"
></button>
</div>
</div>
<!-- #incomplete -->
<!-- Need to Be Worked on the popup for schedule the time -->
<p-dialog
header="Select Days and Times"
[(visible)]="displayDialog"
modal="true"
closable="true"
[style]="{ width: '80%', height: '100%' }"
[baseZIndex]="10000"
styleClass="availability-dialog"
>
<div class="p-fluid grid">
<div class="field col-md-5">
<label for="availability">Select Available Days</label>
<p-multiSelect
[options]="workScheduleOptions"
[(ngModel)]="selectedDays"
optionLabel="label"
placeholder="Select Days"
(onChange)="onDaysChange($event)"
></p-multiSelect>
</div>
<div class="field col-md-1"></div>
<div class="field col-md-5">
<!-- Display time selection for each selected day -->
<div *ngIf="selectedDays && selectedDays.length" class="selected-days-container">
<h3 class="selected-days-header">Set Time for Selected Days</h3>
<div *ngFor="let day of selectedDays" class="day-time-row">
<span class="day-label">{{ day.label }}</span>
<p-dropdown
[options]="timeSlotOptions"
[ngModel]="selectedTimes[day.value]"
(onChange)="onTimeChange(day.value, $event)"
placeholder="Select Time"
style="width: 80%"
></p-dropdown>
</div>
</div>
</div>
</div>
<p-footer>
<div class="footer-buttons p-d-flex p-jc-end">
<button
pButton
type="button"
label="Save"
icon="pi pi-check"
(click)="saveSelection()"
class="p-button-success p-mr-2"
></button
>&nbsp;
<button
pButton
type="button"
label="Cancel"
icon="pi pi-times"
(click)="closeDialog()"
class="p-button-secondary"
></button>
</div>
</p-footer>
</p-dialog>
<!-- end -->
<div class="field col-11">
<label for="description">Description</label>
<textarea
id="description"
name="description"
[(ngModel)]="doctor.description"
rows="5"
cols="30"
pInputTextarea
></textarea>
</div>
<div class="field col-11 flex justify-content-end">
<button
pButton
type="submit"
label="Save"
class="p-button-success"
[disabled]="doctorForm.invalid"
></button>
<button
pButton
type="button"
label="Cancel"
class="p-button-secondary ml-2"
(click)="onClose()"
></button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,85 @@
.hide_body{
scrollbar-width: auto !important;
min-height: 150px;
max-height: calc(100vh - 13rem);
overflow-y: auto;
}
.is-valid {
border-color: green !important;
}
.is-invalid {
border-color: red !important;
}
/* Adjust the z-index of the calendar dropdown */
.p-calendar .p-datepicker {
z-index: 1050 !important; /* Make sure it's above other elements */
}
/* You can also adjust modal z-index if necessary */
.modal {
z-index: 1040; /* Set lower z-index to ensure modal is behind the calendar */
}
.availability-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
padding: 10px;
border-bottom: 1px solid #ddd;
margin-bottom: 10px;
}
.w-full {
width: 100%;
}
.dropdown-item-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0; /* adjust as needed */
}
.day-label {
flex: 0 0 auto;
}
.time-options {
flex: 1;
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.time-label {
display: flex;
align-items: center;
}
/* Style for the container below the multi-select */
.selected-days-container {
margin-top: 1rem;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
background: #fafafa;
}
/* Header styling */
.selected-days-header {
margin-bottom: 0.75rem;
font-size: 1.1rem;
color: #333;
}
/* Each day row */
.day-time-row {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 0.5rem;
}
/* Fixed width for day label */
.day-label {
width: 100px;
font-weight: 600;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DoctorDialogComponent } from './doctor-dialog.component';
describe('DoctorDialogComponent', () => {
let component: DoctorDialogComponent;
let fixture: ComponentFixture<DoctorDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DoctorDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DoctorDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,196 @@
import { ToasterService } from '@abp/ng.theme.shared';
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { DoctorService } from '@proxy/doctors';
import { CreateDoctorDto } from '@proxy/dtos';
import { ButtonModule } from 'primeng/button';
import { CalendarModule } from 'primeng/calendar';
import { ChipModule } from 'primeng/chip';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { RadioButtonModule } from 'primeng/radiobutton';
import { TableModule } from 'primeng/table';
import { RatingModule } from 'primeng/rating';
import { MultiSelectModule } from 'primeng/multiselect';
import { TimeSlot, WorkSchedule, } from '@proxy/global-enum';
@Component({
selector: 'app-doctor-dialog',
standalone: true,
imports: [
TableModule,
DialogModule,
FormsModule,
TableModule,
ButtonModule,
DialogModule,
InputTextModule,
CalendarModule,
DropdownModule,
RadioButtonModule,
InputTextareaModule,
ChipModule,
CommonModule,
RatingModule,
MultiSelectModule,
],
templateUrl: './doctor-dialog.component.html',
styleUrl: './doctor-dialog.component.scss',
})
export class DoctorDialogComponent implements OnInit {
@Input() visible: boolean = false;
@Input() name: string;
@Input() isEditMode: boolean = false;
@Output() save = new EventEmitter<any>();
@Output() close = new EventEmitter<void>();
@Input() Id: string;
doctorDialog: boolean;
JoiningDate: Date;
dateofbirth: Date;
isSchedulePopupVisible = false;
selectedDays: { label: string; value: string }[] = [];
selectedTimeSlots: { [key: number]: number | null } = {};
displayDialog: boolean = false;
selectedDay: any = null;
selectedTime: any = null;
selectedTimes: { [key: number]: number } = {};
workScheduleOptions: { label: string; value: number }[] = [];
timeSlotOptions: { label: string; value: number }[] = [];
constructor(private DoctorService: DoctorService, private toaster: ToasterService) {}
ngOnInit(): void {
if (this.isEditMode) {
this.fetchDoctorData();
} else {
this.doctor = {
firstName: '',
lastName: '',
gender: '',
mobile: '',
password: '',
designation: '',
departmentId: '',
address: '',
email: '',
dob: '',
education: '',
specialization: '',
degree: '',
joiningDate: '',
experience: 0,
consultationFee: 0,
availability: null,
rating: 0,
clinicLocation: '',
};
}
this.initializeWorkScheduleOptions();
this.initializeTimeSlotOptions();
}
doctor: CreateDoctorDto = {
id: '',
firstName: '',
lastName: '',
gender: '',
mobile: '',
password: '',
designation: '',
departmentId: '',
address: '',
email: '',
dob: '',
education: '',
specialization: '',
degree: '',
joiningDate: '',
experience: 0,
consultationFee: 0,
availability: null,
rating: 0,
clinicLocation: '',
};
openDialog() {
this.displayDialog = true;
}
onDaysChange(event: any) {
this.selectedDays=event.value;
}
onTimeChange(dayValue: number, timeValue: number) {
this.selectedTimes[dayValue] = timeValue;
}
closeDialog() {
this.displayDialog = false;
}
initializeWorkScheduleOptions() {
this.workScheduleOptions = Object.keys(WorkSchedule)
.filter(key => isNaN(Number(key)))
.map(key => {
const value = WorkSchedule[key as keyof typeof WorkSchedule];
return { label: key, value };
});
}
initializeTimeSlotOptions() {
this.timeSlotOptions = Object.keys(TimeSlot)
.filter(key => isNaN(Number(key)))
.map(key => {
const value = TimeSlot[key as keyof typeof TimeSlot];
return { label: key, value };
});
}
saveSelection() {
const availability = this.selectedDays.map(day => ({
day: day.value,
time: this.selectedTimes[day.value] || null,
}));
this.displayDialog = false;
}
fetchDoctorData() {
this.DoctorService.getDoctorById(this.Id).subscribe(result => {
this.doctor = result;
this.JoiningDate = new Date(result.joiningDate);
this.dateofbirth = new Date(result.dob);
});
}
saveDoctor(form: NgForm) {
if (form.invalid) {
Object.values(form.controls).forEach(control => control.markAsTouched());
return;
}
this.doctor.dob = this.dateofbirth.toDateString();
this.doctor.joiningDate = this.JoiningDate.toDateString();
if (this.isEditMode) {
this.DoctorService.updatDoctor(this.doctor).subscribe(
() => {
this.toaster.success('Updated Successfully', 'Success');
this.onClose();
},
error => {
this.toaster.error(error, 'Error');
}
);
} else {
this.DoctorService.createDoctor(this.doctor).subscribe(
() => {
this.toaster.success('created successfully', 'Success');
this.onClose();
},
error => {
this.toaster.error(error, 'Error');
}
);
}
}
onClose() {
this.Id = '';
this.close.emit();
}
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DoctorsComponent } from './doctors.component';
import { ShiftManagementComponent } from './shift-management/shift-management.component';
const routes: Routes = [
{ path: '', component: DoctorsComponent },
{ path: 'shift-management/:id', component: ShiftManagementComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class DoctorsRoutingModule {}

View File

@ -0,0 +1,119 @@
<div>
<p-table
#dt
dataKey="id"
[value]="doctors"
[paginator]="true"
[rows]="10"
[totalRecords]="totalRecords"
[lazy]="true"
(onLazyLoad)="loadDoctors($event)"
[rowsPerPageOptions]="[10, 20, 50]"
[responsiveLayout]="'scroll'"
[globalFilterFields]="['id', 'firstName', 'lastName', 'mobile', 'email', 'specialization']"
[filters]="{ global: { value: '', matchMode: 'contains' } }"
class="table table-striped"
>
<ng-template pTemplate="caption">
<div class="flex align-items-center justify-content-between mb-3 gap-3">
<h4 class="m-0">{{ '::doctorList' | abpLocalization }}</h4>
<div class="flex-grow-1 flex justify-content-center">
<div class="input-group">
<span class="input-group-text"><i class="pi pi-search"></i></span>
<input
pInputText
type="text"
class="form-control"
(input)="dt.filterGlobal($event.target.value, 'contains')"
[(ngModel)]="globalFilter"
placeholder="{{ '::searchKeyword' | abpLocalization }}"
/>
</div>
</div>
<div>
<button
pButton
class="p-button-rounded p-button-secondary ml-2"
(click)="loadDoctors($event)"
[title]="'::refresh' | abpLocalization"
>
<i class="pi pi-refresh"></i>
</button>
<button
pButton
class="p-button-rounded p-button-success ml-2"
(click)="openNewDoctorDialog()"
pTooltip="{{ '::addDoctor' | abpLocalization }}"
tooltipPosition="left"
>
<i class="pi pi-plus-circle"></i>
</button>
<button pButton class="p-button-rounded p-button-warning ml-2" (click)="exportDoctors()"
[title]="'::export' | abpLocalization"
>
<i class="pi pi-download"></i>
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="firstName">
{{ '::name' | abpLocalization }}
<p-sortIcon field="firstName" />
</th>
<th pSortableColumn="mobile">
{{ '::mobile' | abpLocalization }}
<p-sortIcon field="mobile" />
</th>
<th pSortableColumn="email">
{{ '::email' | abpLocalization }}
<p-sortIcon field="email" />
</th>
<th pSortableColumn="specialization">
{{ '::specialization' | abpLocalization }}
<p-sortIcon field="specialization" />
</th>
<th pSortableColumn="experience">
{{ '::experience' | abpLocalization }}
<p-sortIcon field="experience" />
</th>
<th pSortableColumn="rating">
{{ '::rating' | abpLocalization }}
<p-sortIcon field="rating" />
</th>
<th>{{ '::actions' | abpLocalization }}</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-doctor>
<tr>
<td>{{ '::doctorPrefix' | abpLocalization }} {{ doctor.firstName }} {{ doctor.lastName }}</td>
<td>{{ doctor.mobile }}</td>
<td>{{ doctor.email }}</td>
<td>{{ doctor.specialization }}</td>
<td>{{ doctor.experience }} {{ '::years' | abpLocalization }}</td>
<td>{{ doctor.rating }}</td>
<td class="d-flex">
<button class="btn btn-warning btn-sm ml-1" (click)="editDoctor(doctor)">
<i class="pi pi-pencil"></i>
</button>
<button class="btn btn-danger btn-sm ml-1" (click)="deleteDoctor(doctor.id)">
<i class="pi pi-trash"></i>
</button>
<button class="btn btn-warning btn-sm ml-1" (click)="GotoShiftManagement(doctor.id)">
<i class="pi pi-arrow-right"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
<app-doctor-dialog
[Id]="DoctorIdToEdit"
[isEditMode]="isEditMode"
[visible]="isModalVisible"
*ngIf="isModalVisible"
[name]="'::doctor' | abpLocalization"
(close)="closeDialog()"
></app-doctor-dialog>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DoctorsComponent } from './doctors.component';
describe('DoctorsComponent', () => {
let component: DoctorsComponent;
let fixture: ComponentFixture<DoctorsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DoctorsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DoctorsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,112 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { DoctorService } from '@proxy/doctors';
import { PagingSortResultDto } from '@proxy/dto';
import { workScheduleOptions } from '@proxy/global-enum';
@Component({
selector: 'app-doctors',
templateUrl: './doctors.component.html',
styleUrl: './doctors.component.scss',
})
export class DoctorsComponent implements OnInit {
totalRecords: number = 0;
doctors = [];
loading: boolean = false;
params: PagingSortResultDto;
isModalVisible: boolean = false;
isEditMode: boolean = false;
DoctorIdToEdit: string;
ngOnInit(): void {
this.loadDoctors({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
constructor(
private DoctorService: DoctorService,
private router: Router
) {}
workSchedule = Object.keys(workScheduleOptions)
.filter(key => !isNaN(Number(key)))
.map(key => ({
label: workScheduleOptions[key as unknown as keyof typeof workScheduleOptions],
value: Number(key),
}));
loadDoctors(event: any) {
this.loading = true;
let order = event.sortOrder == 1 ? ' asc' : ' desc';
event.sortField = event.sortField == undefined ? 'id' : event.sortField;
this.params = {
skipCount: event.first,
maxResultCount: event.rows,
sorting: event.sortField + order,
search: event.globalFilter == null ? '' : event.globalFilter,
};
this.DoctorService.getDoctorList(this.params).subscribe(data => {
this.doctors = data.items;
this.totalRecords = data.totalCount;
this.loading = false;
});
}
openNewDoctorDialog() {
this.isModalVisible = true;
this.isEditMode = false;
}
editDoctor(Doctor: any) {
this.isEditMode = true;
this.DoctorIdToEdit = Doctor.id;
this.isModalVisible = true;
}
closeDialog() {
this.isModalVisible = false;
this.loadDoctors({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
exportDoctors() {
this.DoctorService.getExportDoctorsRecord().subscribe(result => {
const binary = atob(result.fileContent);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'application/xlsx' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = result.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
});
}
GotoShiftManagement(id:string) {
this.router.navigate(['/doctors/shift-management', id]);
}
}

View File

@ -0,0 +1,44 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DoctorsRoutingModule } from './doctors-routing.module';
import { DoctorsComponent } from './doctors.component';
import { FormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { CalendarModule } from 'primeng/calendar';
import { ChipModule } from 'primeng/chip';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { RadioButtonModule } from 'primeng/radiobutton';
import { TableModule } from 'primeng/table';
import { DoctorDialogComponent } from './doctor-dialog.component';
import { ShiftManagementComponent } from './shift-management/shift-management.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [DoctorsComponent,ShiftManagementComponent],
imports: [
CommonModule,
DoctorsRoutingModule,
TableModule,
DialogModule,
FormsModule,
TableModule,
ButtonModule,
DialogModule,
InputTextModule,
CalendarModule,
DropdownModule,
RadioButtonModule,
InputTextareaModule,
ChipModule,
DoctorDialogComponent,
SharedModule
],
})
export class DoctorsModule {}

View File

@ -0,0 +1,52 @@
<div class="card p-4 shift-management">
<h4>Shift Management</h4>
<div class="field">
<p-dropdown
[options]="doctors"
[(ngModel)]="selectedDoctor"
placeholder="Select Doctor"
optionLabel="label"
optionValue="value"
class="w-full"
(onChange)="onDoctorSelect($event.value)"
></p-dropdown>
</div>
<div *ngIf="selectedDoctor" class="shift-grid-container">
<div class="shift-column-layout">
<!-- Column Layout for Days & Time Slots -->
<div *ngFor="let day of selectedDays" class="shift-column">
<div class="day-header">
<strong>{{ day }}</strong>
</div>
<p-dropdown
[options]="timeSlotOptions"
[(ngModel)]="selectedTimeSlots[day]"
optionLabel="label"
optionValue="value"
placeholder="Select Time Slot"
class="w-full"
></p-dropdown>
</div>
</div>
<!-- Buttons -->
<div class="button-container">
<button
pButton
type="button"
label="Save Shift"
class="p-button-success mt-3"
(click)="saveShiftData()"
></button>
<button
pButton
type="button"
label="Back"
class="p-button-primary mt-3"
(click)="backToDoctor()"
></button>
</div>
</div>
</div>

View File

@ -0,0 +1,66 @@
.shift-column-layout {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
.shift-column {
display: flex;
flex-direction: column;
align-items: center;
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
min-width: 150px;
max-width: 180px;
flex: 1;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease-in-out;
}
.shift-column:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.day-header {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: #007bff;
text-transform: uppercase;
}
.dropdown-item {
width: 100%;
}
/* Center the button (optional) */
.button-container {
margin: 1rem 0;
}
/* Styling for the dialog */
.availability-dialog .p-dialog-header {
background-color: #1976d2;
color: #fff;
font-weight: 600;
}
.availability-dialog .p-dialog-content {
padding: 1.5rem;
}
.footer-buttons {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
/* Optional: Adjust field spacing */
.field {
margin-bottom: 1rem;
}

View File

@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditAppointmentComponent } from './edit-appointment.component';
import { ShiftManagementComponent } from './shift-management.component';
describe('EditAppointmentComponent', () => {
let component: EditAppointmentComponent;
let fixture: ComponentFixture<EditAppointmentComponent>;
describe('ShiftManagementComponent', () => {
let component: ShiftManagementComponent;
let fixture: ComponentFixture<ShiftManagementComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EditAppointmentComponent]
imports: [ShiftManagementComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EditAppointmentComponent);
fixture = TestBed.createComponent(ShiftManagementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -0,0 +1,173 @@
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { DoctorService } from '@proxy/doctors';
import { CreateUpdateShiftManagementDto } from '@proxy/doctors/dto';
import { TimeSlot, WorkSchedule } from '@proxy/global-enum';
import { ShiftManagementService } from '@proxy/shift-management';
@Component({
selector: 'app-shift-management',
templateUrl: './shift-management.component.html',
styleUrl: './shift-management.component.scss',
})
export class ShiftManagementComponent implements OnInit {
doctors = [];
selectedDoctor: string | null = null;
selectedDoctorName = '';
selectedDoctorEmail = '';
selectedDoctorPhone = '';
selectedDoctorSpecialty = '';
selectedDays = [];
selectedTimeSlots: { [key: string]: TimeSlot } = {};
DoctorID: string;
constructor(
private DoctorService: DoctorService,
private shiftService: ShiftManagementService,
private toaster: ToasterService,
private router: Router,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.getdoctorlist();
setTimeout(() => {
this.DoctorID = this.route.snapshot.paramMap.get('id');
this.onDoctorSelect(this.DoctorID);
}, 10);
}
getdoctorlist() {
this.DoctorService.get().subscribe(result => {
const doctors = result;
this.doctors = doctors.map(doctor => ({
label: `Dr. ${doctor.firstName} ${doctor.lastName}`,
value: doctor.id,
availability: doctor.availability,
email: doctor.email,
phoneNo: doctor.mobile,
specialist: doctor.specialization,
}));
});
}
shiftData: CreateUpdateShiftManagementDto = {
doctorId: this.selectedDoctor,
shifts: {} as Record<WorkSchedule, TimeSlot>,
isActive: true,
};
workScheduleOptions = Object.keys(WorkSchedule)
.filter(key => isNaN(Number(key)))
.map(key => ({
label: key.replace(/([A-Z])/g, ' $1').trim(),
value: WorkSchedule[key as keyof typeof WorkSchedule],
}));
getTimeRangeLabel(value: TimeSlot): string {
const timeRanges = {
[TimeSlot.TenToSeven]: { start: 10, end: 19 },
[TimeSlot.NineToFive]: { start: 9, end: 17 },
[TimeSlot.EightToFour]: { start: 8, end: 16 },
[TimeSlot.SevenToFour]: { start: 7, end: 16 },
[TimeSlot.SixToThree]: { start: 6, end: 15 },
[TimeSlot.TwelveToNine]: { start: 12, end: 21 },
[TimeSlot.TenToSix]: { start: 10, end: 18 },
[TimeSlot.ElevenToEight]: { start: 11, end: 20 },
};
const { start, end } = timeRanges[value];
const formatTime = (hour: number): string => {
const period = hour >= 12 ? 'PM' : 'AM';
const formattedHour = hour % 12 || 12;
return `${formattedHour}:00 ${period}`;
};
return `${formatTime(start)} - ${formatTime(end)}`;
}
timeSlotOptions = Object.keys(TimeSlot)
.filter(key => isNaN(Number(key)))
.map(key => ({
label: this.getTimeRangeLabel(TimeSlot[key as keyof typeof TimeSlot]),
value: TimeSlot[key as keyof typeof TimeSlot],
}));
onDoctorSelect(doctorId: string) {
const selectedDoctor = this.doctors.find(doctor => doctor.value === doctorId);
if (selectedDoctor) {
this.selectedDoctorName = selectedDoctor.label;
this.selectedDoctorEmail = selectedDoctor.email;
this.selectedDoctorPhone = selectedDoctor.phoneNo;
this.selectedDoctorSpecialty = selectedDoctor.specialist;
this.shiftService.getShiftsByDoctorId(doctorId).subscribe(data => {
if (data && data.shifts && Object.keys(data.shifts).length > 0) {
this.selectedTimeSlots = {};
this.selectedDays = Object.keys(data.shifts).map(key => {
return this.daysOfWeek[WorkSchedule[key as keyof typeof WorkSchedule] - 1];
});
for (const [day, timeSlot] of Object.entries(data.shifts)) {
const dayEnumKey = WorkSchedule[day as keyof typeof WorkSchedule];
if (dayEnumKey !== undefined) {
this.selectedTimeSlots[this.daysOfWeek[dayEnumKey - 1]] = timeSlot;
}
}
} else {
this.selectedDays = this.getDaysFromSchedule(selectedDoctor.availability);
this.selectedDays.forEach(day => {
this.selectedTimeSlots[day] = TimeSlot.NineToFive;
});
}
});
}
}
daysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
getDaysFromSchedule(scheduleValue: WorkSchedule): string[] {
let days: string[] = [];
if (scheduleValue >= 1 && scheduleValue <= 7) {
days.push(this.daysOfWeek[scheduleValue - 1]);
} else {
const startDayIndex = (scheduleValue - 8) % 7;
const endDayIndex = (startDayIndex + 4) % 7;
for (let i = 0; i < 5; i++) {
days.push(this.daysOfWeek[(startDayIndex + i) % 7]);
}
}
return days;
}
saveShiftData() {
if (!this.selectedDoctor) {
return;
}
this.selectedDays;
const shiftData: CreateUpdateShiftManagementDto = {
doctorId: this.selectedDoctor,
shifts: Object.keys(this.selectedTimeSlots).reduce((acc, day) => {
const timeSlot = this.selectedTimeSlots[day]; // Get selected time slot
const dayEnumKey = WorkSchedule[day as keyof typeof WorkSchedule]; // Convert string to WorkSchedule enum
if (timeSlot !== undefined && dayEnumKey !== undefined) {
acc[dayEnumKey] = timeSlot; // Store correctly in shifts
}
return acc;
}, {} as Record<WorkSchedule, TimeSlot>),
isActive: true,
};
this.shiftService.createOrUpdateShift(shiftData).subscribe({
next: response => {
this.toaster.success('Updated Successfully', 'Success');
},
error: err => {
this.toaster.error(err, 'Error');
},
});
}
backToDoctor() {
this.router.navigate(['/doctors']);
}
}

View File

@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IdentityModule } from '@abp/ng.identity';
import { TableModule } from 'primeng/table';
import { ButtonModule } from 'primeng/button';
import { CustomRolesComponent } from './custom-roles/custom-roles.component';
@NgModule({
declarations: [],
imports: [
CommonModule,
IdentityModule,
TableModule,
ButtonModule,
CustomRolesComponent
],
exports:[
CustomRolesComponent
]
})
export class CustomIdentityModule { }

View File

@ -0,0 +1,154 @@
<abp-page [title]="'AbpIdentity::Roles' | abpLocalization" [toolbar]="data.items">
<div id="identity-roles-wrapper" class="card">
<div class="card-body">
<!-- <abp-extensible-table
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table> -->
<div class="flex justify-content-between align-items-center mb-3">
<button
pButton
icon="pi pi-plus"
label="{{ '::addRoles' | abpLocalization }}"
class="p-button-success"
(click)="add()"
></button>
</div>
<p-table [value]="data.items" [paginator]="true" [rows]="5" responsiveLayout="scroll">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">
{{ 'AbpIdentity::RoleName' | abpLocalization }}
<p-sortIcon field="name"></p-sortIcon>
</th>
<th>{{ 'AbpIdentity::Actions' | abpLocalization }}</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-role>
<tr>
<td>{{ role.name }}</td>
<td>
<button
pButton
icon="pi pi-pencil"
class="p-button-text p-button-primary"
(click)="edit(role.id)"
title="{{ 'AbpIdentity::Edit' | abpLocalization }}"
></button>
<button
pButton
icon="pi pi-trash"
class="p-button-text p-button-danger"
(click)="delete(role.id, role.name)"
title="{{ 'AbpIdentity::Delete' | abpLocalization }}"
></button>
<button
pButton
icon="pi pi-lock"
class="p-button-text p-button-success"
(click)="openPermissionsModal(role.name)"
title="{{ 'AbpIdentity::Permissions' | abpLocalization }}"
></button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}</h3>
</ng-template>
<!-- <ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()" validateOnSubmit>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template> -->
<ng-template #abpBody>
<form #roleForm="ngForm" (ngSubmit)="save()" validateOnSubmit>
<!-- Name Field -->
<div class="form-group">
<label for="name">{{ 'AbpIdentity::Name' | abpLocalization }}</label>
<input
id="name"
type="text"
class="form-control"
[(ngModel)]="selected.name"
name="name"
required
#nameCtrl="ngModel"
/>
<small class="text-danger" *ngIf="nameCtrl.invalid && nameCtrl.touched">
{{ 'AbpIdentity::NameIsRequired' | abpLocalization }}
</small>
</div>
<!-- Is Default Checkbox -->
<div class="form-check">
<input
id="isDefault"
type="checkbox"
class="form-check-input"
[(ngModel)]="selected.isDefault"
name="isDefault"
/>
<label for="isDefault" class="form-check-label">
{{ 'AbpIdentity::Default' | abpLocalization }}
</label>
</div>
<!-- Is Public Checkbox -->
<div class="form-check">
<input
id="isPublic"
type="checkbox"
class="form-check-input"
[(ngModel)]="selected.isPublic"
name="isPublic"
/>
<label for="isPublic" class="form-check-label">
{{ 'AbpIdentity::Public' | abpLocalization }}
</label>
</div>
<br />
<abp-button iconClass="fa fa-check" [disabled]="roleForm.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-outline-primary" abpClose>
{{ 'AbpIdentity::Cancel' | abpLocalization }}
</button>
<!-- <abp-button iconClass="fa fa-check" [disabled]="roleForm?.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button> -->
</ng-template>
</abp-modal>
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'R' },
providerKey: { value: providerKey },
visible: { value: visiblePermissions, twoWay: true },
hideBadges: { value: true }
},
outputs: { visibleChange: onVisiblePermissionChange },
componentKey: permissionManagementKey
};
let init = initTemplate
"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
</abp-page>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomRolesComponent } from './custom-roles.component';
describe('CustomRolesComponent', () => {
let component: CustomRolesComponent;
let fixture: ComponentFixture<CustomRolesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CustomRolesComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CustomRolesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,215 @@
import {
ExtensibleModule,
EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps,
} from '@abp/ng.components/extensible';
import { PageModule } from '@abp/ng.components/page';
import {
CoreModule,
ListResultDto,
ListService,
LocalizationModule,
PagedAndSortedResultRequestDto,
PagedResultDto,
} from '@abp/ng.core';
import { IdentityRoleCreateDto, IdentityRoleDto, IdentityRoleService, IdentityRoleUpdateDto } from '@abp/ng.identity/proxy';
import {
ThemeSharedModule,
ConfirmationService,
ToasterService,
Confirmation,
} from '@abp/ng.theme.shared';
import { CommonModule } from '@angular/common';
import { Component, inject, Injector, OnInit } from '@angular/core';
import { FormControl, FormGroup, FormsModule, UntypedFormGroup, Validators } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { TableModule } from 'primeng/table';
import { finalize } from 'rxjs';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { eIdentityComponents } from '@abp/ng.identity';
import { PermissionManagementModule } from '@abp/ng.permission-management';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { MenuItem } from 'primeng/api';
import { Dropdown, DropdownModule } from 'primeng/dropdown';
import { SharedModule } from 'src/app/shared/shared.module';
@Component({
selector: 'app-custom-roles',
standalone: true,
imports: [
CommonModule,
FormsModule,
TableModule,
ButtonModule,
PageModule,
LocalizationModule,
ThemeSharedModule,
NgxDatatableModule,
ExtensibleModule,
PermissionManagementModule,
CoreModule,
NgbNavModule,
DropdownModule,
SharedModule
],
templateUrl: './custom-roles.component.html',
providers: [
ListService,
{
provide: EXTENSIONS_IDENTIFIER,
useValue: eIdentityComponents.Roles,
},
],
styleUrl: './custom-roles.component.scss',
})
export class CustomRolesComponent implements OnInit {
protected readonly list = inject(ListService<PagedAndSortedResultRequestDto>);
protected readonly confirmationService = inject(ConfirmationService);
protected readonly toasterService = inject(ToasterService);
private readonly injector = inject(Injector);
protected readonly service = inject(IdentityRoleService);
data: PagedResultDto<IdentityRoleDto> = { items: [], totalCount: 0 };
form!: UntypedFormGroup;
selected?: IdentityRoleDto;
isModalVisible!: boolean;
visiblePermissions = false;
providerKey?: string;
modalBusy = false;
permissionManagementKey = ePermissionManagementComponents.PermissionManagement;
onVisiblePermissionChange = (event: boolean) => {
this.visiblePermissions = event;
};
ngOnInit() {
this.hookToQuery();
}
// buildForm() {
// const data = new FormPropData(this.injector, this.selected);
// this.form = generateFormFromProps(data);
// }
buildForm() {
this.form = new FormGroup({
name: new FormControl(this.selected?.name || '', Validators.required),
isDefault: new FormControl(this.selected?.isDefault || false),
isPublic: new FormControl(this.selected?.isPublic || false)
});
}
openModal() {
this.buildForm();
this.isModalVisible = true;
}
add() {
debugger
// this.selected = {} as IdentityRoleDto;
// this.openModal();
this.selected = { name: '', isDefault: false, isPublic: false } as IdentityRoleDto;
this.isModalVisible = true;
}
edit(id: string) {
debugger
// this.service.get(id).subscribe(res => {
// this.selected = res;
// this.openModal();
// });
this.service.get(id).subscribe(res => {
this.selected = res;
this.isModalVisible = true;
});
}
// save() {
// debugger
// if (!this.form.valid) return;
// this.modalBusy = true;
// const { id } = this.selected || {};
// (id
// ? this.service.update(id, { ...this.selected, ...this.form.value })
// : this.service.create(this.form.value)
// )
// .pipe(finalize(() => (this.modalBusy = false)))
// .subscribe(() => {
// this.isModalVisible = false;
// this.toasterService.success('AbpUi::SavedSuccessfully');
// this.list.get();
// });
// }
save() {
debugger
if (!this.selected?.name) {
return;
}
this.modalBusy = true;
const { id, name, isDefault, isPublic, concurrencyStamp } = this.selected;
const roleDto = {
name,
isDefault,
isPublic,
concurrencyStamp
};
const saveObservable = id
? this.service.update(id, roleDto) // Update call
: this.service.create(roleDto); // Create call
saveObservable
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.isModalVisible = false;
this.toasterService.success('AbpUi::SavedSuccessfully');
this.list.get();
});
}
delete(id: string, name: string) {
this.confirmationService
.warn('AbpIdentity::RoleDeletionConfirmationMessage', 'AbpIdentity::AreYouSure', {
messageLocalizationParams: [name],
})
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.toasterService.success('AbpUi::DeletedSuccessfully');
this.service.delete(id).subscribe(() => this.list.get());
}
});
}
private hookToQuery() {
this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res));
}
openPermissionsModal(providerKey: string) {
this.providerKey = providerKey;
setTimeout(() => {
this.visiblePermissions = true;
}, 0);
}
sort(data: any) {
const { prop, dir } = data.sorts[0];
this.list.sortKey = prop;
this.list.sortOrder = dir;
}
}

View File

@ -0,0 +1,355 @@
<!-- <div>
<h3>Total Users: {{ data.totalCount }}</h3>
<ul>
<li *ngFor="let user of data.items">
<strong>{{ user.userName }}</strong> ({{ user.email }})
</li>
</ul>
</div> -->
<abp-page [title]="'AbpIdentity::Users' | abpLocalization" [toolbar]="data.items">
<div id="identity-roles-wrapper" class="card">
<div class="card-body">
<div id="data-tables-table-filter" class="data-tables-filter mb-3">
<div class="flex justify-content-between align-items-center mb-3">
<button
pButton
icon="pi pi-plus"
label="{{ '::addUser' | abpLocalization }}"
class="p-button-success"
(click)="add()"
></button>
</div>
<div class="input-group">
<input
type="search"
class="form-control"
[placeholder]="'AbpUi::PagerSearch' | abpLocalization"
[(ngModel)]="list.filter"
/>
</div>
</div>
<!--
<abp-extensible-table
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table> -->
<p-table
[value]="data.items"
[tableStyle]="{ 'min-width': '60rem' }"
[paginator]="true"
[rows]="5"
responsiveLayout="scroll"
>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="userName">
{{ '::username' | abpLocalization }}
<p-sortIcon field="userName"></p-sortIcon>
</th>
<th pSortableColumn="name">
{{ '::firstName' | abpLocalization }}
<p-sortIcon field="name"></p-sortIcon>
</th>
<th pSortableColumn="surname">
{{ '::lastName' | abpLocalization }}
<p-sortIcon field="surname"></p-sortIcon>
</th>
<th pSortableColumn="email">
{{ '::email' | abpLocalization }}
<p-sortIcon field="email"></p-sortIcon>
</th>
<th pSortableColumn="phoneNumber">
{{ '::phoneNumber' | abpLocalization }}
<p-sortIcon field="phoneNumber"></p-sortIcon>
</th>
<th>{{ '::status' | abpLocalization }}</th>
<th>{{ '::actions' | abpLocalization }}</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user>
<tr>
<td>{{ user.userName }}</td>
<td>{{ user.name }}</td>
<td>{{ user.surname }}</td>
<td>{{ user.email }}</td>
<td>{{ user.phoneNumber }}</td>
<td>
<p-tag
[value]="user.isActive ? 'Active' : 'Inactive'"
[severity]="user.isActive ? 'success' : 'danger'"
></p-tag>
</td>
<td>
<div class="btn-group" dropdown placement="bottom left" container="body">
<p-splitButton
label="{{ '::actions' | abpLocalization }}"
icon="pi pi-cog"
class="p-button-sm p-button-primary"
[model]="actionItems"
(onClick)="editUser(user.id)"
>
</p-splitButton>
</div>
<!-- <button
pButton
icon="pi pi-pencil"
class="p-button-text p-button-primary"
(click)="edit(user.id)"
></button>
<button
pButton
icon="pi pi-trash"
class="p-button-text p-button-danger"
(click)="delete(user.id, user.userName)"
></button>
<button
pButton
icon="pi pi-lock"
class="p-button-text p-button-success"
(click)="openPermissionsModal(user.id, user.userName)"
></button> -->
<!-- <div class="btn-group" dropdown placement="bottom left" container="body">
<button
id="dropdownButton"
type="button"
class="btn btn-primary btn-sm dropdown-toggle"
dropdownToggle
aria-controls="dropdownMenu"
(click)="toggleDropdown()"
>
<i class="fa fa-cog"></i>
<span class="caret"></span>
Actions
</button>
<ul
id="dropdownMenu"
#dropdownMenu
class="dropdown-menu"
role="menu"
aria-labelledby="dropdownButton"
>
<li role="menuitem" tabindex="0">
<a
#firstDropdownItem
class="dropdown-item"
href="javascript:;"
(click)="edit(user.id)"
>
Edit
</a>
</li>
</ul>
</div> -->
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>
{{ (selected?.id ? 'AbpIdentity::EditUser' : 'AbpIdentity::NewUser') | abpLocalization }}
</h3>
</ng-template>
<ng-template #abpBody>
<form #userForm="ngForm" (ngSubmit)="save(userForm)">
<!-- User Information Tab -->
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>
<ng-template ngbNavContent>
<div class="form-group">
<label for="userName">
{{ '::userName' | abpLocalization }}
</label>
<input
id="userName"
type="text"
class="form-control"
[(ngModel)]="selected.userName"
name="userName"
required
#userName="ngModel"
/>
<div *ngIf="userName.invalid && userName.touched" class="text-danger">
{{ '::userNameIsRequired' | abpLocalization }}
</div>
</div>
<div class="form-group" *ngIf="!selected.id">
<label for="password">
{{ '::password' | abpLocalization }}
</label>
<input
id="password"
type="password"
class="form-control"
[(ngModel)]="password"
name="password"
required
#passwordField="ngModel"
/>
<div *ngIf="passwordField.invalid && passwordField.touched" class="text-danger">
{{ '::passwordIsRequired' | abpLocalization }}
</div>
</div>
<div class="form-group">
<label for="name">
{{ '::firstName' | abpLocalization }}
</label>
<input
id="name"
type="text"
class="form-control"
[(ngModel)]="selected.name"
name="name"
#name="ngModel"
/>
</div>
<div class="form-group">
<label for="surname">
{{ '::lastName' | abpLocalization }}
</label>
<input
id="surname"
type="text"
class="form-control"
[(ngModel)]="selected.surname"
name="surname"
#surname="ngModel"
/>
</div>
<!-- Email -->
<div class="form-group">
<label for="email">
{{ '::email' | abpLocalization }}
</label>
<input
id="email"
type="email"
class="form-control"
[(ngModel)]="selected.email"
name="email"
required
#email="ngModel"
/>
<div *ngIf="email.invalid && email.touched" class="text-danger">
{{ '::validEmailIsRequired' | abpLocalization }}
</div>
</div>
<div class="form-group">
<label for="phoneNumber">
{{ '::phoneNumber' | abpLocalization }}
</label>
<input
id="phoneNumber"
type="text"
class="form-control"
[(ngModel)]="selected.phoneNumber"
name="phoneNumber"
#phoneNumber="ngModel"
/>
</div>
<div class="form-check">
<input
id="isActive"
type="checkbox"
class="form-check-input"
[(ngModel)]="selected.isActive"
name="isActive"
/>
<label for="isActive" class="form-check-label">
{{ '::active' | abpLocalization }}
</label>
</div>
<div class="form-check">
<input
id="lockoutEnabled"
type="checkbox"
class="form-check-input"
[(ngModel)]="selected.lockoutEnabled"
name="lockoutEnabled"
/>
<label for="lockoutEnabled" class="form-check-label">
{{ '::lockoutEnabled' | abpLocalization }}
</label>
</div>
</ng-template>
</li>
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::Roles' | abpLocalization }}</a>
<ng-template ngbNavContent>
<div *ngFor="let role of roles; let i = index">
<div class="form-check mb-2">
<label class="form-check-label">
<input
type="checkbox"
class="form-check-input"
[(ngModel)]="role.selected"
name="role-{{ i }}"
/>
{{ role.name }}
</label>
</div>
</div>
</ng-template>
</li>
</ul>
<div class="mt-2 fade-in-top" [ngbNavOutlet]="nav"></div>
<div class="mt-3">
<button type="submit" class="btn btn-primary" [disabled]="userForm.invalid">
{{ '::save' | abpLocalization }}
</button>
</div>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-outline-primary" abpClose>
{{ '::cancel' | abpLocalization }}
</button>
<!-- <abp-button iconClass="fa fa-check" [disabled]="form?.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button> -->
</ng-template>
</abp-modal>
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'U' },
providerKey: { value: providerKey },
visible: { value: visiblePermissions, twoWay: true }
},
outputs: { visibleChange: onVisiblePermissionChange },
componentKey: permissionManagementKey
};
let init = initTemplate
"
[entityDisplayName]="entityDisplayName"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
</abp-page>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomUsersComponent } from './custom-users.component';
describe('CustomUsersComponent', () => {
let component: CustomUsersComponent;
let fixture: ComponentFixture<CustomUsersComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CustomUsersComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CustomUsersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,396 @@
import {
FormPropData,
EXTENSIONS_IDENTIFIER,
generateFormFromProps,
ExtensibleModule,
} from '@abp/ng.components/extensible';
import { PageModule } from '@abp/ng.components/page';
import {
CoreModule,
ListResultDto,
ListService,
LocalizationModule,
PagedResultDto,
} from '@abp/ng.core';
import { eIdentityComponents } from '@abp/ng.identity';
import {
GetIdentityUsersInput,
IdentityRoleDto,
IdentityUserDto,
IdentityUserService,
IdentityUserUpdateDto,
} from '@abp/ng.identity/proxy';
import { CommonModule } from '@angular/common';
import {
Component,
ElementRef,
HostListener,
inject,
Injector,
OnInit,
TemplateRef,
TrackByFunction,
ViewChild,
} from '@angular/core';
import {
AbstractControl,
FormsModule,
NgForm,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormGroup,
} from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { TableModule } from 'primeng/table';
import {
Confirmation,
eFormComponets,
ThemeSharedModule,
ToasterService,
ConfirmationService,
} from '@abp/ng.theme.shared';
import { finalize, switchMap, tap } from 'rxjs';
import {
ePermissionManagementComponents,
PermissionManagementModule,
} from '@abp/ng.permission-management';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { PaginatorModule } from 'primeng/paginator';
import { TagModule } from 'primeng/tag'; // For active/inactive status
import { SplitButtonModule } from 'primeng/splitbutton';
import { SharedModule } from 'src/app/shared/shared.module';
@Component({
selector: 'app-custom-users',
standalone: true,
imports: [
CommonModule,
FormsModule,
TableModule,
ButtonModule,
PageModule,
LocalizationModule,
ThemeSharedModule,
NgxDatatableModule,
ExtensibleModule,
PermissionManagementModule,
CoreModule,
NgbNavModule,
PaginatorModule,
TagModule,SplitButtonModule,SharedModule
],
templateUrl: './custom-users.component.html',
providers: [
ListService,
{
provide: EXTENSIONS_IDENTIFIER,
useValue: eIdentityComponents.Users,
},
],
styleUrl: './custom-users.component.scss',
})
export class CustomUsersComponent implements OnInit {
protected readonly list = inject(ListService<GetIdentityUsersInput>);
protected readonly confirmationService = inject(ConfirmationService);
protected readonly service = inject(IdentityUserService);
protected readonly toasterService = inject(ToasterService);
private readonly fb = inject(UntypedFormBuilder);
private readonly injector = inject(Injector);
@ViewChild('firstDropdownItem') firstDropdownItem: ElementRef;
data: PagedResultDto<IdentityUserDto> = { items: [], totalCount: 0 };
@ViewChild('modalContent', { static: false })
modalContent!: TemplateRef<any>;
form!: UntypedFormGroup;
password:string;
selected?: IdentityUserDto;
selectedUserRoles?: IdentityRoleDto[];
// roles?: IdentityRoleDto[];
roles: (IdentityRoleDto & { selected: boolean })[] = []; // Use intersection type to add 'selected'
visiblePermissions = false;
providerKey?: string;
isModalVisible?: boolean;
modalBusy = false;
permissionManagementKey = ePermissionManagementComponents.PermissionManagement;
entityDisplayName: string;
inputKey = eFormComponets.FormCheckboxComponent;
trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index;
isDropdownOpen = false;
toggleDropdown() {
this.isDropdownOpen = !this.isDropdownOpen;
// After the dropdown is shown, focus the first item in the dropdown
setTimeout(() => {
if (this.firstDropdownItem) {
this.firstDropdownItem.nativeElement.focus();
}
}, 100); // Delay to ensure dropdown is rendered
}
closeDropdown() {
this.isDropdownOpen = false;
}
actionItems = [
{ label: 'Edit', icon: 'pi pi-pencil', command: () => this.edit("") },
{ label: 'Delete', icon: 'pi pi-trash', command: () => this.delete("","") },
// { label: 'Permissions', icon: 'pi pi-shield', command: () => this.openPermissionsModal("") },
];
onVisiblePermissionChange = (event: boolean) => {
debugger
this.visiblePermissions = event;
};
get roleGroups(): UntypedFormGroup[] {
return ((this.form.get('roleNames') as UntypedFormArray)?.controls as UntypedFormGroup[]) || [];
}
ngOnInit() {
this.hookToQuery();
}
formValid() {
return true;
}
// buildForm() {
// debugger
// const data = new FormPropData(this.injector, this.selected);
// this.form = generateFormFromProps(data);
// this.service.getAssignableRoles().subscribe(({ items }) => {
// this.roles = items;
// if (this.roles) {
// this.form.addControl(
// 'roleNames',
// this.fb.array(
// this.roles.map(role =>
// this.fb.group({
// [role.name as string]: [
// this.selected?.id
// ? !!this.selectedUserRoles?.find(userRole => userRole.id === role.id)
// : role.isDefault,
// ],
// }),
// ),
// ),
// );
// }
// });
// }
// openModal() {
// this.buildForm();
// this.isModalVisible = true;
// }
// add() {
// debugger
// this.selected = {} as IdentityUserDto;
// this.selectedUserRoles = [] as IdentityRoleDto[];
// this.openModal();
// }
// edit(id: string) {
// debugger
// this.service
// .get(id)
// .pipe(
// tap(user => (this.selected = user)),
// switchMap(() => this.service.getRoles(id)),
// )
// .subscribe(userRole => {
// debugger
// this.selectedUserRoles = userRole.items || [];
// this.openModal();
// });
// }
// save() {
// if (!this.form.valid || this.modalBusy) return;
// this.modalBusy = true;
// const { roleNames = [] } = this.form.value;
// const mappedRoleNames =
// roleNames
// .filter((role: { [key: string]: any }) => !!role[Object.keys(role)[0]])
// .map((role: { [key: string]: any }) => Object.keys(role)[0]) || [];
// const { id } = this.selected || {};
// (id
// ? this.service.update(id, {
// ...this.selected,
// ...this.form.value,
// roleNames: mappedRoleNames,
// })
// : this.service.create({ ...this.form.value, roleNames: mappedRoleNames })
// )
// .pipe(finalize(() => (this.modalBusy = false)))
// .subscribe(() => {
// this.isModalVisible = false;
// this.toasterService.success('AbpUi::SavedSuccessfully');
// this.list.get();
// });
// }
buildForm() {
this.service.getAssignableRoles().subscribe(({ items }) => {
this.roles = items.map(role => ({
...role,
selected: this.selectedUserRoles.some(userRole => userRole.id === role.id),
}));
// Initialize the selected roles based on existing data for selected user
if (this.selected?.id) {
this.roles.forEach(role => {
role.selected = this.selectedUserRoles.some(userRole => userRole.id === role.id);
});
} else {
// Default values when creating a new user
this.roles.forEach(role => {
role.selected = role.isDefault;
});
}
});
}
openModal() {
this.loadRoles(); // Load roles when modal opens
this.isModalVisible = true;
}
loadRoles() {
this.service.getAssignableRoles().subscribe(({ items }) => {
this.roles = items.map(role => ({
...role, // spread IdentityRoleDto properties
selected: false, // Initially no role is selected
}));
});
}
add() {
this.selected = {} as IdentityUserDto;
this.selectedUserRoles = [] as IdentityRoleDto[];
this.password = ''; // Store password separately
this.openModal();
}
edit(id: string) {
this.service
.get(id)
.pipe(finalize(() => this.buildForm()))
.subscribe(user => {
this.selected = user;
this.service.getRoles(id).subscribe(userRoles => {
this.selectedUserRoles = userRoles.items || [];
});
});
this.openModal();
}
// Save user data (either create or update)
save(form: NgForm) {
debugger;
if (this.modalBusy || form.invalid) return;
this.modalBusy = true;
// Prepare roleNames array
// Prepare roleNames array
const selectedRoleNames = this.roles
.filter(role => role.selected) // Only include selected roles
.map(role => role.name); // Extract role names
// Create user payload
const userPayload: any = {
userName: this.selected.userName,
name: this.selected.name,
surname: this.selected.surname,
email: this.selected.email,
phoneNumber: this.selected.phoneNumber,
isActive: this.selected.isActive,
lockoutEnabled: this.selected.lockoutEnabled,
roleNames: selectedRoleNames, // Include selected roles
};
// Only include password if creating a new user
if (!this.selected.id && this.password) {
userPayload.password = this.password;
}
// Handle create or update
const saveObservable = this.selected.id
? this.service.update(this.selected.id, userPayload) // Update if id exists
: this.service.create(userPayload); // Create if no id
saveObservable.pipe(finalize(() => (this.modalBusy = false))).subscribe(() => {
this.isModalVisible = false;
this.toasterService.success('AbpUi::SavedSuccessfully');
this.list.get(); // Reload the list if needed
});
}
private getRoleNames() {
return this.roles.filter(role => role.selected).map(role => role.name);
}
delete(id: string, userName: string) {
this.confirmationService
.warn('AbpIdentity::UserDeletionConfirmationMessage', 'AbpIdentity::AreYouSure', {
messageLocalizationParams: [userName],
})
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.service.delete(id).subscribe(() => {
this.toasterService.success('AbpUi::DeletedSuccessfully');
this.list.get();
});
}
});
}
sort(data: any) {
const { prop, dir } = data.sorts[0];
this.list.sortKey = prop;
this.list.sortOrder = dir;
}
// private hookToQuery() {
// this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res));
// }
private hookToQuery() {
this.list
.hookToQuery(query => {
console.log('Query:', query); // Log the query
return this.service.getList(query);
})
.subscribe(res => {
debugger;
this.data = res;
});
}
openPermissionsModal(providerKey: string, entityDisplayName?: string) {
debugger;
this.providerKey = providerKey;
this.entityDisplayName = entityDisplayName;
setTimeout(() => {
this.visiblePermissions = true;
}, 0);
}
}

View File

@ -0,0 +1,335 @@
<!-- <div>
<p-table #dt2 dataKey="id" [value]="patients" [paginator]="true" [rows]="10" [totalRecords]="totalRecords"
[lazy]="true" (onLazyLoad)="loadPatient($event)" [rowsPerPageOptions]="[10, 20, 50]"
[responsiveLayout]="'scroll'" [globalFilterFields]="['id', 'name', 'status']"
[filters]="{ global: { value: '', matchMode: 'contains' } }" class="table table-striped">
<ng-template pTemplate="caption">
<div class="flex">
<h2 class="mr-auto">Patient List</h2>
<div>
<button *ngIf="createpermission" pButton class="p-button-rounded p-button-success ml-2"
(click)="openNewPatientDialog()">
<i class="pi pi-plus-circle"></i>
</button>
<button pButton class="p-button-rounded p-button-warning ml-2" (click)="exportPatient()">
<i class="pi pi-download"></i>
</button>
<p-iconField iconPosition="right" class="ml-2">
<p-inputIcon>
<i class="pi pi-search"></i>
</p-inputIcon>
<input pInputText type="text" (input)="dt2.filterGlobal($event.target.value, 'contains')"
[(ngModel)]="globalFilter" placeholder="Search keyword" />
</p-iconField>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th class="hidden" pSortableColumn="id">Patient ID <p-sortIcon field="id" /></th>
<th pSortableColumn="name">Full Name <p-sortIcon field="name" /></th>
<th pSortableColumn="gender">Gender <p-sortIcon field="gender" /></th>
<th pSortableColumn="admissionDate">Date of Admission <p-sortIcon field="admissionDate" /></th>
<th pSortableColumn="bloodGroup">Blood Group <p-sortIcon field="bloodGroup" /></th>
<th>Mobile</th>
<th>Email</th>
<th pSortableColumn="status">Status <p-sortIcon field="status" /></th>
<th>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-patient>
<tr>
<td class="hidden">{{ patient.id }}</td>
<td>{{ patient.name }}</td>
<td>
<span class="badge" [ngClass]="patient.gender === 1 ? 'male' : 'female'">
{{ gender[patient.gender] }}
</span>
</td>
<td>{{ patient.admissionDate | date }}</td>
<td>{{ patient.bloodGroup }}</td>
<td>{{ patient.mobile }}</td>
<td>{{ patient.email }}</td>
<td>{{ status[patient.status] }}</td>
<td class="d-flex">
<button *ngIf="createpermission" pButton class="btn btn-success btn-sm"
(click)="addnewrecord(patient.id)">
<i class="pi pi-arrow-right"></i>
</button>
<button *ngIf="editpermission" class="btn btn-warning btn-sm ml-1" (click)="editPatient(patient)"><i
class="pi pi-pencil"></i></button>
<button *ngIf="deletepermission" class="btn btn-danger btn-sm ml-1"
(click)="deletePatient(patient.id)"><i class="pi pi-trash"></i></button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="10">No Patients found.</td>
</tr>
</ng-template>
</p-table>
<span>Total Records: {{totalRecords}}</span>
</div> -->
<div class="container-fluid py-3">
<div class="card shadow-sm p-3">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Patient List</h2>
<div class="d-flex align-items-center gap-2">
<button *ngIf="createpermission" class="btn btn-success btn-sm" (click)="openNewPatientDialog()">
<i class="pi pi-plus-circle"></i>
</button>
<button class="btn btn-warning btn-sm" (click)="exportPatient()">
<i class="pi pi-download"></i>
</button>
<div class="input-group">
<span class="input-group-text"><i class="pi pi-search"></i></span>
<input pInputText type="text" class="form-control"
(input)="dt2.filterGlobal($event.target.value, 'contains')" [(ngModel)]="globalFilter"
placeholder="Search keyword" />
</div>
</div>
</div>
<p-table #dt2 dataKey="id" [value]="patients" [paginator]="true" [rows]="10" [totalRecords]="totalRecords"
[lazy]="true" (onLazyLoad)="loadPatient($event)" [rowsPerPageOptions]="[10, 20, 50]"
styleClass="p-datatable-gridlines" [responsiveLayout]="'scroll'"
[globalFilterFields]="['id', 'name', 'status']" [filters]="{ global: { value: '', matchMode: 'contains' } }"
class="table table-hover table-responsive-md">
<ng-template pTemplate="header">
<tr class="table-dark">
<th class="hidden" pSortableColumn="id">Patient ID <p-sortIcon field="id" /></th>
<th pSortableColumn="patientCardId">Card ID <p-sortIcon field="patientCardId" /></th>
<th pSortableColumn="name">Full Name <p-sortIcon field="name" /></th>
<th pSortableColumn="gender">Gender <p-sortIcon field="gender" /></th>
<th pSortableColumn="bloodGroup">Blood Group <p-sortIcon field="bloodGroup" /></th>
<th>Mobile</th>
<th>Age</th>
<th>Email</th>
<th pSortableColumn="insuranceProvider">Insurance <p-sortIcon field="insuranceProvider" /></th>
<th>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-patient>
<tr>
<td class="hidden">{{ patient.id }}</td>
<td>{{patient.patientCardId}}</td>
<td>{{ patient.name }}</td>
<td>
<span class="badge" [ngClass]="patient.gender === 1 ? 'bg-primary' : 'bg-pink'">
{{ gender[patient.gender] }}
</span>
</td>
<td>{{ patient.bloodGroup }}</td>
<td>{{ patient.mobile }}</td>
<td>{{patient.age}}</td>
<td>{{ patient.email }}</td>
<td>{{patient.insuranceProvider}}</td>
<td class="d-flex gap-1">
<button *ngIf="createpermission" class="btn btn-success btn-sm"
(click)="addnewrecord(patient.id)">
<i class="pi pi-arrow-right"></i>
</button>
<button *ngIf="editpermission" class="btn btn-warning btn-sm" (click)="editPatient(patient)"><i
class="pi pi-pencil"></i></button>
<button *ngIf="deletepermission" class="btn btn-danger btn-sm"
(click)="deletePatient(patient.id)"><i class="pi pi-trash"></i></button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="10" class="text-center">No Patients found.</td>
</tr>
</ng-template>
</p-table>
<div class="text-end mt-2 fw-bold">Total Records: {{ totalRecords }}</div>
</div>
</div>
<div>
<p-dialog *ngIf="patientDialog" header="{{patientDialogTitle}}" [(visible)]="patientDialog" [modal]="true"
[closable]="true" [style]="{width: '80%', height: 'auto'}" class="modern-dialog">
<form #patientrecord="ngForm" (ngSubmit)="savePatient(patientrecord)">
<div class="p-fluid grid">
<!-- Full Name & Profile Image -->
<div class="col-6">
<div class="field">
<label for="name"><i class="pi pi-user"></i> Full Name</label>
<input id="name" name="name" type="text" pInputText [(ngModel)]="selectedPatient.name"
placeholder="Enter full patient name" required #name="ngModel" />
<small *ngIf="name.invalid && name.touched" class="p-error">Full Name is required</small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="age">Age</label>
<input id="age" name="age" type="number" pInputText [(ngModel)]="selectedPatient.age"
placeholder="Enter age" required min="1" max="120" #age="ngModel" />
<small *ngIf="age.invalid && age.touched" class="p-error">
Age is required and must be between 1 and 90
</small>
</div>
</div>
</div>
<div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="image"><i class="pi pi-image"></i> Profile Image</label>
<input class="d-flex" type="file" id="image" name="image" accept=".jpg,.png"
(change)="profileimageupload($event)" />
</div>
<small *ngIf="error !== ''" class="p-error">{{error}}</small>
<div *ngIf="uploadProgress > 0">
<p>Uploading... {{ uploadProgress }}%</p>
<progress [value]="uploadProgress" max="100"></progress>
</div>
<small *ngIf="imgpath !== ''">{{imgpath}} <i class="pi pi-image"></i></small>
<!-- <div class="col-6">
<div class="field">
<label for="status">Status</label>
<p-dropdown name="status" id="status" [options]="statuslist" [(ngModel)]="selectedstatus"
optionLabel="label" optionValue="value" placeholder="Select status"
required></p-dropdown>
<small *ngIf="selectedstatus === 0" class="p-error">Status is required</small>
</div>
</div> -->
</div>
<div class="col-6">
<div class="field">
<label for="gender"><i class="pi pi-users"></i> Gender</label>
<div>
<label class="mx-1">
<input id="male" type="radio" name="gender" [(ngModel)]="selectedgender" [value]="1"
required />
Male
</label>
<label>
<input id="female" type="radio" name="gender" [(ngModel)]="selectedgender"
[value]="2" />
Female
</label>
</div>
<small *ngIf="!selectedgender" class="p-error">Gender is required</small>
</div>
</div>
</div>
<div class="p-fluid grid">
<!-- Mobile & Address -->
<div class="col-6">
<div class="field">
<label for="mobile"><i class="pi pi-phone"></i> Mobile</label>
<input id="mobile" name="mobile" type="text" pInputText [(ngModel)]="selectedPatient.mobile"
placeholder="Enter mobile number" maxlength="10" required pattern="[0-9]{10}"
#mobile="ngModel" />
<small *ngIf="mobile.invalid && mobile.touched" class="p-error">
Mobile is required and must be 10 digits
</small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="address"><i class="pi pi-map-marker"></i> Address</label>
<input id="address" name="address" type="text" pInputText [(ngModel)]="selectedPatient.address"
placeholder="Enter address" required #address="ngModel" />
<small *ngIf="address.invalid && address.touched" class="p-error">Address is
required</small>
</div>
</div>
</div>
<!-- <div class="p-fluid grid"> -->
<!-- <div class="col-6">
<div class="field">
<label for="admissionDate"><i class="pi pi-calendar"></i> Admission Date</label>
<p-calendar appendTo="body" id="admissionDate" name="admissionDate" [(ngModel)]="selectadmissionDate" showIcon
styleClass="small-calendar" required #admissionDate="ngModel"></p-calendar>
<small *ngIf="admissionDate.invalid && admissionDate.touched" class="p-error">
Admission Date is required
</small>
</div>
</div> -->
<!-- </div> -->
<!-- <div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="doctorAssigned"><i class="pi pi-user-md"></i> Doctor Assigned</label>
<input id="doctorAssigned" name="doctorAssigned" type="text" pInputText
[(ngModel)]="selectedPatient.doctorAssigned" placeholder="Enter assigned doctor" />
</div>
</div>
<div class="col-6">
<div class="field">
<label for="treatment"><i class="pi pi-heartbeat"></i> Treatment</label>
<input id="treatment" name="treatment" type="text" pInputText
[(ngModel)]="selectedPatient.treatment" placeholder="Enter treatment" />
</div>
</div>
</div> -->
<div class="p-fluid grid">
<!-- Blood Group & Email -->
<div class="col-6">
<div class="field">
<label for="bloodGroup"><i class="pi pi-tint"></i> Blood Group</label>
<input id="bloodGroup" name="bloodGroup" type="text" pInputText
[(ngModel)]="selectedPatient.bloodGroup" placeholder="Enter blood group" required
#bloodGroup="ngModel" />
<small *ngIf="bloodGroup.invalid && bloodGroup.touched" class="p-error">Blood Group is
required</small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="email"><i class="pi pi-envelope"></i> Email</label>
<input id="email" name="email" type="email" pInputText [(ngModel)]="selectedPatient.email"
placeholder="Enter email address" required email #email="ngModel" />
<small *ngIf="email.invalid && email.touched" class="p-error">Enter a valid email
address</small>
</div>
</div>
</div>
<!-- Insurance Provider -->
<div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="insuranceProvider"><i class="pi pi-credit-card"></i> Insurance Provider</label>
<input id="insuranceProvider" name="insuranceProvider" type="text" pInputText
[(ngModel)]="selectedPatient.insuranceProvider" required
placeholder="Enter insurance provider" #insurance="ngModel" />
<small *ngIf="insurance.invalid && insurance.touched" class="p-error">
Insurance is required.
</small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="patientCardId"><i class="pi pi-credit-card"></i> Card ID</label>
<input id="patientCardId" name="patientCardId" type="text" pInputText
[(ngModel)]="selectedPatient.patientCardId" required
placeholder="Enter patient card Id" #patientCardId="ngModel" />
<small *ngIf="patientCardId.invalid && patientCardId.touched" class="p-error">
Card Id is required.
</small>
</div>
</div>
</div>
<div class="text-right">
<button type="submit" pButton class="btn btn-primary"
[disabled]="(patientrecord.invalid || !selectedgender)">
<i class="pi pi-check"></i> Save
</button>
<button pButton class="btn btn-secondary ml-1" (click)="closeDialog()">
<i class="pi pi-times"></i> Close
</button>
</div>
</form>
</p-dialog>
</div>

View File

@ -0,0 +1,37 @@
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.badge {
display: inline-block;
padding: 0.2rem 0.6rem;
border-radius: 0.5rem;
color: white;
font-weight: bold;
font-size: 0.8rem;
}
.male {
background-color: green;
}
.female {
background-color: purple;
}
.pdf-icon {
color: red;
font-size: 1.2rem;
}
.bg-pink {
background-color: #ff4081 !important;
color: white;
}
.gap-1 {
gap: 5px;
}

View File

@ -0,0 +1,259 @@
import { PermissionService } from '@abp/ng.core';
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { HttpClient, HttpEventType, HttpParams, HttpRequest } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { PagingSortResultDto } from '@proxy/dto';
import { Gender } from '@proxy/global-enum';
import { PatientService } from '@proxy/patients';
import { PatientDto, CreateUpdatePatientDto } from '@proxy/patients/dto';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-all-patients',
templateUrl: './all-patients.component.html',
styleUrl: './all-patients.component.scss',
providers: [PatientService, ConfirmationService, ToasterService, PermissionService],
})
export class AllPatientsComponent implements OnInit {
globalFilter: string = '';
patients: PatientDto[];
totalRecords: number = 0;
loading: boolean = false;
patientDialog: boolean = false;
selectedPatient: CreateUpdatePatientDto;
isEditMode: boolean = false;
patientDialogTitle: string = '';
params: PagingSortResultDto;
gender: any;
selectedgender: any = 0;
selectadmissionDate: Date = new Date();
selectdischargeDate: Date = new Date();
createpermission: boolean;
editpermission: boolean;
deletepermission: boolean;
imgpath: string = '';
guid: string = '00000000-0000-0000-0000-000000000000';
uploadProgress = 0;
constructor(
private patientService: PatientService,
private http: HttpClient,
private confirmation: ConfirmationService,
private toaster: ToasterService,
private permissionChecker: PermissionService,
private router: Router
) {}
ngOnInit() {
this.createpermission = this.permissionChecker.getGrantedPolicy(
'HospitalManagementSystem.Patient.Create'
);
this.editpermission = this.permissionChecker.getGrantedPolicy(
'HospitalManagementSystem.Patient.Edit'
);
this.deletepermission = this.permissionChecker.getGrantedPolicy(
'HospitalManagementSystem.Patient.Delete'
);
this.gender = Gender;
this.resetselectpatient();
this.loadPatient({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
}
resetselectpatient() {
this.selectedPatient = {
name: '',
gender: Gender.Male,
mobile: '',
email: '',
age: 0,
address: '',
bloodGroup: '',
};
this.imgpath = '';
this.selectedgender = 0;
this.uploadProgress = 0;
}
loadPatient(event: any) {
this.loading = true;
let order = event.sortOrder == 1 ? ' asc' : ' desc';
event.sortField = event.sortField == undefined ? 'id' : event.sortField;
this.params = {
skipCount: event.first,
maxResultCount: event.rows,
sorting: event.sortField + order,
search: event.globalFilter == null ? '' : event.globalFilter,
};
this.patientService.getPatientList(this.params).subscribe(data => {
this.patients = data.items;
this.totalRecords = data.totalCount;
this.loading = false;
});
}
exportPatient() {
this.patientService.getExportPatientData().subscribe(result => {
const binary = atob(result.fileContent);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'application/xlsx' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = result.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
});
}
openNewPatientDialog() {
this.patientDialogTitle = 'New Patient';
this.resetselectpatient();
this.selectedgender = 0;
this.patientDialog = true;
this.isEditMode = false;
}
addnewrecord(id: any) {
this.router.navigate(['/patients/patient-record', id]);
}
profileimageupload(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files.length > 0) {
const tag = 'Image';
const formdata = new FormData();
formdata.append('file', input.files[0]);
this.UploadFileData(tag, formdata);
} else {
this.uploadProgress = 0;
return;
}
}
UploadFileData(tag: string, formdata: FormData) {
const req = new HttpRequest(
'POST',
environment.apis.default.url + '/api/app/shared/upload-file',
formdata,
{
reportProgress: true,
responseType: 'text',
params: new HttpParams().set('tagName', tag),
}
);
this.http.request(req).subscribe(
event => {
if (event.type === HttpEventType.UploadProgress) {
this.uploadProgress = Math.round((event.loaded / event.total!) * 100);
} else if (event.type === HttpEventType.Response) {
this.uploadProgress = 100;
this.selectedPatient.imageID = event.body.toString();
}
},
error => {
this.uploadProgress = 0;
}
);
}
editPatient(Patient: any) {
this.resetselectpatient();
this.patientDialogTitle = 'Edit Patient';
this.patientService.getPatientById(Patient.id).subscribe(result => {
this.selectedPatient = result;
this.imgpath = result.imagepath != null ? result.imagepath.split('\\')[3] : '';
this.selectedgender = this.selectedPatient.gender;
});
this.patientDialog = true;
this.isEditMode = true;
}
deletePatient(id: any) {
this.confirmation
.warn('Do you really want to delete this patient?', {
key: '::AreYouSure',
defaultValue: 'Are you sure?',
})
.subscribe((status: Confirmation.Status) => {
// your code here
if (status == 'confirm') {
this.patientService.deletePatient(id).subscribe(() => {
this.toaster.success('Patient deleted successfully', 'Success');
this.loadPatient(this.params);
});
}
});
}
savePatient(form: NgForm) {
if (form.invalid) {
Object.values(form.controls).forEach(control => control.markAsTouched());
return;
}
this.selectedPatient.gender = this.selectedgender;
this.selectedPatient.imageID = this.selectedPatient.imageID
? this.selectedPatient.imageID.replace('"', '').replace('"', '')
: this.guid;
if (this.isEditMode) {
this.patientService.updatePatient(this.selectedPatient.id, this.selectedPatient).subscribe(
() => {
this.toaster.success('Patient updated successfully', 'Success');
this.patientDialog = false;
this.loadPatient({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
},
error => {
this.closeDialog();
}
);
} else {
this.patientService.createPatient(this.selectedPatient).subscribe(
() => {
this.toaster.success('Patient created successfully', 'Success');
this.patientDialog = false;
this.loadPatient({
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
});
},
error => {
this.closeDialog();
}
);
}
}
closeDialog() {
this.patientDialog = false;
}
}

View File

@ -0,0 +1,252 @@
<div class="patient-container">
<div class="patient-card">
<div class="patient-image">
<img [src]="patientImageUrl" alt="Patient Image">
</div>
<div class="patient-info">
<h2 class="patient-name">{{ patientdto?.name }}</h2>
<div class="patient-details">
<div class="info-item">
<strong>Age:</strong> {{ patientdto?.age }}
</div>
<div class="info-item">
<strong>Gender:</strong> {{ gender[patientdto?.gender] }}
</div>
<div class="info-item">
<strong>Mobile:</strong> {{ patientdto?.mobile }}
</div>
<div class="info-item">
<strong>Email:</strong> {{ patientdto?.email }}
</div>
<div class="info-item">
<strong>Blood Group:</strong> {{ patientdto?.bloodGroup }}
</div>
<div class="info-item">
<strong>Card ID:</strong> {{ patientdto?.patientCardId }}
</div>
</div>
</div>
</div>
</div>
<div>
<button class="btn btn-sm btn-primary mb-2" (click)="backtopatient()">
<i class="pi pi-arrow-left"></i> Back
</button>
</div>
<div class="mt-4">
<p-table #dt2 [value]="patientrecords" [paginator]="true" [rows]="10" [totalRecords]="totalRecords" [lazy]="true"
(onLazyLoad)="loadPatient($event,patientId)" [rowsPerPageOptions]="[10, 20, 50]" [responsiveLayout]="'scroll'"
[globalFilterFields]="['id', 'name', 'diagnosis']" class="card shadow-sm">
<ng-template pTemplate="caption">
<div class="flex justify-content-between align-items-center">
<h3>{{'::PatientHeader' | abpLocalization}}</h3>
<div class="flex gap-2">
<button *ngIf="createpermission" pButton class="btn btn-success btn-sm" (click)="openNewPatientDialog()">
<i class="pi pi-plus-circle"></i>
</button>
<button pButton class="btn btn-warning btn-sm" (click)="exportPatient()">
<i class="pi pi-download"></i>
</button>
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input pInputText type="text" [(ngModel)]="globalFilter"
(input)="dt2.filterGlobal($event.target.value, 'contains')" placeholder="Search...">
</span>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th class="hidden" pSortableColumn="id">Patient ID <p-sortIcon field="id" /></th>
<th>Full Name</th>
<th>Gender</th>
<th>Doctor</th>
<th pSortableColumn="dateOfAdmission">Date of Admission <p-sortIcon field="dateOfAdmission" /></th>
<th pSortableColumn="dischargeDate">Discharge Date <p-sortIcon field="dischargeDate" /></th>
<th pSortableColumn="nextFollowUp">Next Follow-Up <p-sortIcon field="nextFollowUp" /></th>
<th pSortableColumn="diagnosis">Diagnosis <p-sortIcon field="diagnosis" /></th>
<th>Lab Reports</th>
<th>Medications</th>
<th pSortableColumn="status">Status <p-sortIcon field="status" /></th>
<th>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-patientrecord>
<tr>
<td class="hidden">{{ patientrecord.id }}</td>
<td>{{ patientrecord.patients.name }}</td>
<td>
<span class="badge" [ngClass]="patientrecord.patients.gender === 1 ? 'male' : 'female'">
{{ gender[patientrecord.patients.gender] }}
</span>
</td>
<td><span *ngIf="patientrecord.doctorAssigned !== null">{{'Dr. ' + patientrecord.doctorAssigned?.firstName + ' '
+
patientrecord.doctorAssigned?.lastName}}</span></td>
<td>{{ patientrecord.dateOfAdmission | date }}</td>
<td>{{ patientrecord.dischargeDate | date }}</td>
<td>{{ patientrecord.nextFollowUp | date }}</td>
<td>{{ patientrecord.diagnosis }}</td>
<td><i class="pi pi-file-pdf text-danger"></i></td>
<td><i class="pi pi-file-pdf text-primary"></i></td>
<td>
<span class="badge"
[ngClass]="patientrecord.status === 1 ? 'bg-success' : (patientrecord.status === 2 ? 'bg-primary':'bg-danger')">
{{ status[patientrecord.status] }}
</span>
</td>
<td class="flex gap-2">
<button *ngIf="editpermission" class="btn btn-warning btn-sm" (click)="editPatient(patientrecord)">
<i class="pi pi-pencil"></i>
</button>
<button *ngIf="deletepermission" class="btn btn-danger btn-sm" (click)="deletePatient(patientrecord.id)">
<i class="pi pi-trash"></i>
</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="9">No Patients found.</td>
</tr>
</ng-template>
</p-table>
<div class="mt-2">Total Records: {{totalRecords}}</div>
</div>
<div>
<p-dialog *ngIf="patientDialog" header="{{patientDialogTitle}}" [(visible)]="patientDialog" [modal]="true"
[closable]="true" [style]="{width: '70%', height: 'auto'}">
<form #patientrecord="ngForm" (ngSubmit)="savePatient(patientrecord)" novalidate>
<div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="admissionDate"><i class="pi pi-calendar"></i> Admission Date <span
class="text-danger">*</span></label>
<p-calendar appendTo="body" id="admissionDate" name="admissionDate" [(ngModel)]="selectdateOfAdmission"
showIcon required #admissionDate="ngModel"></p-calendar>
<small *ngIf="admissionDate.invalid && admissionDate.touched" class="p-error">
Admission Date is required.
</small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="nextFollowUp"><i class="pi pi-calendar-plus"></i> Next Follow-Up Date</label>
<p-calendar appendTo="body" id="nextFollowUp" name="nextFollowUp" [(ngModel)]="selectnextFollowUp"
showIcon></p-calendar>
</div>
</div>
</div>
<div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="dischargeDate"><i class="pi pi-calendar"></i> Discharge Date</label>
<p-calendar appendTo="body" id="dischargeDate" name="dischargeDate" [(ngModel)]="selectdischargeDate"
showIcon></p-calendar>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="status">Status <span class="text-danger">*</span></label>
<p-dropdown name="status" id="status" [options]="statuslist" [(ngModel)]="selectedstatus"
optionLabel="label" optionValue="value" placeholder="Select status" required></p-dropdown>
<small *ngIf="selectedstatus === 0" class="p-error">Status is required</small>
</div>
</div>
</div>
<div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="doctorAssignedID">Consulting Doctor <span class="text-danger">*</span></label>
<p-dropdown id="doctorAssignedID" name="doctorAssignedID"
[(ngModel)]="selectedPatientRecord.doctorAssignedID" [options]="doctorOptions" placeholder="Select Doctor"
optionLabel="label" optionValue="value" required></p-dropdown>
</div>
</div>
</div>
<div class="p-fluid">
<div class="field">
<label for="diagnosis"><i class="pi pi-heartbeat"></i> Diagnosis</label>
<textarea style="width: 100%; background-color: #fff; color: black;" id="diagnosis" name="diagnosis"
pInputTextarea rows="5" cols="30" [(ngModel)]="selectedPatientRecord.diagnosis"></textarea>
</div>
</div>
<div class="p-fluid">
<div class="field">
<label for="treatmentPlan"><i class="pi pi-heartbeat"></i> Treatment Plan</label>
<textarea style="width: 100%; background-color: #fff; color: black;" id="treatmentPlan" name="treatmentPlan"
pInputTextarea rows="5" cols="30" [(ngModel)]="selectedPatientRecord.treatmentPlan"></textarea>
</div>
</div>
<div class="p-fluid">
<div class="field">
<label for="doctorNotes"><i class="pi pi-heartbeat"></i> Doctor Notes</label>
<textarea style="width: 100%; background-color: #fff; color: black;" id="doctorNotes" name="doctorNotes"
pInputTextarea rows="5" cols="30" [(ngModel)]="selectedPatientRecord.doctorNotes"></textarea>
</div>
</div>
<hr>
<h3 class="mb-4">Documents</h3>
<!-- File Uploads -->
<div class="p-fluid grid">
<div class="col-6">
<div class="field">
<label for="labReport"><i class="pi pi-file-pdf"></i> Lab Report</label>
<input class="d-flex" type="file" id="labReport" name="labReport" accept=".pdf,.doc,.docx"
(change)="handleUpload($event,'Lab-Report')" />
<div *ngIf="uploadProgress1 > 0">
<p>Uploading... {{ uploadProgress1 }}%</p>
<progress [value]="uploadProgress1" max="100"></progress>
</div>
<small *ngIf="labReportUrlpath!==''">{{labReportUrlpath}} <i class="pi pi-file"></i></small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="medications"><i class="pi pi-file-pdf"></i> Medications</label>
<input class="d-flex" type="file" id="medications" name="medications" accept=".pdf,.doc,.docx"
(change)="handleUpload($event,'Medication')" />
<div *ngIf="uploadProgress2 > 0">
<p>Uploading... {{ uploadProgress2 }}%</p>
<progress [value]="uploadProgress2" max="100"></progress>
</div>
<small *ngIf="medicationUrlpath!==''">{{medicationUrlpath}} <i class="pi pi-file"></i></small>
</div>
</div>
<div class="col-6">
<div class="field">
<label for="medicationHistory"><i class="pi pi-file-pdf"></i>Medication History</label>
<input class="d-flex" type="file" id="medicationHistory" name="medicationHistory" accept=".pdf,.doc,.docx"
(change)="handleUpload($event,'Medication-History')" />
<div *ngIf="uploadProgress3 > 0">
<p>Uploading... {{ uploadProgress3 }}%</p>
<progress [value]="uploadProgress3" max="100"></progress>
</div>
<small *ngIf="medicationHistoryUrlpath !==''">{{medicationHistoryUrlpath}} <i
class="pi pi-file-pdf pdf-icon"></i></small>
</div>
</div>
</div>
<!-- Buttons -->
<div class="p-dialog-footer flex justify-content-end">
<button type="submit" pButton class="btn btn-primary"
[disabled]="patientrecord.invalid || selectedstatus === 0">
<i class="pi pi-check"></i> Save
</button>
<button pButton class="btn btn-secondary ml-2" (click)="closeDialog()">
<i class="pi pi-times"></i> Close
</button>
</div>
</form>
</p-dialog>
</div>

View File

@ -0,0 +1,82 @@
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.badge {
display: inline-block;
padding: 0.2rem 0.6rem;
border-radius: 0.5rem;
color: white;
font-weight: bold;
font-size: 0.8rem;
}
.male {
background-color: green;
}
.female {
background-color: #ff4081 !important;
color: white;
}
.pdf-icon {
color: red;
font-size: 1.2rem;
}
.patient-container {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.patient-card {
display: flex;
align-items: center;
width: 100%;
max-width: 800px;
background: #ffffff;
border-radius: 12px;
padding: 20px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
border-left: 5px solid #007bff;
}
.patient-image img {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #007bff;
}
.patient-info {
flex: 1;
margin-left: 20px;
}
.patient-name {
font-size: 22px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.patient-details {
display: grid;
grid-template-columns: repeat(2, minmax(150px, 1fr));
gap: 8px;
}
.info-item {
font-size: 14px;
background: #f8f9fa;
padding: 6px 12px;
border-radius: 6px;
}

View File

@ -0,0 +1,357 @@
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { PagingSortResultDto } from '@proxy/dto';
import { Gender, Status } from '@proxy/global-enum';
import { PatientService } from '@proxy/patients';
import { CreateUpdatePatientRecordDto, PatientDto, PatientRecordDto } from '@proxy/patients/dto';
import { PermissionService } from '@abp/ng.core';
import { ActivatedRoute, Router } from '@angular/router';
import { environment } from 'src/environments/environment';
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { HttpClient, HttpEventType, HttpParams, HttpRequest } from '@angular/common/http';
import { DoctorService } from '@proxy/doctors/doctor.service';
@Component({
selector: 'app-patient-record',
templateUrl: './patient-record.component.html',
styleUrl: './patient-record.component.scss',
providers: [
PatientService,
ConfirmationService,
DoctorService,
ToasterService,
PermissionService,
],
})
export class PatientRecordComponent implements OnInit {
globalFilter: string = '';
patientrecords: PatientRecordDto[];
totalRecords: number = 0;
loading: boolean = false;
patientDialog: boolean = false;
selectedPatientRecord: CreateUpdatePatientRecordDto;
patientdto: PatientDto;
isEditMode: boolean = false;
patientDialogTitle: string = '';
params: PagingSortResultDto;
status: any;
gender: any;
statuslist: any;
selectdateOfAdmission: Date = new Date();
selectnextFollowUp: Date = new Date();
selectdischargeDate: Date = new Date();
selectedstatus = 0;
createpermission: boolean;
editpermission: boolean;
deletepermission: boolean;
patientId: any;
patientImageUrl: string;
labReportUrlpath: string;
medicationHistoryUrlpath: string;
medicationUrlpath: string;
guid: string = '00000000-0000-0000-0000-000000000000';
uploadProgress1 = 0;
uploadProgress2 = 0;
uploadProgress3 = 0;
doctorOptions = [];
doctorname = '';
constructor(
private patientService: PatientService,
private confirmation: ConfirmationService,
private permissionChecker: PermissionService,
private DoctorService: DoctorService,
private toaster: ToasterService,
private route: ActivatedRoute,
private router: Router,
private http: HttpClient
) {}
ngOnInit() {
this.patientId = this.route.snapshot.paramMap.get('id');
this.createpermission = this.permissionChecker.getGrantedPolicy(
'HospitalManagementSystem.Patient.Create'
);
this.editpermission = this.permissionChecker.getGrantedPolicy(
'HospitalManagementSystem.Patient.Edit'
);
this.deletepermission = this.permissionChecker.getGrantedPolicy(
'HospitalManagementSystem.Patient.Delete'
);
this.status = Status;
this.gender = Gender;
this.patientService.getStatusDropdown().subscribe(result => {
this.statuslist = result;
});
this.resetselectpatient();
this.getdoctorlist();
}
resetselectpatient() {
this.selectedPatientRecord = {
patientId: this.patientId,
dateOfAdmission: '',
// labReportUrl: '',
// medicationUrl: '',
// medicationHistoryUrl: '',
nextFollowUp: '',
diagnosis: '',
treatmentPlan: '',
doctorNotes: '',
status: Status.InTreatment,
};
this.labReportUrlpath = '';
this.medicationUrlpath = '';
this.medicationHistoryUrlpath = '';
this.selectdateOfAdmission = new Date();
this.selectnextFollowUp = new Date();
this.selectdischargeDate = new Date();
this.selectedstatus = 0;
this.uploadProgress1 = 0;
this.uploadProgress2 = 0;
this.uploadProgress3 = 0;
}
loadPatient(event: any, id: any) {
this.selectedPatientRecord.patientId = this.patientId;
this.loading = true;
let order = event.sortOrder == 1 ? ' asc' : ' desc';
event.sortField = event.sortField == undefined ? 'id' : event.sortField;
this.params = {
skipCount: event.first,
maxResultCount: event.rows,
sorting: event.sortField + order,
search: event.globalFilter == null ? '' : event.globalFilter,
};
this.patientService.getPatientById(this.patientId).subscribe(result => {
this.patientdto = result;
this.patientImageUrl =
environment.apis.default.url +
(result.imagepath !== null ? result.imagepath : '/images/default-profile.png');
});
this.patientService.getPatientRecordList(this.params, id).subscribe(data => {
this.patientrecords = data.items;
this.totalRecords = data.totalCount;
this.loading = false;
});
}
getdoctorlist() {
this.DoctorService.get().subscribe(result => {
const doctors = result;
this.doctorOptions = doctors.map(doctor => ({
label: `Dr. ${doctor.firstName} ${doctor.lastName}`, // Combine first and last name
value: doctor.id, // Use the ID as the value
}));
});
}
backtopatient() {
this.router.navigate(['/patients/all-patients']);
}
exportPatient() {
this.patientService.getExportPatientRecord(this.patientId).subscribe(result => {
const binary = atob(result.fileContent);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'application/xlsx' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = result.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
});
}
openNewPatientDialog() {
this.patientDialogTitle = 'New Patient Record';
this.resetselectpatient();
this.patientDialog = true;
this.isEditMode = false;
}
editPatient(Patient: any) {
this.resetselectpatient();
this.patientDialogTitle = 'Edit Patient';
this.patientService.getPatientRecordById(Patient.id).subscribe(result => {
this.selectedPatientRecord = result;
this.selectedPatientRecord.patientId = this.patientId;
this.selectdateOfAdmission = new Date(this.selectedPatientRecord.dateOfAdmission);
this.selectnextFollowUp = new Date(this.selectedPatientRecord.nextFollowUp);
this.selectdischargeDate = new Date(this.selectedPatientRecord.dischargeDate);
this.selectedstatus = this.selectedPatientRecord.status;
this.selectedPatientRecord.doctorAssignedID = result.doctorAssigned.id;
this.labReportUrlpath = result.labReportUrl != null ? result.labReportUrl.split('\\')[3] : '';
this.medicationUrlpath =
result.medicationUrl != null ? result.medicationUrl.split('\\')[3] : '';
this.medicationHistoryUrlpath =
result.medicationHistoryUrl != null ? result.medicationHistoryUrl.split('\\')[3] : '';
});
this.patientDialog = true;
this.isEditMode = true;
}
deletePatient(id: any) {
this.confirmation
.warn('Do you really want to delete this record ?', {
key: '::AreYouSure',
defaultValue: 'Are you sure?',
})
.subscribe((status: Confirmation.Status) => {
// your code here
if (status == 'confirm') {
this.patientService.deletePatientRecord(id).subscribe(() => {
this.loadPatient(this.params, this.patientId);
this.toaster.success('Patient deleted successfully', 'Success');
});
}
});
}
handleUpload(event: Event, tag: string): void {
const input = event.target as HTMLInputElement;
if (input.files.length > 0) {
const formdata = new FormData();
formdata.append('file', input.files[0]);
this.UploadFileData(tag, formdata);
} else {
this.uploadProgress1 = 0;
this.uploadProgress2 = 0;
this.uploadProgress3 = 0;
return;
}
}
UploadFileData(tag: string, formdata: FormData) {
const req = new HttpRequest(
'POST',
environment.apis.default.url + '/api/app/shared/upload-file',
formdata,
{
reportProgress: true,
responseType: 'text',
params: new HttpParams().set('tagName', tag),
}
);
this.http.request(req).subscribe(
event => {
if (event.type === HttpEventType.UploadProgress) {
switch (tag) {
case 'Lab-Report':
this.uploadProgress1 = Math.round((event.loaded / event.total!) * 100);
break;
case 'Medication':
this.uploadProgress2 = Math.round((event.loaded / event.total!) * 100);
break;
case 'Medication-History':
this.uploadProgress3 = Math.round((event.loaded / event.total!) * 100);
break;
}
} else if (event.type === HttpEventType.Response) {
switch (tag) {
case 'Lab-Report':
this.uploadProgress1 = 100;
this.selectedPatientRecord.labReportUrlID = event.body.toString();
break;
case 'Medication':
this.uploadProgress2 = 100;
this.selectedPatientRecord.medicationUrlID = event.body.toString();
break;
case 'Medication-History':
this.uploadProgress3 = 100;
this.selectedPatientRecord.medicationHistoryUrlID = event.body.toString();
break;
}
}
},
error => {
this.uploadProgress1 = 100;
this.uploadProgress2 = 100;
this.uploadProgress3 = 100;
}
);
}
savePatient(form: NgForm) {
this.selectedPatientRecord.patientId = this.patientId;
this.selectedPatientRecord.status = this.selectedPatientRecord.status;
this.selectedPatientRecord.dateOfAdmission = this.selectdateOfAdmission.toDateString();
this.selectedPatientRecord.nextFollowUp = this.selectnextFollowUp.toDateString();
this.selectedPatientRecord.dischargeDate = this.selectdischargeDate.toDateString();
this.selectedPatientRecord.status = this.selectedstatus;
this.selectedPatientRecord.labReportUrlID = this.selectedPatientRecord.labReportUrlID
? this.selectedPatientRecord.labReportUrlID.replace('"', '').replace('"', '')
: this.guid;
this.selectedPatientRecord.medicationUrlID = this.selectedPatientRecord.medicationUrlID
? this.selectedPatientRecord.medicationUrlID.replace('"', '').replace('"', '')
: this.guid;
this.selectedPatientRecord.medicationHistoryUrlID = this.selectedPatientRecord
.medicationHistoryUrlID
? this.selectedPatientRecord.medicationHistoryUrlID.replace('"', '').replace('"', '')
: this.guid;
if (this.isEditMode) {
this.patientService
.updatePatientRecord(this.selectedPatientRecord.id, this.selectedPatientRecord)
.subscribe(
result => {
this.toaster.success('Patient updated successfully', 'Success');
this.patientDialog = false;
this.loadPatient(
{
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
},
this.patientId
);
},
error => {
this.closeDialog();
}
);
} else {
this.patientService.createPatientRecord(this.selectedPatientRecord).subscribe(
() => {
this.toaster.success('Patient created successfully', 'Success');
this.patientDialog = false;
this.loadPatient(
{
first: 0,
rows: 10,
sortField: 'id',
sortOrder: 1,
globalFilter: null,
},
this.patientId
);
},
error => {
this.closeDialog();
}
);
}
}
closeDialog() {
this.patientDialog = false;
}
}

View File

@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AllPatientsComponent } from './all-patients/all-patients.component';
import { PatientRecordComponent } from './patient-record/patient-record.component';
import { authGuard, permissionGuard } from '@abp/ng.core';
const routes: Routes = [
{
path: '',
children: [
{
path: 'patient-record/:id',
component: PatientRecordComponent,
// canActivate: [authGuard, permissionGuard],
},
{ path: 'all-patients', component: AllPatientsComponent },
// { path: 'create-new/:id', component: FaqCreateComponent },
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PatientsRoutingModule {}

View File

@ -0,0 +1,37 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PatientsRoutingModule } from './patients-routing.module';
import { AllPatientsComponent } from './all-patients/all-patients.component';
import { PatientRecordComponent } from './patient-record/patient-record.component';
import { TableModule } from 'primeng/table';
import { IconFieldModule } from 'primeng/iconfield';
import { InputIconModule } from 'primeng/inputicon';
import { FormsModule } from '@angular/forms';
import { InputTextModule } from 'primeng/inputtext';
import { PaginatorModule } from 'primeng/paginator';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { FileUploadModule } from 'primeng/fileupload';
import { CalendarModule } from 'primeng/calendar';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [AllPatientsComponent, PatientRecordComponent],
imports: [
CommonModule,
PatientsRoutingModule,
TableModule,
InputIconModule,
IconFieldModule,
FormsModule,
InputTextModule,
PaginatorModule,
DialogModule,
DropdownModule,
FileUploadModule,
CalendarModule,
SharedModule
],
})
export class PatientsModule {}

View File

@ -0,0 +1,17 @@
# Proxy Generation Output
This directory includes the output of the latest proxy generation.
The files and folders in it will be overwritten when proxy generation is run again.
Therefore, please do not place your own content in this folder.
In addition, `generate-proxy.json` works like a lock file.
It includes information used by the proxy generator, so please do not delete or modify it.
Finally, the name of the files and folders should not be changed for two reasons:
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content.
- ABP Suite generates files which include imports from this folder.
> **Important Notice:** If you are building a module and are planning to publish to npm,
> some of the generated proxies are likely to be exported from public-api.ts file. In such a case,
> please make sure you export files directly and not from barrel exports. In other words,
> do not include index.ts exports in your public-api.ts exports.

View File

@ -0,0 +1,2 @@
import * as Services from './services';
export { Services };

View File

@ -0,0 +1 @@
export * from './models';

View File

@ -0,0 +1,8 @@
export interface ListResultDto<T> {
items: T[];
}
export interface PagedResultDto<T> extends ListResultDto<T> {
totalCount: number;
}

View File

@ -0,0 +1,2 @@
import * as Dto from './dto';
export { Dto };

View File

@ -0,0 +1,2 @@
import * as Application from './application';
export { Application };

View File

@ -0,0 +1 @@
export * from './models';

View File

@ -0,0 +1,46 @@
import type { Gender } from '../../global-enum/gender.enum';
import type { appointmentStatus } from '../../global-enum/appointment-status.enum';
import type { visitType } from '../../global-enum/visit-type.enum';
import type { paymentStatus } from '../../global-enum/payment-status.enum';
import type { DoctorDto } from '../../doctors/dto/models';
export interface AppointmentDto {
id?: string;
firstName?: string;
lastName?: string;
gender: Gender;
mobile?: string;
address?: string;
email?: string;
dob?: string;
doctorId?: string;
dateOfAppointment?: string;
timeOfAppointment?: string;
injuryORContion?: string;
note?: string;
insuranceProvider?: string;
appointmentStatus: appointmentStatus;
visitType: visitType;
paymentStatus: paymentStatus;
doctor: DoctorDto;
}
export interface CreateOrUpdateAppointmentDto {
id?: string;
firstName?: string;
lastName?: string;
gender: Gender;
mobile?: string;
address?: string;
email?: string;
dob?: string;
doctorId?: string;
dateOfAppointment?: string;
timeOfAppointment?: string;
injuryORContion?: string;
note?: string;
insuranceProvider?: string;
appointmentStatus: appointmentStatus;
visitType: visitType;
paymentStatus: paymentStatus;
}

View File

@ -0,0 +1,2 @@
import * as Dto from './dto';
export { Dto };

View File

@ -0,0 +1,74 @@
import { RestService, Rest } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { PagedResultDto } from '../abp/application/services/dto/models';
import type { AppointmentDto, CreateOrUpdateAppointmentDto } from '../appoinments/dto/models';
import type { FileDownloadDto, PagingSortResultDto } from '../dto/models';
@Injectable({
providedIn: 'root',
})
export class AppointmentService {
apiName = 'Default';
createAppointment = (input: CreateOrUpdateAppointmentDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'POST',
url: '/api/app/appointment/appointment',
body: input,
},
{ apiName: this.apiName,...config });
deleteAppointmentRecord = (id: string, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'DELETE',
url: `/api/app/appointment/${id}/appointment-record`,
},
{ apiName: this.apiName,...config });
get = (config?: Partial<Rest.Config>) =>
this.restService.request<any, string>({
method: 'GET',
responseType: 'text',
url: '/api/app/appointment',
},
{ apiName: this.apiName,...config });
getAppointmentById = (id: string, config?: Partial<Rest.Config>) =>
this.restService.request<any, AppointmentDto>({
method: 'GET',
url: `/api/app/appointment/${id}/appointment-by-id`,
},
{ apiName: this.apiName,...config });
getAppointmentList = (input: PagingSortResultDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, PagedResultDto<AppointmentDto>>({
method: 'GET',
url: '/api/app/appointment/appointment-list',
params: { search: input.search, sorting: input.sorting, skipCount: input.skipCount, maxResultCount: input.maxResultCount },
},
{ apiName: this.apiName,...config });
getExportAppointmentRecord = (config?: Partial<Rest.Config>) =>
this.restService.request<any, FileDownloadDto>({
method: 'GET',
url: '/api/app/appointment/export-appointment-record',
},
{ apiName: this.apiName,...config });
updateAppointment = (input: CreateOrUpdateAppointmentDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'PUT',
url: '/api/app/appointment/appointment',
body: input,
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

View File

@ -0,0 +1 @@
export * from './appointment.service';

View File

@ -0,0 +1,65 @@
import { RestService, Rest } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { PagedResultDto } from '../abp/application/services/dto/models';
import type { FileDownloadDto, PagingSortResultDto } from '../dto/models';
import type { CreateDepartmentDto, DepartmentDto } from '../dtos/models';
@Injectable({
providedIn: 'root',
})
export class DepartmentService {
apiName = 'Default';
createDepartment = (input: CreateDepartmentDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'POST',
url: '/api/app/department/department',
body: input,
},
{ apiName: this.apiName,...config });
deleteDepartmentRecord = (id: string, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'DELETE',
url: `/api/app/department/${id}/department-record`,
},
{ apiName: this.apiName,...config });
getDepartmentById = (id: string, config?: Partial<Rest.Config>) =>
this.restService.request<any, DepartmentDto>({
method: 'GET',
url: `/api/app/department/${id}/department-by-id`,
},
{ apiName: this.apiName,...config });
getDepartmentList = (input: PagingSortResultDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, PagedResultDto<DepartmentDto>>({
method: 'GET',
url: '/api/app/department/department-list',
params: { search: input.search, sorting: input.sorting, skipCount: input.skipCount, maxResultCount: input.maxResultCount },
},
{ apiName: this.apiName,...config });
getExportDepartmentRecord = (config?: Partial<Rest.Config>) =>
this.restService.request<any, FileDownloadDto>({
method: 'GET',
url: '/api/app/department/export-department-record',
},
{ apiName: this.apiName,...config });
updateDepartment = (input: CreateDepartmentDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'PUT',
url: '/api/app/department/department',
body: input,
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

View File

@ -0,0 +1 @@
export * from './department.service';

View File

@ -0,0 +1,74 @@
import type { DoctorDto } from './dto/models';
import { RestService, Rest } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { PagedResultDto } from '../abp/application/services/dto/models';
import type { FileDownloadDto, PagingSortResultDto } from '../dto/models';
import type { CreateDoctorDto } from '../dtos/models';
@Injectable({
providedIn: 'root',
})
export class DoctorService {
apiName = 'Default';
createDoctor = (input: CreateDoctorDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'POST',
url: '/api/app/doctor/doctor',
body: input,
},
{ apiName: this.apiName,...config });
deleteDoctorRecord = (id: string, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'DELETE',
url: `/api/app/doctor/${id}/doctor-record`,
},
{ apiName: this.apiName,...config });
get = (config?: Partial<Rest.Config>) =>
this.restService.request<any, DoctorDto[]>({
method: 'GET',
url: '/api/app/doctor',
},
{ apiName: this.apiName,...config });
getDoctorById = (id: string, config?: Partial<Rest.Config>) =>
this.restService.request<any, DoctorDto>({
method: 'GET',
url: `/api/app/doctor/${id}/doctor-by-id`,
},
{ apiName: this.apiName,...config });
getDoctorList = (input: PagingSortResultDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, PagedResultDto<DoctorDto>>({
method: 'GET',
url: '/api/app/doctor/doctor-list',
params: { search: input.search, sorting: input.sorting, skipCount: input.skipCount, maxResultCount: input.maxResultCount },
},
{ apiName: this.apiName,...config });
getExportDoctorsRecord = (config?: Partial<Rest.Config>) =>
this.restService.request<any, FileDownloadDto>({
method: 'GET',
url: '/api/app/doctor/export-doctors-record',
},
{ apiName: this.apiName,...config });
updatDoctor = (input: CreateDoctorDto, config?: Partial<Rest.Config>) =>
this.restService.request<any, void>({
method: 'POST',
url: '/api/app/doctor/updat-doctor',
body: input,
},
{ apiName: this.apiName,...config });
constructor(private restService: RestService) {}
}

View File

@ -0,0 +1 @@
export * from './models';

View File

@ -0,0 +1,39 @@
import type { FullAuditedEntity } from '../../volo/abp/domain/entities/auditing/models';
import type { WorkSchedule } from '../../global-enum/work-schedule.enum';
import type { FullAuditedEntityDto } from '@abp/ng.core';
import type { TimeSlot } from '../../global-enum/time-slot.enum';
export interface DoctorDto extends FullAuditedEntity<string> {
id?: string;
firstName?: string;
lastName?: string;
gender?: string;
mobile?: string;
designation?: string;
departmentId?: string;
address?: string;
email?: string;
dob?: string;
education?: string;
specialization?: string;
degree?: string;
joiningDate?: string;
experience?: number;
consultationFee?: number;
availability: WorkSchedule[];
rating?: number;
clinicLocation?: string;
shifts: ShiftManagementDto[];
}
export interface ShiftManagementDto extends FullAuditedEntityDto<string> {
doctorId?: string;
shifts: Record<WorkSchedule, TimeSlot>;
isActive: boolean;
}
export interface CreateUpdateShiftManagementDto {
doctorId?: string;
shifts: Record<WorkSchedule, TimeSlot>;
isActive: boolean;
}

View File

@ -0,0 +1,3 @@
import * as Dto from './dto';
export * from './doctor.service';
export { Dto };

View File

@ -0,0 +1 @@
export * from './models';

View File

@ -0,0 +1,11 @@
import type { AuditedAggregateRoot } from '../volo/abp/domain/entities/auditing/models';
export interface EntityDocument extends AuditedAggregateRoot<string> {
originalFileName?: string;
generatedFileName?: string;
fileSize?: string;
filePath?: string;
fileType?: string;
tagName?: string;
uploadDate?: string;
}

View File

@ -0,0 +1 @@
export * from './models';

View File

@ -0,0 +1,15 @@
import type { PagedAndSortedResultRequestDto } from '@abp/ng.core';
export interface DropDownItems {
label?: string;
value: number;
}
export interface FileDownloadDto {
fileName?: string;
fileContent?: string;
}
export interface PagingSortResultDto extends PagedAndSortedResultRequestDto {
search?: string;
}

View File

@ -0,0 +1 @@
export * from './models';

View File

@ -0,0 +1,44 @@
import type { WorkSchedule } from '../global-enum/work-schedule.enum';
import type { FullAuditedEntity } from '../volo/abp/domain/entities/auditing/models';
export interface CreateDepartmentDto {
id?: string;
departmentNo?: string;
departmentName?: string;
departmentDate?: string;
departmentHead?: string;
description?: string;
status?: string;
}
export interface CreateDoctorDto {
id?: string;
firstName?: string;
lastName?: string;
gender?: string;
mobile?: string;
password?: string;
designation?: string;
departmentId?: string;
address?: string;
email?: string;
dob?: string;
education?: string;
specialization?: string;
degree?: string;
joiningDate?: string;
experience?: number;
consultationFee?: number;
availability: WorkSchedule[];
rating?: number;
clinicLocation?: string;
}
export interface DepartmentDto extends FullAuditedEntity<string> {
departmentNo?: string;
departmentName?: string;
departmentDate?: string;
departmentHead?: string;
status?: string;
description?: string;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum appointmentStatus {
Scheduled = 1,
Completed = 2,
Canceled = 3,
}
export const appointmentStatusOptions = mapEnumToOptions(appointmentStatus);

View File

@ -0,0 +1,8 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum Gender {
Male = 1,
Female = 2,
}
export const genderOptions = mapEnumToOptions(Gender);

View File

@ -0,0 +1,7 @@
export * from './appointment-status.enum';
export * from './gender.enum';
export * from './payment-status.enum';
export * from './status.enum';
export * from './time-slot.enum';
export * from './visit-type.enum';
export * from './work-schedule.enum';

View File

@ -0,0 +1,9 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum paymentStatus {
Paid = 1,
Pending = 2,
Unpaid = 3,
}
export const paymentStatusOptions = mapEnumToOptions(paymentStatus);

View File

@ -0,0 +1,9 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum Status {
Recovered = 1,
Observation = 2,
InTreatment = 3,
}
export const statusOptions = mapEnumToOptions(Status);

View File

@ -0,0 +1,14 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum TimeSlot {
TenToSeven = 1,
NineToFive = 2,
EightToFour = 3,
SevenToFour = 4,
SixToThree = 5,
TwelveToNine = 6,
TenToSix = 7,
ElevenToEight = 8,
}
export const timeSlotOptions = mapEnumToOptions(TimeSlot);

View File

@ -0,0 +1,8 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum visitType {
NewPatient = 1,
Followup = 2,
}
export const visitTypeOptions = mapEnumToOptions(visitType);

Some files were not shown because too many files have changed in this diff Show More