Files
Hospital-booking/frontend/src/app/pages/doctor/components/ehr/ehr.component.ts
Iliyan Angelov c15a7bdbde Start
2025-11-16 02:44:17 +02:00

627 lines
19 KiB
TypeScript

import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MedicalRecordService, MedicalRecord, MedicalRecordRequest, VitalSigns, VitalSignsRequest, LabResultRequest } from '../../../../services/medical-record.service';
import { UserService, PatientProfile } from '../../../../services/user.service';
import { ModalService } from '../../../../services/modal.service';
import { LoggerService } from '../../../../services/logger.service';
@Component({
selector: 'app-ehr',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './ehr.component.html',
styleUrl: './ehr.component.scss'
})
export class EhrComponent implements OnInit, OnChanges {
@Input() doctorId: string | null = null;
@Input() selectedPatientId: string | null = null;
@Input() patients: any[] = [];
@Output() patientSelected = new EventEmitter<string>();
@Output() dataChanged = new EventEmitter<void>();
// State
showAllRecords = false;
activeTab: 'records' | 'vitals' | 'labResults' = 'records';
loading = false;
error: string | null = null;
// Local patient selection for dropdown (separate from Input)
localSelectedPatientId: string | null = null;
// Patient search
patientSearchQuery: string = '';
filteredPatients: any[] = [];
// Medical Records
medicalRecords: MedicalRecord[] = [];
allMedicalRecords: MedicalRecord[] = [];
showCreateMedicalRecord = false;
newMedicalRecord: MedicalRecordRequest = {
patientId: '',
doctorId: '',
recordType: 'NOTE',
title: '',
content: '',
diagnosisCode: ''
};
// Vital Signs
vitalSigns: VitalSigns[] = [];
latestVitalSigns: VitalSigns | null = null;
showCreateVitalSigns = false;
newVitalSigns: VitalSignsRequest = {
patientId: '',
temperature: undefined,
bloodPressureSystolic: undefined,
bloodPressureDiastolic: undefined,
heartRate: undefined,
respiratoryRate: undefined,
oxygenSaturation: undefined,
weight: undefined,
height: undefined,
notes: ''
};
// Lab Results
labResults: any[] = [];
showCreateLabResult = false;
showUpdateLabResult = false;
selectedLabResult: any = null;
newLabResult: LabResultRequest = {
patientId: undefined as any,
doctorId: '',
testName: '',
resultValue: '',
status: 'PENDING',
orderedDate: new Date().toISOString().split('T')[0]
};
// Patient Profile Modal
selectedPatientProfile: PatientProfile | null = null;
showPatientProfileModal = false;
constructor(
private medicalRecordService: MedicalRecordService,
private userService: UserService,
private modalService: ModalService,
private logger: LoggerService
) {}
ngOnInit() {
if (this.doctorId) {
this.newMedicalRecord.doctorId = this.doctorId;
this.newLabResult.doctorId = this.doctorId;
this.loadAllDoctorRecords();
}
// Initialize local selection from Input if provided, but don't auto-load
// This allows parent to set initial selection, but user must explicitly choose
if (this.selectedPatientId) {
this.localSelectedPatientId = this.selectedPatientId;
}
// Initialize filtered patients
this.filterPatients();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['doctorId'] && this.doctorId) {
this.newMedicalRecord.doctorId = this.doctorId;
this.newLabResult.doctorId = this.doctorId;
if (!this.allMedicalRecords.length) {
this.loadAllDoctorRecords();
}
}
// Update local selection when Input changes, but don't auto-load data
// User must explicitly select a patient from the dropdown
if (changes['selectedPatientId']) {
if (this.selectedPatientId) {
this.localSelectedPatientId = this.selectedPatientId;
} else {
this.localSelectedPatientId = null;
this.showAllRecords = true;
}
}
// Update filtered patients when patients list changes
if (changes['patients']) {
this.filterPatients();
}
}
async loadAllDoctorRecords() {
if (!this.doctorId) return;
try {
this.loading = true;
this.allMedicalRecords = await this.medicalRecordService.getMedicalRecordsByDoctorId(this.doctorId);
} catch (e: any) {
this.logger.error('Error loading all doctor records:', e);
this.error = e?.response?.data?.error || 'Failed to load medical records';
this.allMedicalRecords = [];
} finally {
this.loading = false;
}
}
async loadPatientData() {
if (!this.localSelectedPatientId) return;
await Promise.all([
this.loadPatientMedicalRecords(),
this.loadPatientVitalSigns(),
this.loadPatientLabResults()
]);
}
async loadPatientMedicalRecords() {
if (!this.localSelectedPatientId) return;
try {
this.loading = true;
this.medicalRecords = await this.medicalRecordService.getMedicalRecordsByPatientId(this.localSelectedPatientId);
} catch (e: any) {
this.logger.error('Error loading medical records:', e);
this.error = e?.response?.data?.error || 'Failed to load medical records';
this.medicalRecords = [];
} finally {
this.loading = false;
}
}
async loadPatientVitalSigns() {
if (!this.localSelectedPatientId) return;
try {
this.loading = true;
[this.vitalSigns, this.latestVitalSigns] = await Promise.all([
this.medicalRecordService.getVitalSignsByPatientId(this.localSelectedPatientId),
this.medicalRecordService.getLatestVitalSignsByPatientId(this.localSelectedPatientId)
]);
} catch (e: any) {
this.logger.error('Error loading vital signs:', e);
this.error = e?.response?.data?.error || 'Failed to load vital signs';
this.vitalSigns = [];
this.latestVitalSigns = null;
} finally {
this.loading = false;
}
}
async loadPatientLabResults() {
if (!this.localSelectedPatientId) return;
try {
this.loading = true;
this.labResults = await this.medicalRecordService.getLabResultsByPatientId(this.localSelectedPatientId);
} catch (e: any) {
this.logger.error('Error loading lab results:', e);
this.error = e?.response?.data?.error || 'Failed to load lab results';
this.labResults = [];
} finally {
this.loading = false;
}
}
selectPatient(patientId: string | null) {
if (!patientId) {
// Clear selection
this.localSelectedPatientId = null;
this.selectedPatientId = null;
this.showAllRecords = true;
this.patientSelected.emit('');
// Clear patient-specific data
this.medicalRecords = [];
this.vitalSigns = [];
this.latestVitalSigns = null;
this.labResults = [];
return;
}
// User explicitly selected a patient - load their data
this.localSelectedPatientId = patientId;
this.selectedPatientId = patientId;
this.showAllRecords = false;
this.patientSelected.emit(patientId);
this.loadPatientData();
}
async toggleRecordsView() {
this.showAllRecords = !this.showAllRecords;
if (this.showAllRecords) {
await this.loadAllDoctorRecords();
// Clear patient selection when showing all records
this.localSelectedPatientId = null;
} else if (this.localSelectedPatientId) {
// Only load if patient is explicitly selected
await this.loadPatientData();
}
}
// Medical Records Methods
openCreateMedicalRecord() {
this.showCreateMedicalRecord = !this.showCreateMedicalRecord;
if (this.showCreateMedicalRecord) {
this.newMedicalRecord = {
patientId: this.localSelectedPatientId || '',
doctorId: this.doctorId || '',
recordType: 'NOTE',
title: '',
content: '',
diagnosisCode: ''
};
}
}
async createMedicalRecord() {
if (!this.newMedicalRecord.patientId || !this.newMedicalRecord.title || !this.newMedicalRecord.content) {
this.error = 'Please fill in all required fields';
return;
}
if (!this.newMedicalRecord.doctorId) {
this.newMedicalRecord.doctorId = this.doctorId || '';
}
try {
this.loading = true;
const recordId = (this.newMedicalRecord as any).recordId;
if (recordId) {
await this.medicalRecordService.updateMedicalRecord(recordId, this.newMedicalRecord);
} else {
await this.medicalRecordService.createMedicalRecord(this.newMedicalRecord);
}
this.showCreateMedicalRecord = false;
this.newMedicalRecord = {
patientId: '',
doctorId: this.doctorId || '',
recordType: 'NOTE',
title: '',
content: '',
diagnosisCode: ''
};
delete (this.newMedicalRecord as any).recordId;
await Promise.all([
this.localSelectedPatientId ? this.loadPatientMedicalRecords() : Promise.resolve(),
this.loadAllDoctorRecords()
]);
this.dataChanged.emit();
} catch (e: any) {
this.logger.error('Error creating/updating medical record:', e);
const recordId = (this.newMedicalRecord as any).recordId;
const errorMessage = e?.response?.data?.message ||
e?.response?.data?.error ||
e?.message ||
(recordId ? 'Failed to update medical record' : 'Failed to create medical record');
this.error = errorMessage;
// Auto-hide error after 5 seconds
setTimeout(() => {
this.error = null;
}, 5000);
} finally {
this.loading = false;
}
}
editMedicalRecord(record: MedicalRecord) {
this.newMedicalRecord = {
patientId: record.patientId,
doctorId: record.doctorId,
appointmentId: record.appointmentId,
recordType: record.recordType,
title: record.title,
content: record.content,
diagnosisCode: record.diagnosisCode
};
this.showCreateMedicalRecord = true;
(this.newMedicalRecord as any).recordId = record.id;
}
async deleteMedicalRecord(recordId: string) {
const confirmed = await this.modalService.confirm(
'Are you sure you want to delete this medical record? This action cannot be undone.',
'Delete Medical Record',
'Delete',
'Cancel'
);
if (!confirmed) {
return;
}
try {
this.loading = true;
await this.medicalRecordService.deleteMedicalRecord(recordId);
if (this.showAllRecords) {
await this.loadAllDoctorRecords();
} else if (this.localSelectedPatientId) {
await this.loadPatientMedicalRecords();
}
this.dataChanged.emit();
} catch (e: any) {
this.error = e?.response?.data?.error || 'Failed to delete medical record';
} finally {
this.loading = false;
}
}
// Vital Signs Methods
openCreateVitalSigns() {
this.showCreateVitalSigns = !this.showCreateVitalSigns;
if (this.showCreateVitalSigns) {
this.newVitalSigns = {
patientId: this.localSelectedPatientId || '',
temperature: undefined,
bloodPressureSystolic: undefined,
bloodPressureDiastolic: undefined,
heartRate: undefined,
respiratoryRate: undefined,
oxygenSaturation: undefined,
weight: undefined,
height: undefined,
notes: ''
};
}
}
async createVitalSigns() {
if (!this.newVitalSigns.patientId) {
this.error = 'Please select a patient';
return;
}
try {
this.loading = true;
await this.medicalRecordService.createVitalSigns(this.newVitalSigns);
this.showCreateVitalSigns = false;
this.newVitalSigns = {
patientId: '',
temperature: undefined,
bloodPressureSystolic: undefined,
bloodPressureDiastolic: undefined,
heartRate: undefined,
respiratoryRate: undefined,
oxygenSaturation: undefined,
weight: undefined,
height: undefined,
notes: ''
};
if (this.localSelectedPatientId) {
await this.loadPatientVitalSigns();
}
this.dataChanged.emit();
} catch (e: any) {
this.error = e?.response?.data?.error || 'Failed to create vital signs';
} finally {
this.loading = false;
}
}
async deleteVitalSigns(vitalSignsId: string) {
const confirmed = await this.modalService.confirm(
'Are you sure you want to delete this vital signs record?',
'Delete Vital Signs',
'Delete',
'Cancel'
);
if (!confirmed) {
return;
}
try {
this.loading = true;
await this.medicalRecordService.deleteVitalSigns(vitalSignsId);
if (this.localSelectedPatientId) {
await this.loadPatientVitalSigns();
}
this.dataChanged.emit();
} catch (e: any) {
this.error = e?.response?.data?.error || 'Failed to delete vital signs';
} finally {
this.loading = false;
}
}
// Lab Results Methods
openCreateLabResult() {
this.showCreateLabResult = !this.showCreateLabResult;
this.showUpdateLabResult = false;
if (this.showCreateLabResult) {
this.newLabResult = {
patientId: undefined as any,
doctorId: this.doctorId || '',
testName: '',
resultValue: '',
status: 'PENDING',
orderedDate: new Date().toISOString().split('T')[0]
};
if (this.localSelectedPatientId) {
this.newLabResult.patientId = this.localSelectedPatientId as any;
}
delete (this.newLabResult as any).labResultId;
}
}
async createLabResult() {
// Auto-fill patientId from context if missing
if (!this.newLabResult.patientId) {
if (this.localSelectedPatientId) {
this.newLabResult.patientId = this.localSelectedPatientId as any;
} else if (Array.isArray(this.patients) && this.patients.length === 1) {
this.newLabResult.patientId = this.patients[0].id as any;
}
}
// If patientId is not in known patient IDs, try to resolve from userId
if (this.newLabResult.patientId && Array.isArray(this.patients) && !this.patients.some(p => p.id === this.newLabResult.patientId)) {
try {
const resolved = await this.userService.getPatientIdByUserId(this.newLabResult.patientId as any);
if (resolved) {
this.newLabResult.patientId = resolved as any;
}
} catch {}
}
const missing: string[] = [];
if (!this.newLabResult.patientId) missing.push('patient');
if (!this.newLabResult.testName) missing.push('test name');
if (!this.newLabResult.resultValue) missing.push('result value');
if (missing.length) {
this.error = `Please fill in all required fields: ${missing.join(', ')}`;
return;
}
try {
this.loading = true;
const labResultId = (this.newLabResult as any).labResultId;
if (labResultId) {
await this.medicalRecordService.updateLabResult(labResultId, this.newLabResult);
} else {
await this.medicalRecordService.createLabResult(this.newLabResult);
}
this.showCreateLabResult = false;
this.showUpdateLabResult = false;
this.newLabResult = {
patientId: undefined as any,
doctorId: this.doctorId || '',
testName: '',
resultValue: '',
status: 'PENDING',
orderedDate: new Date().toISOString().split('T')[0]
};
delete (this.newLabResult as any).labResultId;
if (this.localSelectedPatientId) {
await this.loadPatientLabResults();
}
this.dataChanged.emit();
} catch (e: any) {
this.logger.error('[CreateLabResult] Error:', e);
let errorMsg = 'Failed to create lab result';
if (e?.response?.data) {
if (typeof e.response.data === 'string') {
errorMsg = e.response.data;
} else if (e.response.data.error) {
errorMsg = e.response.data.error;
} else if (e.response.data.message) {
errorMsg = e.response.data.message;
}
} else if (e?.message) {
errorMsg = e.message;
}
this.error = errorMsg;
} finally {
this.loading = false;
}
}
editLabResult(labResult: any) {
this.selectedLabResult = labResult;
this.newLabResult = {
patientId: labResult.patientId,
doctorId: labResult.doctorId,
testName: labResult.testName,
resultValue: labResult.resultValue,
referenceRange: labResult.referenceRange,
unit: labResult.unit,
status: labResult.status,
orderedDate: labResult.orderedDate,
resultDate: labResult.resultDate,
notes: labResult.notes
};
this.showCreateLabResult = true;
this.showUpdateLabResult = true;
(this.newLabResult as any).labResultId = labResult.id;
}
async deleteLabResult(labResultId: string) {
const confirmed = await this.modalService.confirm(
'Are you sure you want to delete this lab result?',
'Delete Lab Result',
'Delete',
'Cancel'
);
if (!confirmed) {
return;
}
try {
this.loading = true;
await this.medicalRecordService.deleteLabResult(labResultId);
if (this.localSelectedPatientId) {
await this.loadPatientLabResults();
}
this.dataChanged.emit();
} catch (e: any) {
this.error = e?.response?.data?.error || 'Failed to delete lab result';
} finally {
this.loading = false;
}
}
formatDate(dateString: string): string {
if (!dateString) return 'N/A';
const date = new Date(dateString);
return date.toLocaleDateString();
}
formatDateTime(dateString: string): string {
if (!dateString) return 'N/A';
const date = new Date(dateString);
return date.toLocaleString();
}
getCurrentRecords() {
return this.showAllRecords ? this.allMedicalRecords : this.medicalRecords;
}
isUpdatingMedicalRecord(): boolean {
return !!(this.newMedicalRecord as any).recordId;
}
isUpdatingLabResult(): boolean {
return !!(this.newLabResult as any).labResultId;
}
cancelMedicalRecordForm() {
this.showCreateMedicalRecord = false;
delete (this.newMedicalRecord as any).recordId;
}
cancelLabResultForm() {
this.showCreateLabResult = false;
this.showUpdateLabResult = false;
delete (this.newLabResult as any).labResultId;
}
async viewPatientProfile(patientId: string) {
try {
this.selectedPatientProfile = await this.userService.getPatientProfileById(patientId);
this.showPatientProfileModal = true;
} catch (e: any) {
this.error = e?.message || 'Failed to load patient profile';
this.logger.error('Failed to load patient profile:', e);
}
}
closePatientProfileModal() {
this.showPatientProfileModal = false;
this.selectedPatientProfile = null;
}
filterPatients() {
if (!this.patientSearchQuery || this.patientSearchQuery.trim() === '') {
this.filteredPatients = this.patients || [];
return;
}
const query = this.patientSearchQuery.toLowerCase().trim();
this.filteredPatients = (this.patients || []).filter(patient => {
const firstName = (patient.firstName || '').toLowerCase();
const lastName = (patient.lastName || '').toLowerCase();
const fullName = `${firstName} ${lastName}`.trim();
const email = (patient.email || '').toLowerCase();
return firstName.includes(query) ||
lastName.includes(query) ||
fullName.includes(query) ||
email.includes(query);
});
}
onPatientSearchChange() {
this.filterPatients();
}
clearPatientSearch() {
this.patientSearchQuery = '';
this.filterPatients();
}
}