/* * File: patientCareSelectors.ts * Description: Redux selectors for patient care state * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../../../store'; import { PatientData } from './patientCareSlice'; // ============================================================================ // BASE SELECTORS // ============================================================================ /** * Select Patient Care State * * Purpose: Get the entire patient care state */ export const selectPatientCareState = (state: RootState) => state.patientCare; /** * Select Patients * * Purpose: Get the patients array */ export const selectPatients = (state: RootState) => state.patientCare.patients; /** * Select Current Patient * * Purpose: Get the currently selected patient */ export const selectCurrentPatient = (state: RootState) => state.patientCare.currentPatient; /** * Select Patients Loading State * * Purpose: Get the loading state for patients */ export const selectPatientsLoading = (state: RootState) => state.patientCare.isLoading; /** * Select Is Refreshing State * * Purpose: Get the refreshing state for pull-to-refresh */ export const selectIsRefreshing = (state: RootState) => state.patientCare.isRefreshing; /** * Select Patient Details Loading State * * Purpose: Get the loading state for patient details */ export const selectPatientDetailsLoading = (state: RootState) => state.patientCare.isLoadingPatientDetails; /** * Select Patients Error * * Purpose: Get the error state for patients */ export const selectPatientsError = (state: RootState) => state.patientCare.error; /** * Select Search Query * * Purpose: Get the current search query */ export const selectSearchQuery = (state: RootState) => state.patientCare.searchQuery; /** * Select Selected Filter * * Purpose: Get the currently selected filter */ export const selectSelectedFilter = (state: RootState) => state.patientCare.selectedFilter; /** * Select Sort By * * Purpose: Get the current sort option */ export const selectSortBy = (state: RootState) => state.patientCare.sortBy; /** * Select Sort Order * * Purpose: Get the current sort order */ export const selectSortOrder = (state: RootState) => state.patientCare.sortOrder; /** * Select Pagination Info * * Purpose: Get pagination-related state */ export const selectPaginationInfo = (state: RootState) => ({ currentPage: state.patientCare.currentPage, itemsPerPage: state.patientCare.itemsPerPage, totalItems: state.patientCare.totalItems, }); /** * Select Last Updated * * Purpose: Get the last updated timestamp */ export const selectLastUpdated = (state: RootState) => state.patientCare.lastUpdated; // ============================================================================ // COMPUTED SELECTORS // ============================================================================ /** * Select Filtered Patients * * Purpose: Get patients filtered by search query and selected filter */ export const selectFilteredPatients = createSelector( [selectPatients, selectSearchQuery, selectSelectedFilter, selectSortBy, selectSortOrder], (patients, searchQuery, selectedFilter, sortBy, sortOrder) => { // Ensure patients is always an array if (!patients || !Array.isArray(patients)) { return []; } let filteredPatients = [...patients]; // Apply filter based on processing status if (selectedFilter !== 'all') { filteredPatients = filteredPatients.filter((patient: PatientData) => { const status = patient.patient_info.status.toLowerCase(); return status === selectedFilter; }); } // Apply search if (searchQuery.trim()) { const query = searchQuery.toLowerCase().trim(); filteredPatients = filteredPatients.filter((patient: PatientData) => { const patientInfo = patient.patient_info; const name = (patientInfo.name || '').toLowerCase(); const patId = (patient.patid || '').toLowerCase(); const institution = (patientInfo.institution || '').toLowerCase(); const modality = (patientInfo.modality || '').toLowerCase(); return ( name.includes(query) || patId.includes(query) || institution.includes(query) || modality.includes(query) ); }); } // Apply sorting filteredPatients.sort((a: PatientData, b: PatientData) => { let aValue: any; let bValue: any; switch (sortBy) { case 'name': aValue = (a.patient_info.name || '').toLowerCase(); bValue = (b.patient_info.name || '').toLowerCase(); break; case 'processed': aValue = new Date(a.last_processed_at).getTime(); bValue = new Date(b.last_processed_at).getTime(); break; case 'date': default: aValue = new Date(a.patient_info.date).getTime(); bValue = new Date(b.patient_info.date).getTime(); break; } if (aValue < bValue) { return sortOrder === 'asc' ? -1 : 1; } if (aValue > bValue) { return sortOrder === 'asc' ? 1 : -1; } return 0; }); return filteredPatients; } ); /** * Select Processed Patients * * Purpose: Get patients with processed status */ export const selectProcessedPatients = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) return []; return patients.filter((patient: PatientData) => patient.patient_info.status.toLowerCase() === 'processed' ); } ); /** * Select Pending Patients * * Purpose: Get patients with pending status */ export const selectPendingPatients = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) return []; return patients.filter((patient: PatientData) => patient.patient_info.status.toLowerCase() === 'pending' ); } ); /** * Select Error Patients * * Purpose: Get patients with error status */ export const selectErrorPatients = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) return []; return patients.filter((patient: PatientData) => patient.patient_info.status.toLowerCase() === 'error' ); } ); /** * Select Patients by Modality * * Purpose: Get patients grouped by imaging modality */ export const selectPatientsByModality = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) return {}; const grouped: { [key: string]: PatientData[] } = {}; patients.forEach((patient: PatientData) => { const modality = patient.patient_info.modality || 'Unknown'; if (!grouped[modality]) { grouped[modality] = []; } grouped[modality].push(patient); }); return grouped; } ); /** * Select Patient Statistics * * Purpose: Get statistics about patients */ export const selectPatientStats = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) { return { total: 0, processed: 0, pending: 0, averageAge: 0, modalities: {}, totalFiles: 0, processedPercentage: 0, pendingPercentage: 0, }; } const total = patients.length; const processed = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'processed').length; const pending = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'pending').length; // Calculate average age const totalAge = patients.reduce((sum: number, patient: PatientData) => { const age = parseInt(patient.patient_info.age) || 0; return sum + age; }, 0); const averageAge = total > 0 ? Math.round(totalAge / total) : 0; // Modality distribution const modalities: { [key: string]: number } = {}; patients.forEach((patient: PatientData) => { const modality = patient.patient_info.modality || 'Unknown'; modalities[modality] = (modalities[modality] || 0) + 1; }); // Total files processed const totalFiles = patients.reduce((sum: number, patient: PatientData) => sum + (patient.total_files_processed || 0), 0); return { total, processed, pending, averageAge, modalities, totalFiles, processedPercentage: total > 0 ? Math.round((processed / total) * 100) : 0, pendingPercentage: total > 0 ? Math.round((pending / total) * 100) : 0, }; } ); /** * Select Patient by ID * * Purpose: Get a specific patient by ID * * @param patientId - The ID of the patient to find */ export const selectPatientById = (patientId: string) => createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) return undefined; return patients.find((patient: PatientData) => patient.patid === patientId); } ); /** * Select Patients Need Attention * * Purpose: Get patients that need immediate attention */ export const selectPatientsNeedAttention = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) return []; return patients.filter((patient: PatientData) => { // Error patients always need attention if (patient.patient_info.status.toLowerCase() === 'error') return true; // Patients with critical report status if (patient.patient_info.report_status.toLowerCase() === 'critical') return true; // Patients with high frame count (complex cases) if (patient.patient_info.frame_count > 100) return true; // Patients with multiple series (complex cases) if (patient.series_summary.length > 5) return true; return false; }); } ); /** * Select Has Data * * Purpose: Check if we have patient data */ export const selectHasPatientData = createSelector( [selectPatients], (patients) => patients && Array.isArray(patients) && patients.length > 0 ); /** * Select Is Empty State * * Purpose: Check if we should show empty state */ export const selectIsEmptyState = createSelector( [selectPatients, selectPatientsLoading, selectFilteredPatients], (patients, isLoading, filteredPatients) => !isLoading && patients && Array.isArray(patients) && patients.length > 0 && filteredPatients.length === 0 ); /** * Select Patient Counts for Filters * * Purpose: Get patient counts for each filter category */ export const selectPatientCounts = createSelector( [selectPatients], (patients) => { if (!patients || !Array.isArray(patients)) { return { all: 0, processed: 0, pending: 0 }; } return { all: patients.length, processed: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'processed').length, pending: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'pending').length, }; } ); /* * End of File: patientCareSelectors.ts * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */