264 lines
9.6 KiB
TypeScript
264 lines
9.6 KiB
TypeScript
import { Component, Input, OnInit, OnChanges, SimpleChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { UserService } from '../../../../services/user.service';
|
|
import { AppointmentService } from '../../../../services/appointment.service';
|
|
import { ModalService } from '../../../../services/modal.service';
|
|
import { ChatService } from '../../../../services/chat.service';
|
|
import { LoggerService } from '../../../../services/logger.service';
|
|
|
|
@Component({
|
|
selector: 'app-patients',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule],
|
|
templateUrl: './patients.component.html',
|
|
styleUrl: './patients.component.scss'
|
|
})
|
|
export class PatientsComponent implements OnInit, OnChanges {
|
|
@Input() patients: any[] = []; // Fallback from appointments
|
|
@Output() patientRemoved = new EventEmitter<void>();
|
|
@Output() patientSelected = new EventEmitter<any>();
|
|
@Output() createAppointmentRequested = new EventEmitter<any>();
|
|
@Output() startChatRequested = new EventEmitter<any>();
|
|
|
|
loading = false;
|
|
loadingPatients = false;
|
|
searchTerm: string = '';
|
|
filteredPatients: any[] = [];
|
|
allPatients: any[] = []; // All patients from system
|
|
|
|
constructor(
|
|
private userService: UserService,
|
|
private appointmentService: AppointmentService,
|
|
private modalService: ModalService,
|
|
private chatService: ChatService,
|
|
private cdr: ChangeDetectorRef,
|
|
private logger: LoggerService
|
|
) {}
|
|
|
|
async ngOnInit() {
|
|
this.logger.debug('[PatientsComponent] ngOnInit - patients:', this.patients.length);
|
|
// Load all patients from system
|
|
await this.loadAllPatients();
|
|
// Initialize filtered patients
|
|
this.applySearch();
|
|
}
|
|
|
|
async loadAllPatients() {
|
|
try {
|
|
this.loadingPatients = true;
|
|
const patients = await this.userService.getAllPatients();
|
|
this.logger.debug('[PatientsComponent] Loaded all patients from system:', patients.length);
|
|
|
|
// Map PatientProfile to the format we need
|
|
// PatientProfile has firstName, lastName, email, phoneNumber directly (not nested in user)
|
|
// Now also includes userId for chat functionality
|
|
this.allPatients = patients.map(p => ({
|
|
id: p.id,
|
|
userId: p.userId, // Include userId for chat functionality
|
|
firstName: p.firstName || '',
|
|
lastName: p.lastName || '',
|
|
email: p.email || '',
|
|
phoneNumber: p.phoneNumber || '',
|
|
displayName: `${p.firstName || ''} ${p.lastName || ''}`.trim(),
|
|
bloodType: p.bloodType,
|
|
allergies: p.allergies || []
|
|
}));
|
|
|
|
// Merge with patients from appointments (if any) to avoid duplicates
|
|
const existingIds = new Set(this.allPatients.map(p => p.id));
|
|
const newPatients = this.patients.filter(p => p.id && !existingIds.has(p.id));
|
|
this.allPatients = [...this.allPatients, ...newPatients];
|
|
|
|
// Update patients array to include all
|
|
this.patients = this.allPatients;
|
|
this.applySearch();
|
|
} catch (error: any) {
|
|
this.logger.error('[PatientsComponent] Error loading all patients:', error);
|
|
// Fallback to patients from input
|
|
this.patients = this.patients || [];
|
|
this.applySearch();
|
|
} finally {
|
|
this.loadingPatients = false;
|
|
this.cdr.detectChanges();
|
|
}
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
if (changes['patients'] && !changes['patients'].firstChange) {
|
|
this.logger.debug('[PatientsComponent] ngOnChanges - patients changed:', {
|
|
previousValue: changes['patients'].previousValue?.length,
|
|
currentValue: changes['patients'].currentValue?.length
|
|
});
|
|
// Merge with all patients if needed
|
|
if (this.allPatients.length > 0) {
|
|
const existingIds = new Set(this.allPatients.map(p => p.id));
|
|
const newPatients = this.patients.filter(p => p.id && !existingIds.has(p.id));
|
|
this.allPatients = [...this.allPatients, ...newPatients];
|
|
this.patients = this.allPatients;
|
|
}
|
|
this.applySearch();
|
|
}
|
|
}
|
|
|
|
applySearch() {
|
|
this.logger.debug('[PatientsComponent] applySearch called', {
|
|
searchTerm: this.searchTerm,
|
|
patientsCount: this.patients.length,
|
|
filteredCount: this.filteredPatients.length
|
|
});
|
|
|
|
// If no search term, show all patients
|
|
if (!this.searchTerm || this.searchTerm.trim() === '') {
|
|
this.filteredPatients = [...this.patients];
|
|
this.logger.debug('[PatientsComponent] No search term, showing all patients:', this.filteredPatients.length);
|
|
return;
|
|
}
|
|
|
|
// Filter patients based on search term
|
|
const search = this.searchTerm.toLowerCase().trim();
|
|
this.logger.debug('[PatientsComponent] Filtering with search term:', search);
|
|
|
|
this.filteredPatients = this.patients.filter(patient => {
|
|
// Search by name (first name + last name)
|
|
const fullName = `${patient.firstName || ''} ${patient.lastName || ''}`.toLowerCase();
|
|
const nameMatch = fullName.includes(search);
|
|
|
|
// Search by email
|
|
const emailMatch = patient.email && patient.email.toLowerCase().includes(search);
|
|
|
|
// Search by phone number
|
|
const phoneMatch = patient.phoneNumber && patient.phoneNumber.toLowerCase().includes(search);
|
|
|
|
const matches = nameMatch || emailMatch || phoneMatch;
|
|
if (matches) {
|
|
this.logger.debug('[PatientsComponent] Patient matches:', patient.firstName, patient.lastName);
|
|
}
|
|
return matches;
|
|
});
|
|
|
|
this.logger.debug('[PatientsComponent] Filtered results:', this.filteredPatients.length);
|
|
}
|
|
|
|
onSearchChange() {
|
|
this.logger.debug('[PatientsComponent] onSearchChange called, searchTerm:', this.searchTerm);
|
|
this.applySearch();
|
|
this.cdr.detectChanges(); // Force change detection
|
|
}
|
|
|
|
clearSearch() {
|
|
this.searchTerm = '';
|
|
this.applySearch();
|
|
}
|
|
|
|
async loadPatients() {
|
|
// Reload all patients from system
|
|
await this.loadAllPatients();
|
|
}
|
|
|
|
async createAppointment(patient: any) {
|
|
this.createAppointmentRequested.emit(patient);
|
|
}
|
|
|
|
async startChat(patient: any) {
|
|
try {
|
|
// Get the user ID for the patient
|
|
// PatientProfile now includes userId directly
|
|
let userId: string | null = patient.userId || null;
|
|
|
|
// If userId is not available, try to find it by searching (fallback)
|
|
if (!userId) {
|
|
this.logger.warn('[PatientsComponent] userId not found in patient object, searching...', patient);
|
|
|
|
// Try searching by full name
|
|
if (patient.firstName && patient.lastName) {
|
|
try {
|
|
const fullName = `${patient.firstName} ${patient.lastName}`;
|
|
const chatUsers = await this.chatService.searchPatients(fullName);
|
|
const matchingUser = chatUsers.find(u =>
|
|
u.firstName?.toLowerCase() === patient.firstName?.toLowerCase() &&
|
|
u.lastName?.toLowerCase() === patient.lastName?.toLowerCase()
|
|
);
|
|
if (matchingUser) {
|
|
userId = matchingUser.userId;
|
|
this.logger.debug('[PatientsComponent] Found user ID by name search:', userId);
|
|
}
|
|
} catch (e) {
|
|
this.logger.warn('[PatientsComponent] Could not search by name:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!userId) {
|
|
this.logger.error('[PatientsComponent] Could not find user ID for patient:', {
|
|
id: patient.id,
|
|
userId: patient.userId,
|
|
firstName: patient.firstName,
|
|
lastName: patient.lastName,
|
|
email: patient.email
|
|
});
|
|
await this.modalService.alert(
|
|
`Could not find patient user information for ${patient.firstName} ${patient.lastName}. The patient may not be available for chat.`,
|
|
'error',
|
|
'Error'
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Start chat with patient
|
|
this.logger.debug('[PatientsComponent] Starting chat with userId:', userId);
|
|
this.startChatRequested.emit({
|
|
userId: userId,
|
|
patientId: patient.id,
|
|
patientName: `${patient.firstName} ${patient.lastName}`
|
|
});
|
|
} catch (error: any) {
|
|
this.logger.error('[PatientsComponent] Error starting chat:', error);
|
|
await this.modalService.alert(
|
|
error?.message || 'Failed to start chat with patient',
|
|
'error',
|
|
'Error'
|
|
);
|
|
}
|
|
}
|
|
|
|
viewPatient(patient: any) {
|
|
this.patientSelected.emit(patient);
|
|
}
|
|
|
|
async removePatientFromHistory(patient: any) {
|
|
const confirmed = await this.modalService.confirm(
|
|
`Are you sure you want to remove ${patient.firstName} ${patient.lastName} from your history? This will hide all appointments with this patient from your view. This action cannot be undone.`,
|
|
'Remove Patient from History',
|
|
'warning'
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
this.loading = true;
|
|
await this.appointmentService.removePatientFromHistory(patient.id);
|
|
await this.modalService.alert(
|
|
'Patient removed from history successfully',
|
|
'success',
|
|
'Success'
|
|
);
|
|
|
|
// Remove from local list immediately (optimistic update)
|
|
this.patients = this.patients.filter(p => p.id !== patient.id);
|
|
this.filteredPatients = this.filteredPatients.filter(p => p.id !== patient.id);
|
|
|
|
// Emit event to parent to refresh data (will update the @Input binding)
|
|
this.patientRemoved.emit();
|
|
} catch (error: any) {
|
|
this.logger.error('Error removing patient from history:', error);
|
|
await this.modalService.alert(
|
|
error?.message || 'Failed to remove patient from history',
|
|
'error',
|
|
'Error'
|
|
);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
} |