NeoScan_Radiologist/app/modules/PatientCare/redux/patientCareSelectors.ts
2025-08-07 19:42:41 +05:30

388 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/store';
import { MedicalCase } from '../../../shared/types';
// ============================================================================
// 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) => {
let filteredPatients = [...patients];
// Helper function to parse JSON strings safely
const parseJsonSafely = (jsonString: string | object) => {
if (typeof jsonString === 'object') {
return jsonString;
}
if (typeof jsonString === 'string') {
try {
return JSON.parse(jsonString);
} catch (error) {
console.warn('Failed to parse JSON:', error);
return {};
}
}
return {};
};
// Apply filter
if (selectedFilter !== 'all') {
filteredPatients = filteredPatients.filter(
patient => patient.type === selectedFilter
);
}
// Apply search
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase().trim();
filteredPatients = filteredPatients.filter(patient => {
const patientDetails = parseJsonSafely(patient.patientdetails);
const patientData = patientDetails.patientdetails || patientDetails;
const name = (patientData.Name || '').toLowerCase();
const patId = (patientData.PatID || '').toLowerCase();
const instName = (patientData.InstName || '').toLowerCase();
const modality = (patientData.Modality || '').toLowerCase();
return (
name.includes(query) ||
patId.includes(query) ||
instName.includes(query) ||
modality.includes(query)
);
});
}
// Apply sorting
filteredPatients.sort((a, b) => {
const patientDetailsA = parseJsonSafely(a.patientdetails);
const patientDataA = patientDetailsA.patientdetails || patientDetailsA;
const patientDetailsB = parseJsonSafely(b.patientdetails);
const patientDataB = patientDetailsB.patientdetails || patientDetailsB;
let aValue: any;
let bValue: any;
switch (sortBy) {
case 'name':
aValue = (patientDataA.Name || '').toLowerCase();
bValue = (patientDataB.Name || '').toLowerCase();
break;
case 'age':
aValue = parseInt(patientDataA.PatAge || '0');
bValue = parseInt(patientDataB.PatAge || '0');
break;
case 'date':
default:
aValue = new Date(a.created_at).getTime();
bValue = new Date(b.created_at).getTime();
break;
}
if (aValue < bValue) {
return sortOrder === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortOrder === 'asc' ? 1 : -1;
}
return 0;
});
return filteredPatients;
}
);
/**
* Select Critical Patients
*
* Purpose: Get patients with critical priority
*/
export const selectCriticalPatients = createSelector(
[selectPatients],
(patients) => patients.filter(patient => patient.type === 'Critical')
);
/**
* Select Active Patients
*
* Purpose: Get patients with active status
*/
export const selectActivePatients = createSelector(
[selectPatients],
(patients: MedicalCase[]) => patients.filter((patient: MedicalCase) => {
// Parse patient details to check status
const parseJsonSafely = (jsonString: string | object) => {
if (typeof jsonString === 'object') return jsonString;
if (typeof jsonString === 'string') {
try { return JSON.parse(jsonString); } catch { return {}; }
}
return {};
};
const patientDetails = parseJsonSafely(patient.patientdetails);
const patientData = patientDetails.patientdetails || patientDetails;
return patientData.Status === 'Active';
})
);
/**
* Select Patients by Department
*
* Purpose: Get patients grouped by department
*/
export const selectPatientsByDepartment = createSelector(
[selectPatients],
(patients: MedicalCase[]) => {
const grouped: { [key: string]: MedicalCase[] } = {};
patients.forEach((patient: MedicalCase) => {
const dept = patient.type; // Use case type instead of department
if (!grouped[dept]) {
grouped[dept] = [];
}
grouped[dept].push(patient);
});
return grouped;
}
);
/**
* Select Patient Statistics
*
* Purpose: Get statistics about patients
*/
export const selectPatientStats = createSelector(
[selectPatients],
(patients: MedicalCase[]) => {
const total = patients.length;
const critical = patients.filter((p: MedicalCase) => p.type === 'Critical').length;
const emergency = patients.filter((p: MedicalCase) => p.type === 'Emergency').length;
const routine = patients.filter((p: MedicalCase) => p.type === 'Routine').length;
// Parse patient details for age calculation
const parseJsonSafely = (jsonString: string | object) => {
if (typeof jsonString === 'object') return jsonString;
if (typeof jsonString === 'string') {
try { return JSON.parse(jsonString); } catch { return {}; }
}
return {};
};
const totalAge = patients.reduce((sum: number, patient: MedicalCase) => {
const patientDetails = parseJsonSafely(patient.patientdetails);
const patientData = patientDetails.patientdetails || patientDetails;
return sum + parseInt(patientData.PatAge || '0');
}, 0);
const averageAge = total > 0 ? Math.round(totalAge / total) : 0;
// Case type distribution
const caseTypes: { [key: string]: number } = {};
patients.forEach((patient: MedicalCase) => {
caseTypes[patient.type] = (caseTypes[patient.type] || 0) + 1;
});
return {
total,
critical,
emergency,
routine,
averageAge,
caseTypes,
criticalPercentage: total > 0 ? Math.round((critical / total) * 100) : 0,
emergencyPercentage: total > 0 ? Math.round((emergency / 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) => patients.find(patient => patient.id === patientId)
);
/**
* Select Patients Need Attention
*
* Purpose: Get patients that need immediate attention
*/
export const selectPatientsNeedAttention = createSelector(
[selectPatients],
(patients) => {
return patients.filter(patient => {
// Critical patients always need attention
if (patient.priority === 'CRITICAL') return true;
// Check vital signs for abnormal values
const vitals = patient.vitalSigns;
// Check blood pressure (hypertensive crisis)
if (vitals.bloodPressure.systolic > 180 || vitals.bloodPressure.diastolic > 120) {
return true;
}
// Check heart rate (too high or too low)
if (vitals.heartRate.value > 120 || vitals.heartRate.value < 50) {
return true;
}
// Check temperature (fever or hypothermia)
if (vitals.temperature.value > 38.5 || vitals.temperature.value < 35) {
return true;
}
// Check oxygen saturation (low)
if (vitals.oxygenSaturation.value < 90) {
return true;
}
return false;
});
}
);
/**
* Select Has Data
*
* Purpose: Check if we have patient data
*/
export const selectHasPatientData = createSelector(
[selectPatients],
(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.length > 0 && filteredPatients.length === 0
);
/*
* End of File: patientCareSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/