458 lines
13 KiB
TypeScript
458 lines
13 KiB
TypeScript
/*
|
|
* File: patientCareSlice.ts
|
|
* Description: Patient care state management slice
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
|
import { MedicalCase, PatientCareState } from '../../../shared/types';
|
|
import { patientAPI } from '../services/patientAPI';
|
|
|
|
// ============================================================================
|
|
// ASYNC THUNKS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Fetch Patients Async Thunk
|
|
*
|
|
* Purpose: Fetch patients list from API
|
|
*
|
|
* @returns Promise with patients data or error
|
|
*/
|
|
export const fetchPatients = createAsyncThunk(
|
|
'patientCare/fetchPatients',
|
|
async (token: string, { rejectWithValue }) => {
|
|
try {
|
|
// Make actual API call to fetch medical cases
|
|
const response :any = await patientAPI.getPatients(token);
|
|
if (response.ok && response.data&&response.data.success) {
|
|
// Add random case types to each patient record
|
|
const caseTypes: Array<'Critical' | 'Emergency' | 'Routine'> = ['Critical', 'Emergency', 'Routine'];
|
|
|
|
const patientsWithTypes = response.data.data.map((patient: any) => ({
|
|
...patient,
|
|
type: caseTypes[Math.floor(Math.random() * caseTypes.length)]
|
|
}));
|
|
|
|
return patientsWithTypes as MedicalCase[];
|
|
} else {
|
|
// Fallback to mock data for development
|
|
const mockPatients: MedicalCase[] = [
|
|
{
|
|
id: 1,
|
|
patientdetails: JSON.stringify({
|
|
patientdetails: {
|
|
Date: '2024-01-15',
|
|
Name: 'John Doe',
|
|
PatID: 'MRN001',
|
|
PatAge: '38',
|
|
PatSex: 'M',
|
|
Status: 'Active',
|
|
InstName: 'City General Hospital',
|
|
Modality: 'CT',
|
|
ReportStatus: 'Pending'
|
|
}
|
|
}),
|
|
series: JSON.stringify([
|
|
{
|
|
Path: ['/dicom/series1'],
|
|
SerDes: 'Chest CT',
|
|
ViePos: 'Supine',
|
|
pngpath: '/images/ct_chest_1.png',
|
|
SeriesNum: '1',
|
|
ImgTotalinSeries: '50'
|
|
}
|
|
]),
|
|
created_at: '2024-01-15T10:30:00Z',
|
|
updated_at: '2024-01-15T11:45:00Z',
|
|
series_id: 'series_001',
|
|
type: 'Critical'
|
|
},
|
|
{
|
|
id: 2,
|
|
patientdetails: JSON.stringify({
|
|
patientdetails: {
|
|
Date: '2024-01-15',
|
|
Name: 'Jane Smith',
|
|
PatID: 'MRN002',
|
|
PatAge: '33',
|
|
PatSex: 'F',
|
|
Status: 'Active',
|
|
InstName: 'Memorial Medical Center',
|
|
Modality: 'MR',
|
|
ReportStatus: 'Completed'
|
|
}
|
|
}),
|
|
series: JSON.stringify([
|
|
{
|
|
Path: ['/dicom/series2'],
|
|
SerDes: 'Brain MRI',
|
|
ViePos: 'Supine',
|
|
pngpath: '/images/mri_brain_1.png',
|
|
SeriesNum: '2',
|
|
ImgTotalinSeries: '120'
|
|
}
|
|
]),
|
|
created_at: '2024-01-15T09:15:00Z',
|
|
updated_at: '2024-01-15T10:30:00Z',
|
|
series_id: 'series_002',
|
|
type: 'Routine'
|
|
},
|
|
];
|
|
|
|
return mockPatients;
|
|
}
|
|
} catch (error: any) {
|
|
console.error('Fetch patients error:', error);
|
|
return rejectWithValue(error.message || 'Failed to fetch patients.');
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Fetch Patient Details Async Thunk
|
|
*
|
|
* Purpose: Fetch detailed patient information
|
|
*
|
|
* @param patientId - ID of the patient to fetch
|
|
* @returns Promise with patient details or error
|
|
*/
|
|
export const fetchPatientDetails = createAsyncThunk(
|
|
'patientCare/fetchPatientDetails',
|
|
async (patientId: string, { rejectWithValue }) => {
|
|
try {
|
|
// TODO: Replace with actual API call
|
|
await new Promise((resolve) => setTimeout(resolve as any, 1000));
|
|
|
|
// Mock patient details for specific patient
|
|
const mockPatient: MedicalCase = {
|
|
id: parseInt(patientId),
|
|
patientdetails: JSON.stringify({
|
|
patientdetails: {
|
|
Date: '2024-01-15',
|
|
Name: 'John Doe',
|
|
PatID: `MRN${patientId.padStart(3, '0')}`,
|
|
PatAge: '38',
|
|
PatSex: 'M',
|
|
Status: 'Active',
|
|
InstName: 'City General Hospital',
|
|
Modality: 'CT',
|
|
ReportStatus: 'Pending'
|
|
}
|
|
}),
|
|
series: JSON.stringify([
|
|
{
|
|
Path: [`/dicom/series${patientId}`],
|
|
SerDes: 'Chest CT',
|
|
ViePos: 'Supine',
|
|
pngpath: `/images/ct_chest_${patientId}.png`,
|
|
SeriesNum: patientId,
|
|
ImgTotalinSeries: '50'
|
|
}
|
|
]),
|
|
created_at: '2024-01-15T10:30:00Z',
|
|
updated_at: '2024-01-15T11:45:00Z',
|
|
series_id: `series_${patientId.padStart(3, '0')}`,
|
|
type: 'Critical'
|
|
};
|
|
|
|
return mockPatient;
|
|
} catch (error) {
|
|
return rejectWithValue('Failed to fetch patient details.');
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Update Patient Async Thunk
|
|
*
|
|
* Purpose: Update patient information
|
|
*
|
|
* @param patientData - Updated patient data
|
|
* @returns Promise with updated patient or error
|
|
*/
|
|
export const updatePatient = createAsyncThunk(
|
|
'patientCare/updatePatient',
|
|
async (patientData: Partial<MedicalCase> & { id: number }, { rejectWithValue }) => {
|
|
try {
|
|
// TODO: Replace with actual API call
|
|
await new Promise((resolve) => setTimeout(resolve as any, 800));
|
|
return patientData;
|
|
} catch (error) {
|
|
return rejectWithValue('Failed to update patient.');
|
|
}
|
|
}
|
|
);
|
|
|
|
// ============================================================================
|
|
// INITIAL STATE
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Initial Patient Care State
|
|
*
|
|
* Purpose: Define the initial state for patient care
|
|
*
|
|
* Features:
|
|
* - Patients list and management
|
|
* - Current patient details
|
|
* - Loading states for async operations
|
|
* - Error handling and messages
|
|
* - Search and filtering
|
|
*/
|
|
const initialState: PatientCareState = {
|
|
// Patients data
|
|
patients: [],
|
|
currentPatient: null,
|
|
|
|
// Loading states
|
|
isLoading: false,
|
|
isRefreshing: false,
|
|
isLoadingPatientDetails: false,
|
|
|
|
// Error handling
|
|
error: null,
|
|
|
|
// Search and filtering
|
|
searchQuery: '',
|
|
selectedFilter: 'all',
|
|
sortBy: 'date',
|
|
sortOrder: 'desc',
|
|
|
|
// Pagination
|
|
currentPage: 1,
|
|
itemsPerPage: 20,
|
|
totalItems: 0,
|
|
|
|
// Cache
|
|
lastUpdated: null,
|
|
cacheExpiry: null,
|
|
};
|
|
|
|
// ============================================================================
|
|
// PATIENT CARE SLICE
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Patient Care Slice
|
|
*
|
|
* Purpose: Redux slice for patient care state management
|
|
*
|
|
* Features:
|
|
* - Patient data management
|
|
* - Search and filtering
|
|
* - Pagination
|
|
* - Caching
|
|
* - Error handling
|
|
* - Loading states
|
|
*/
|
|
const patientCareSlice = createSlice({
|
|
name: 'patientCare',
|
|
initialState,
|
|
reducers: {
|
|
/**
|
|
* Clear Error Action
|
|
*
|
|
* Purpose: Clear patient care errors
|
|
*/
|
|
clearError: (state) => {
|
|
state.error = null;
|
|
},
|
|
|
|
/**
|
|
* Set Search Query Action
|
|
*
|
|
* Purpose: Set search query for patients
|
|
*/
|
|
setSearchQuery: (state, action: PayloadAction<string>) => {
|
|
state.searchQuery = action.payload;
|
|
state.currentPage = 1; // Reset to first page when searching
|
|
},
|
|
|
|
/**
|
|
* Set Filter Action
|
|
*
|
|
* Purpose: Set patient filter
|
|
*/
|
|
setFilter: (state, action: PayloadAction<'all' | 'Critical' | 'Routine' | 'Emergency'>) => {
|
|
state.selectedFilter = action.payload;
|
|
state.currentPage = 1; // Reset to first page when filtering
|
|
},
|
|
|
|
/**
|
|
* Set Sort Action
|
|
*
|
|
* Purpose: Set patient sort options
|
|
*/
|
|
setSort: (state, action: PayloadAction<{ by: 'date' | 'name' | 'age'; order: 'asc' | 'desc' }>) => {
|
|
state.sortBy = action.payload.by;
|
|
state.sortOrder = action.payload.order;
|
|
},
|
|
|
|
/**
|
|
* Set Current Page Action
|
|
*
|
|
* Purpose: Set current page for pagination
|
|
*/
|
|
setCurrentPage: (state, action: PayloadAction<number>) => {
|
|
state.currentPage = action.payload;
|
|
},
|
|
|
|
/**
|
|
* Set Items Per Page Action
|
|
*
|
|
* Purpose: Set items per page for pagination
|
|
*/
|
|
setItemsPerPage: (state, action: PayloadAction<number>) => {
|
|
state.itemsPerPage = action.payload;
|
|
state.currentPage = 1; // Reset to first page when changing items per page
|
|
},
|
|
|
|
/**
|
|
* Set Current Patient Action
|
|
*
|
|
* Purpose: Set the currently selected patient
|
|
*/
|
|
setCurrentPatient: (state, action: PayloadAction<MedicalCase | null>) => {
|
|
state.currentPatient = action.payload;
|
|
},
|
|
|
|
/**
|
|
* Update Patient in List Action
|
|
*
|
|
* Purpose: Update a patient in the patients list
|
|
*/
|
|
updatePatientInList: (state, action: PayloadAction<MedicalCase>) => {
|
|
const index = state.patients.findIndex(patient => patient.id === action.payload.id);
|
|
if (index !== -1) {
|
|
state.patients[index] = action.payload;
|
|
}
|
|
|
|
// Update current patient if it's the same patient
|
|
if (state.currentPatient && state.currentPatient.id === action.payload.id) {
|
|
state.currentPatient = action.payload;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add Patient Action
|
|
*
|
|
* Purpose: Add a new patient to the list
|
|
*/
|
|
addPatient: (state, action: PayloadAction<MedicalCase>) => {
|
|
state.patients.unshift(action.payload);
|
|
state.totalItems += 1;
|
|
},
|
|
|
|
/**
|
|
* Remove Patient Action
|
|
*
|
|
* Purpose: Remove a patient from the list
|
|
*/
|
|
removePatient: (state, action: PayloadAction<number>) => {
|
|
const index = state.patients.findIndex(patient => patient.id === action.payload);
|
|
if (index !== -1) {
|
|
state.patients.splice(index, 1);
|
|
state.totalItems -= 1;
|
|
}
|
|
|
|
// Clear current patient if it's the same patient
|
|
if (state.currentPatient && state.currentPatient.id === action.payload) {
|
|
state.currentPatient = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear Cache Action
|
|
*
|
|
* Purpose: Clear patient data cache
|
|
*/
|
|
clearCache: (state) => {
|
|
state.patients = [];
|
|
state.currentPatient = null;
|
|
state.lastUpdated = null;
|
|
state.cacheExpiry = null;
|
|
},
|
|
},
|
|
extraReducers: (builder) => {
|
|
// Fetch Patients
|
|
builder
|
|
.addCase(fetchPatients.pending, (state) => {
|
|
state.isLoading = true;
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchPatients.fulfilled, (state, action) => {
|
|
state.isLoading = false;
|
|
state.patients = action.payload;
|
|
state.totalItems = action.payload.length;
|
|
state.lastUpdated = new Date().toLocaleDateString();
|
|
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000).toLocaleDateString(); // 5 minutes
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchPatients.rejected, (state, action) => {
|
|
state.isLoading = false;
|
|
state.error = action.payload as string;
|
|
});
|
|
|
|
// Fetch Patient Details
|
|
builder
|
|
.addCase(fetchPatientDetails.pending, (state) => {
|
|
state.isLoadingPatientDetails = true;
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchPatientDetails.fulfilled, (state, action) => {
|
|
state.isLoadingPatientDetails = false;
|
|
state.currentPatient = action.payload;
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchPatientDetails.rejected, (state, action) => {
|
|
state.isLoadingPatientDetails = false;
|
|
state.error = action.payload as string;
|
|
});
|
|
|
|
// Update Patient
|
|
builder
|
|
.addCase(updatePatient.fulfilled, (state, action) => {
|
|
// Update patient in list
|
|
const index = state.patients.findIndex(patient => patient.id === action.payload.id);
|
|
if (index !== -1) {
|
|
state.patients[index] = { ...state.patients[index], ...action.payload };
|
|
}
|
|
|
|
// Update current patient if it's the same patient
|
|
if (state.currentPatient && state.currentPatient.id === action.payload.id) {
|
|
state.currentPatient = { ...state.currentPatient, ...action.payload };
|
|
}
|
|
})
|
|
.addCase(updatePatient.rejected, (state, action) => {
|
|
state.error = action.payload as string;
|
|
});
|
|
},
|
|
});
|
|
|
|
// ============================================================================
|
|
// EXPORTS
|
|
// ============================================================================
|
|
|
|
export const {
|
|
clearError,
|
|
setSearchQuery,
|
|
setFilter,
|
|
setSort,
|
|
setCurrentPage,
|
|
setItemsPerPage,
|
|
setCurrentPatient,
|
|
updatePatientInList,
|
|
addPatient,
|
|
removePatient,
|
|
clearCache,
|
|
} = patientCareSlice.actions;
|
|
|
|
export default patientCareSlice.reducer;
|
|
|
|
/*
|
|
* End of File: patientCareSlice.ts
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|