Start
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user