388 lines
11 KiB
TypeScript
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.
|
|
*/ |