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(); @Output() dataChanged = new EventEmitter(); // 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(); } }