/* * 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 { patientAPI } from '../services/patientAPI'; import { logoutUser } from '../../Auth/redux/authActions'; // ============================================================================ // TYPES // ============================================================================ /** * New API Response Types */ export interface SeriesSummary { series_num: string; series_description: string; total_images: number; png_preview: string; modality: string; } export interface PatientInfo { name: string; age: string; sex: string; date: string; institution: string; modality: string; status: string; report_status: string; file_name: string; file_type: string; frame_count: number; } export interface PatientData { patid: string; hospital_id: string; first_processed_at: string; last_processed_at: string; total_files_processed: number; patient_info: PatientInfo; series_summary: SeriesSummary[]; processing_metadata: any; } export interface PatientCareState { // Patients data patients: PatientData[]; currentPatient: PatientData | null; // Loading states isLoading: boolean; isRefreshing: boolean; isLoadingPatientDetails: boolean; // Error handling error: string | null; // Search and filtering searchQuery: string; selectedFilter: 'all' | 'processed' | 'pending'; sortBy: 'date' | 'name' | 'processed'; sortOrder: 'asc' | 'desc'; // Pagination currentPage: number; itemsPerPage: number; totalItems: number; // Cache lastUpdated: string | null; cacheExpiry: string | null; } // ============================================================================ // 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, dispatch }) => { try { const response: any = await patientAPI.getPatients(token); // Check for 401 Unauthorized status and logout user if (response.status === 401) { dispatch(logoutUser()); return rejectWithValue('Session expired. Please login again.'); } if (response.ok && response.data&& response.data.data) { // Return the patients data directly from the new API structure return response.data.data as PatientData[]; } else { // Fallback to mock data for development const mockPatients: PatientData[] = [ { patid: "demo001", hospital_id: "demo-hospital-001", first_processed_at: "2025-01-15T10:30:00Z", last_processed_at: "2025-01-15T11:45:00Z", total_files_processed: 3, patient_info: { name: "John Doe", age: "38", sex: "M", date: "2025-01-15", institution: "City General Hospital", modality: "CT", status: "Processed", report_status: "Available", file_name: "chest_ct_001.dcm", file_type: "dcm", frame_count: 50 }, series_summary: [ { series_num: "1", series_description: "Chest CT", total_images: 50, png_preview: "/images/ct_chest_1.png", modality: "CT" } ], processing_metadata: {} }, { patid: "demo002", hospital_id: "demo-hospital-002", first_processed_at: "2025-01-15T09:15:00Z", last_processed_at: "2025-01-15T10:30:00Z", total_files_processed: 2, patient_info: { name: "Jane Smith", age: "33", sex: "F", date: "2025-01-15", institution: "Memorial Medical Center", modality: "MR", status: "Processed", report_status: "Available", file_name: "brain_mri_001.dcm", file_type: "dcm", frame_count: 120 }, series_summary: [ { series_num: "1", series_description: "Brain MRI", total_images: 120, png_preview: "/images/mri_brain_1.png", modality: "MR" } ], processing_metadata: {} } ]; return []; } } 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: PatientData = { patid: patientId, hospital_id: `demo-hospital-${patientId}`, first_processed_at: "2025-01-15T10:30:00Z", last_processed_at: "2025-01-15T11:45:00Z", total_files_processed: 3, patient_info: { name: `Patient ${patientId}`, age: "38", sex: "M", date: "2025-01-15", institution: "City General Hospital", modality: "CT", status: "Processed", report_status: "Available", file_name: `patient_${patientId}.dcm`, file_type: "dcm", frame_count: 50 }, series_summary: [ { series_num: "1", series_description: "Chest CT", total_images: 50, png_preview: `/images/ct_chest_${patientId}.png`, modality: "CT" } ], processing_metadata: {} }; 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 & { patid: string }, { 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 * - Pagination and caching */ 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) => { 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' | 'processed' | 'pending'>) => { 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' | 'processed'; 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) => { state.currentPage = action.payload; }, /** * Set Items Per Page Action * * Purpose: Set items per page for pagination */ setItemsPerPage: (state, action: PayloadAction) => { 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) => { state.currentPatient = action.payload; }, /** * Update Patient in List Action * * Purpose: Update a patient in the patients list */ updatePatientInList: (state, action: PayloadAction) => { const index = state.patients.findIndex(patient => patient.patid === action.payload.patid); if (index !== -1) { state.patients[index] = action.payload; } // Update current patient if it's the same patient if (state.currentPatient && state.currentPatient.patid === action.payload.patid) { state.currentPatient = action.payload; } }, /** * Add Patient Action * * Purpose: Add a new patient to the list */ addPatient: (state, action: PayloadAction) => { state.patients.unshift(action.payload); state.totalItems += 1; }, /** * Remove Patient Action * * Purpose: Remove a patient from the list */ removePatient: (state, action: PayloadAction) => { const index = state.patients.findIndex(patient => patient.patid === 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.patid === 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.patid === action.payload.patid); 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.patid === action.payload.patid) { 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. */