NeoScan_Radiologist/app/modules/Dashboard/redux/alertsSlice.ts
2025-08-05 18:01:36 +05:30

341 lines
9.1 KiB
TypeScript

/*
* File: alertsSlice.ts
* Description: Alerts state management slice
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { AlertType, AlertsState } from '../../../shared/types';
// ============================================================================
// ASYNC THUNKS
// ============================================================================
/**
* Fetch Alerts Async Thunk
*
* Purpose: Fetch alerts from API
*
* @returns Promise with alerts data or error
*/
export const fetchAlerts = createAsyncThunk(
'alerts/fetchAlerts',
async (_, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise(resolve => setTimeout(resolve, 1000));
// Mock alerts data
const mockAlerts: AlertType[] = [
{
id: '1',
type: 'CRITICAL_FINDING',
priority: 'CRITICAL',
title: 'Critical Finding Detected',
message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.',
patientId: '1',
patientName: 'John Doe',
bedNumber: 'A1',
timestamp: new Date(),
isRead: false,
isAcknowledged: false,
actionRequired: true,
},
{
id: '2',
type: 'VITAL_SIGNS_ALERT',
priority: 'HIGH',
title: 'Vital Signs Alert',
message: 'Patient vitals showing concerning trends. Blood pressure elevated.',
patientId: '2',
patientName: 'Jane Smith',
bedNumber: 'B2',
timestamp: new Date(Date.now() - 300000), // 5 minutes ago
isRead: true,
isAcknowledged: true,
actionRequired: false,
},
];
return mockAlerts;
} catch (error) {
return rejectWithValue('Failed to fetch alerts.');
}
}
);
/**
* Acknowledge Alert Async Thunk
*
* Purpose: Acknowledge an alert
*
* @param alertId - ID of the alert to acknowledge
* @returns Promise with success or error
*/
export const acknowledgeAlert = createAsyncThunk(
'alerts/acknowledgeAlert',
async (alertId: string, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise(resolve => setTimeout(resolve, 500));
return alertId;
} catch (error) {
return rejectWithValue('Failed to acknowledge alert.');
}
}
);
/**
* Mark Alert as Read Async Thunk
*
* Purpose: Mark an alert as read
*
* @param alertId - ID of the alert to mark as read
* @returns Promise with success or error
*/
export const markAlertAsRead = createAsyncThunk(
'alerts/markAlertAsRead',
async (alertId: string, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise(resolve => setTimeout(resolve, 300));
return alertId;
} catch (error) {
return rejectWithValue('Failed to mark alert as read.');
}
}
);
// ============================================================================
// INITIAL STATE
// ============================================================================
/**
* Initial Alerts State
*
* Purpose: Define the initial state for alerts
*
* Features:
* - Alerts list and management
* - Loading states for async operations
* - Error handling and messages
* - Real-time updates tracking
*/
const initialState: AlertsState = {
// Alerts data
alerts: [],
// Loading states
isLoading: false,
isRefreshing: false,
// Error handling
error: null,
// Real-time updates
lastUpdated: null,
unreadCount: 0,
criticalCount: 0,
// Filters and preferences
selectedFilter: 'all',
sortBy: 'timestamp',
sortOrder: 'desc',
};
// ============================================================================
// ALERTS SLICE
// ============================================================================
/**
* Alerts Slice
*
* Purpose: Redux slice for alerts state management
*
* Features:
* - Alerts data management
* - Real-time updates
* - Filtering and sorting
* - Error handling
* - Loading states
*/
const alertsSlice = createSlice({
name: 'alerts',
initialState,
reducers: {
/**
* Clear Error Action
*
* Purpose: Clear alerts errors
*/
clearError: (state) => {
state.error = null;
},
/**
* Set Filter Action
*
* Purpose: Set alerts filter
*/
setFilter: (state, action: PayloadAction<'all' | 'critical' | 'unread' | 'acknowledged'>) => {
state.selectedFilter = action.payload;
},
/**
* Set Sort Action
*
* Purpose: Set alerts sort options
*/
setSort: (state, action: PayloadAction<{ by: string; order: 'asc' | 'desc' }>) => {
state.sortBy = action.payload.by;
state.sortOrder = action.payload.order;
},
/**
* Add Alert Action
*
* Purpose: Add a new alert
*/
addAlert: (state, action: PayloadAction<AlertType>) => {
state.alerts.unshift(action.payload);
state.unreadCount += 1;
if (action.payload.priority === 'CRITICAL') {
state.criticalCount += 1;
}
state.lastUpdated = new Date();
},
/**
* Remove Alert Action
*
* Purpose: Remove an alert
*/
removeAlert: (state, action: PayloadAction<string>) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload);
if (alertIndex !== -1) {
const alert = state.alerts[alertIndex];
if (!alert.isRead) {
state.unreadCount -= 1;
}
if (alert.priority === 'CRITICAL') {
state.criticalCount -= 1;
}
state.alerts.splice(alertIndex, 1);
}
},
/**
* Update Alert Action
*
* Purpose: Update an existing alert
*/
updateAlert: (state, action: PayloadAction<{ id: string; updates: Partial<AlertType> }>) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload.id);
if (alertIndex !== -1) {
const oldAlert = state.alerts[alertIndex];
state.alerts[alertIndex] = { ...oldAlert, ...action.payload.updates };
// Update counters
if (action.payload.updates.isRead !== undefined) {
if (action.payload.updates.isRead && !oldAlert.isRead) {
state.unreadCount -= 1;
} else if (!action.payload.updates.isRead && oldAlert.isRead) {
state.unreadCount += 1;
}
}
}
},
/**
* Clear All Alerts Action
*
* Purpose: Clear all alerts
*/
clearAllAlerts: (state) => {
state.alerts = [];
state.unreadCount = 0;
state.criticalCount = 0;
},
/**
* Mark All as Read Action
*
* Purpose: Mark all alerts as read
*/
markAllAsRead: (state) => {
state.alerts.forEach(alert => {
alert.isRead = true;
});
state.unreadCount = 0;
},
},
extraReducers: (builder) => {
// Fetch Alerts
builder
.addCase(fetchAlerts.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchAlerts.fulfilled, (state, action) => {
state.isLoading = false;
state.alerts = action.payload;
state.unreadCount = action.payload.filter(alert => !alert.isRead).length;
state.criticalCount = action.payload.filter(alert => alert.priority === 'CRITICAL').length;
state.lastUpdated = new Date();
state.error = null;
})
.addCase(fetchAlerts.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
// Acknowledge Alert
builder
.addCase(acknowledgeAlert.fulfilled, (state, action) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload);
if (alertIndex !== -1) {
state.alerts[alertIndex].isAcknowledged = true;
}
})
.addCase(acknowledgeAlert.rejected, (state, action) => {
state.error = action.payload as string;
});
// Mark Alert as Read
builder
.addCase(markAlertAsRead.fulfilled, (state, action) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload);
if (alertIndex !== -1 && !state.alerts[alertIndex].isRead) {
state.alerts[alertIndex].isRead = true;
state.unreadCount -= 1;
}
})
.addCase(markAlertAsRead.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
// ============================================================================
// EXPORTS
// ============================================================================
export const {
clearError,
setFilter,
setSort,
addAlert,
removeAlert,
updateAlert,
clearAllAlerts,
markAllAsRead,
} = alertsSlice.actions;
export default alertsSlice.reducer;
/*
* End of File: alertsSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/