NeoScan_Physician/app/modules/PatientCare/redux/patientCareSelectors.ts
2025-08-22 00:24:24 +05:30

405 lines
11 KiB
TypeScript

/*
* 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.
*/