dashboard modifiedd
This commit is contained in:
parent
467dc0b8cf
commit
266de3b512
337
app/modules/Dashboard/components/FeedbackAnalysisPieChart.tsx
Normal file
337
app/modules/Dashboard/components/FeedbackAnalysisPieChart.tsx
Normal file
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* File: FeedbackAnalysisPieChart.tsx
|
||||
* Description: Pie chart component for feedback analysis using react-native-chart-kit
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
||||
import { PieChart } from 'react-native-chart-kit';
|
||||
import { theme } from '../../../theme/theme';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Feedback Analysis Data Interface
|
||||
*
|
||||
* Purpose: Defines the structure of feedback analysis data for pie chart
|
||||
*/
|
||||
interface FeedbackAnalysisData {
|
||||
positive: number;
|
||||
negative: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* FeedbackAnalysisPieChart Props Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the FeedbackAnalysisPieChart component
|
||||
*
|
||||
* Props:
|
||||
* - data: Feedback analysis data containing positive, negative, and total counts
|
||||
* - title: Optional title for the chart
|
||||
* - width: Chart width (defaults to screen width - 32)
|
||||
* - height: Chart height (defaults to 220)
|
||||
*/
|
||||
interface FeedbackAnalysisPieChartProps {
|
||||
data: FeedbackAnalysisData;
|
||||
title?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* FeedbackAnalysisPieChart Component
|
||||
*
|
||||
* Purpose: Renders a pie chart showing feedback analysis distribution
|
||||
*
|
||||
* Features:
|
||||
* - Pie chart visualization of positive vs negative feedback
|
||||
* - Custom colors for different feedback types
|
||||
* - Responsive sizing
|
||||
* - Legend with percentages
|
||||
* - Empty state handling
|
||||
*/
|
||||
export const FeedbackAnalysisPieChart: React.FC<FeedbackAnalysisPieChartProps> = ({
|
||||
data,
|
||||
title = 'Feedback Analysis Overview',
|
||||
width = Dimensions.get('window').width - 32,
|
||||
height = 220,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// DATA PROCESSING
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Process data for pie chart
|
||||
*
|
||||
* Purpose: Convert feedback data into chart-kit format
|
||||
*/
|
||||
const chartData = React.useMemo(() => {
|
||||
const { positive, negative } = data;
|
||||
|
||||
// Only show data if there are actual feedbacks
|
||||
if (positive === 0 && negative === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const chartDataArray = [];
|
||||
|
||||
// Add positive feedback data
|
||||
if (positive > 0) {
|
||||
chartDataArray.push({
|
||||
name: 'Positive',
|
||||
population: positive,
|
||||
color: theme.colors.success,
|
||||
legendFontColor: theme.colors.textPrimary,
|
||||
legendFontSize: 12,
|
||||
});
|
||||
}
|
||||
|
||||
// Add negative feedback data
|
||||
if (negative > 0) {
|
||||
chartDataArray.push({
|
||||
name: 'Negative',
|
||||
population: negative,
|
||||
color: theme.colors.error,
|
||||
legendFontColor: theme.colors.textPrimary,
|
||||
legendFontSize: 12,
|
||||
});
|
||||
}
|
||||
|
||||
return chartDataArray;
|
||||
}, [data]);
|
||||
|
||||
// ============================================================================
|
||||
// CHART CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Chart configuration object
|
||||
*
|
||||
* Purpose: Configure pie chart appearance and behavior
|
||||
*/
|
||||
const chartConfig = {
|
||||
backgroundColor: theme.colors.background,
|
||||
backgroundGradientFrom: theme.colors.background,
|
||||
backgroundGradientTo: theme.colors.background,
|
||||
decimalPlaces: 0,
|
||||
color: (opacity = 1) => theme.colors.primary,
|
||||
labelColor: (opacity = 1) => theme.colors.textPrimary,
|
||||
style: {
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
},
|
||||
propsForDots: {
|
||||
r: '6',
|
||||
strokeWidth: '2',
|
||||
stroke: theme.colors.primary,
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Render empty state
|
||||
*
|
||||
* Purpose: Show message when no feedback data is available
|
||||
*/
|
||||
const renderEmptyState = () => (
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={styles.emptyStateText}>No feedback data available</Text>
|
||||
<Text style={styles.emptyStateSubtext}>
|
||||
Feedback will appear here once received
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* Render chart legend
|
||||
*
|
||||
* Purpose: Display custom legend with percentages
|
||||
*/
|
||||
const renderLegend = () => {
|
||||
const { positive, negative, total } = data;
|
||||
|
||||
if (total === 0) return null;
|
||||
|
||||
const positivePercentage = ((positive / total) * 100).toFixed(1);
|
||||
const negativePercentage = ((negative / total) * 100).toFixed(1);
|
||||
|
||||
return (
|
||||
<View style={styles.legendContainer}>
|
||||
<View style={styles.legendItem}>
|
||||
<View style={[styles.legendColor, { backgroundColor: theme.colors.success }]} />
|
||||
<Text style={styles.legendText}>
|
||||
Positive: {positive} ({positivePercentage}%)
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.legendItem}>
|
||||
<View style={[styles.legendColor, { backgroundColor: theme.colors.error }]} />
|
||||
<Text style={styles.legendText}>
|
||||
Negative: {negative} ({negativePercentage}%)
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.totalContainer}>
|
||||
<Text style={styles.totalText}>
|
||||
Total Feedback: {total}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MAIN RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Chart Title */}
|
||||
{/* {title && (
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
)} */}
|
||||
|
||||
{/* Chart Container */}
|
||||
<View style={styles.chartContainer}>
|
||||
{chartData.length > 0 ? (
|
||||
<>
|
||||
{/* Pie Chart */}
|
||||
<PieChart
|
||||
data={chartData}
|
||||
width={width}
|
||||
height={height}
|
||||
chartConfig={chartConfig}
|
||||
accessor="population"
|
||||
backgroundColor="transparent"
|
||||
paddingLeft="0"
|
||||
center={[width/4, 0]}
|
||||
absolute
|
||||
hasLegend={false}
|
||||
/>
|
||||
|
||||
{/* Custom Legend */}
|
||||
{renderLegend()}
|
||||
</>
|
||||
) : (
|
||||
renderEmptyState()
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: 250,
|
||||
},
|
||||
|
||||
// Chart title styling
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.md,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// Chart container
|
||||
chartContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Empty state styling
|
||||
emptyState: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: theme.spacing.xl,
|
||||
minHeight: 150,
|
||||
},
|
||||
|
||||
// Empty state text styling
|
||||
emptyStateText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Empty state subtext styling
|
||||
emptyStateSubtext: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// Legend container styling
|
||||
legendContainer: {
|
||||
marginTop: theme.spacing.md,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Legend item styling
|
||||
legendItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Legend color indicator styling
|
||||
legendColor: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 8,
|
||||
marginRight: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Legend text styling
|
||||
legendText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Total container styling
|
||||
totalContainer: {
|
||||
marginTop: theme.spacing.sm,
|
||||
paddingTop: theme.spacing.sm,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Total text styling
|
||||
totalText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: FeedbackAnalysisPieChart.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
@ -3,4 +3,5 @@ export { CriticalAlerts } from './CriticalAlerts';
|
||||
export { DashboardHeader } from './DashboardHeader';
|
||||
export { QuickActions } from './QuickActions';
|
||||
export { DepartmentStats } from './DepartmentStats';
|
||||
export { BrainPredictionsOverview } from './BrainPredictionsOverview';
|
||||
export { BrainPredictionsOverview } from './BrainPredictionsOverview';
|
||||
export { FeedbackAnalysisPieChart } from './FeedbackAnalysisPieChart';
|
||||
14
app/modules/Dashboard/hooks/index.ts
Normal file
14
app/modules/Dashboard/hooks/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Dashboard hooks exports
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export * from './useAIDashboard';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
101
app/modules/Dashboard/hooks/useAIDashboard.ts
Normal file
101
app/modules/Dashboard/hooks/useAIDashboard.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* File: useAIDashboard.ts
|
||||
* Description: Custom hook for AI dashboard functionality
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppDispatch } from '../../../store';
|
||||
import { selectAIDashboardData, selectAIDashboardError, selectAIDashboardLoading, selectAIDashboardRefreshing, selectDashboardMessage } from '../redux/aiDashboardSelectors';
|
||||
import { fetchAIDashboardStatistics, refreshAIDashboardStatistics } from '../redux/aiDashboardSlice';
|
||||
import { selectUser } from '../../Auth/redux';
|
||||
|
||||
// import {
|
||||
// fetchAIDashboardStatistics,
|
||||
// refreshAIDashboardStatistics,
|
||||
// selectAIDashboardData,
|
||||
// selectAIDashboardLoading,
|
||||
// selectAIDashboardRefreshing,
|
||||
// selectAIDashboardError,
|
||||
// selectDashboardMessage
|
||||
// } from '../redux';
|
||||
|
||||
/**
|
||||
* useAIDashboard Custom Hook
|
||||
*
|
||||
* Purpose: Custom hook for AI dashboard functionality
|
||||
*
|
||||
* Features:
|
||||
* - Fetch dashboard statistics from API
|
||||
* - Refresh dashboard data
|
||||
* - Access dashboard state from Redux
|
||||
* - Handle authentication token
|
||||
*
|
||||
* @returns Object containing dashboard state and actions
|
||||
*/
|
||||
export const useAIDashboard = () => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
// Select dashboard data from Redux store
|
||||
const dashboardData = useSelector(selectAIDashboardData);
|
||||
const isLoading = useSelector(selectAIDashboardLoading);
|
||||
const isRefreshing = useSelector(selectAIDashboardRefreshing);
|
||||
const error = useSelector(selectAIDashboardError);
|
||||
const dashboardMessage = useSelector(selectDashboardMessage);
|
||||
|
||||
// TODO: Get actual authentication token from auth store
|
||||
// For now, using a placeholder token
|
||||
const authToken = useSelector(selectUser)?.access_token;
|
||||
|
||||
/**
|
||||
* Fetch Dashboard Statistics
|
||||
*
|
||||
* Purpose: Fetch dashboard statistics from API
|
||||
*/
|
||||
const fetchDashboardStatistics = () => {
|
||||
dispatch(fetchAIDashboardStatistics(authToken));
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh Dashboard Statistics
|
||||
*
|
||||
* Purpose: Refresh dashboard statistics from API
|
||||
*/
|
||||
const refreshDashboardStatistics = () => {
|
||||
dispatch(refreshAIDashboardStatistics(authToken));
|
||||
};
|
||||
|
||||
/**
|
||||
* useEffect for initial data loading
|
||||
*
|
||||
* Purpose: Load initial dashboard data from API when hook is used
|
||||
*/
|
||||
useEffect(() => {
|
||||
// Fetch dashboard statistics from API
|
||||
fetchDashboardStatistics();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// State
|
||||
dashboardData,
|
||||
isLoading,
|
||||
isRefreshing,
|
||||
error,
|
||||
dashboardMessage,
|
||||
|
||||
// Actions
|
||||
fetchDashboardStatistics,
|
||||
refreshDashboardStatistics,
|
||||
|
||||
// Constants
|
||||
authToken
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: useAIDashboard.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
@ -39,6 +39,9 @@ export { default as DashboardHeader } from './components/DashboardHeader';
|
||||
export { default as QuickActions } from './components/QuickActions';
|
||||
export { default as DepartmentStats } from './components/DepartmentStats';
|
||||
|
||||
// Export hooks
|
||||
export * from './hooks';
|
||||
|
||||
// Export Redux
|
||||
export {
|
||||
fetchDashboardData,
|
||||
@ -51,6 +54,36 @@ export {
|
||||
updateDashboardData,
|
||||
} from './redux/dashboardSlice';
|
||||
|
||||
// Export AI Dashboard Redux
|
||||
export {
|
||||
fetchAIDashboardStatistics,
|
||||
refreshAIDashboardStatistics,
|
||||
clearError as clearAIDashboardError,
|
||||
setTimeRange,
|
||||
setHospital,
|
||||
setDepartment,
|
||||
updateDashboardData as updateAIDashboardData,
|
||||
} from './redux/aiDashboardSlice';
|
||||
|
||||
// Export AI Dashboard Selectors
|
||||
export {
|
||||
selectAIDashboardData,
|
||||
selectAIDashboardLoading,
|
||||
selectAIDashboardRefreshing,
|
||||
selectAIDashboardError,
|
||||
selectDashboardMessage,
|
||||
selectTotalPredictions,
|
||||
selectTotalPatients,
|
||||
selectTotalFeedbacks,
|
||||
selectFeedbackRatePercentage,
|
||||
selectAverageConfidenceScore,
|
||||
selectCriticalCasePercentage,
|
||||
selectConfidenceScores,
|
||||
selectUrgencyLevels,
|
||||
selectFeedbackAnalysis,
|
||||
selectTimeAnalysis,
|
||||
} from './redux/aiDashboardSelectors';
|
||||
|
||||
export {
|
||||
fetchAlerts,
|
||||
acknowledgeAlert,
|
||||
|
||||
426
app/modules/Dashboard/redux/aiDashboardSelectors.ts
Normal file
426
app/modules/Dashboard/redux/aiDashboardSelectors.ts
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
* File: aiDashboardSelectors.ts
|
||||
* Description: Selectors for AI dashboard state management
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from '../../../store';
|
||||
|
||||
// ============================================================================
|
||||
// BASE SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select AI Dashboard State
|
||||
*
|
||||
* Purpose: Get the entire AI dashboard state from root state
|
||||
*/
|
||||
const selectAIDashboardState = (state: RootState) => state.aiDashboard;
|
||||
|
||||
/**
|
||||
* Select AI Dashboard Data
|
||||
*
|
||||
* Purpose: Get the AI dashboard data from state
|
||||
*/
|
||||
export const selectAIDashboardData = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.dashboardData
|
||||
);
|
||||
|
||||
/**
|
||||
* Select AI Dashboard Statistics
|
||||
*
|
||||
* Purpose: Get the AI dashboard statistics data
|
||||
*/
|
||||
export const selectAIDashboardStats = createSelector(
|
||||
[selectAIDashboardData],
|
||||
(dashboardData) => dashboardData?.data
|
||||
);
|
||||
|
||||
/**
|
||||
* Select AI Dashboard Summary
|
||||
*
|
||||
* Purpose: Get the AI dashboard summary data
|
||||
*/
|
||||
export const selectAIDashboardSummary = createSelector(
|
||||
[selectAIDashboardData],
|
||||
(dashboardData) => dashboardData?.summary
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// LOADING STATE SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select AI Dashboard Loading State
|
||||
*
|
||||
* Purpose: Get the loading state for AI dashboard
|
||||
*/
|
||||
export const selectAIDashboardLoading = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.isLoading
|
||||
);
|
||||
|
||||
/**
|
||||
* Select AI Dashboard Refreshing State
|
||||
*
|
||||
* Purpose: Get the refreshing state for AI dashboard
|
||||
*/
|
||||
export const selectAIDashboardRefreshing = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.isRefreshing
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// ERROR STATE SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select AI Dashboard Error
|
||||
*
|
||||
* Purpose: Get the error state for AI dashboard
|
||||
*/
|
||||
export const selectAIDashboardError = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.error
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// FILTER STATE SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Selected Time Range
|
||||
*
|
||||
* Purpose: Get the currently selected time range filter
|
||||
*/
|
||||
export const selectSelectedTimeRange = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.selectedTimeRange
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Selected Hospital
|
||||
*
|
||||
* Purpose: Get the currently selected hospital filter
|
||||
*/
|
||||
export const selectSelectedHospital = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.selectedHospital
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Selected Department
|
||||
*
|
||||
* Purpose: Get the currently selected department filter
|
||||
*/
|
||||
export const selectSelectedDepartment = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.selectedDepartment
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// DERIVED DATA SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Total Predictions Count
|
||||
*
|
||||
* Purpose: Get the total number of AI predictions
|
||||
*/
|
||||
export const selectTotalPredictions = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.total_predictions || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Total Patients Count
|
||||
*
|
||||
* Purpose: Get the total number of unique patients
|
||||
*/
|
||||
export const selectTotalPatients = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.total_patients || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Total Feedbacks Count
|
||||
*
|
||||
* Purpose: Get the total number of feedbacks received
|
||||
*/
|
||||
export const selectTotalFeedbacks = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.total_feedbacks || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Feedback Rate Percentage
|
||||
*
|
||||
* Purpose: Get the feedback rate as a percentage
|
||||
*/
|
||||
export const selectFeedbackRatePercentage = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.feedback_rate_percentage || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Average Confidence Score
|
||||
*
|
||||
* Purpose: Get the average confidence score for AI predictions
|
||||
*/
|
||||
export const selectAverageConfidenceScore = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.average_confidence_score || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Critical Case Percentage
|
||||
*
|
||||
* Purpose: Get the percentage of critical cases
|
||||
*/
|
||||
export const selectCriticalCasePercentage = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.critical_case_percentage || 0
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// CONFIDENCE SCORE SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Confidence Score Distribution
|
||||
*
|
||||
* Purpose: Get the distribution of confidence scores
|
||||
*/
|
||||
export const selectConfidenceScores = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.confidence_scores || { high: 0, medium: 0, low: 0 }
|
||||
);
|
||||
|
||||
/**
|
||||
* Select High Confidence Count
|
||||
*
|
||||
* Purpose: Get the count of high confidence predictions
|
||||
*/
|
||||
export const selectHighConfidenceCount = createSelector(
|
||||
[selectConfidenceScores],
|
||||
(confidenceScores) => confidenceScores.high
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Medium Confidence Count
|
||||
*
|
||||
* Purpose: Get the count of medium confidence predictions
|
||||
*/
|
||||
export const selectMediumConfidenceCount = createSelector(
|
||||
[selectConfidenceScores],
|
||||
(confidenceScores) => confidenceScores.medium
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Low Confidence Count
|
||||
*
|
||||
* Purpose: Get the count of low confidence predictions
|
||||
*/
|
||||
export const selectLowConfidenceCount = createSelector(
|
||||
[selectConfidenceScores],
|
||||
(confidenceScores) => confidenceScores.low
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// URGENCY LEVEL SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Urgency Level Distribution
|
||||
*
|
||||
* Purpose: Get the distribution of urgency levels
|
||||
*/
|
||||
export const selectUrgencyLevels = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.urgency_levels || { critical: 0, urgent: 0, routine: 0 }
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Critical Urgency Count
|
||||
*
|
||||
* Purpose: Get the count of critical urgency cases
|
||||
*/
|
||||
export const selectCriticalUrgencyCount = createSelector(
|
||||
[selectUrgencyLevels],
|
||||
(urgencyLevels) => urgencyLevels.critical
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Urgent Urgency Count
|
||||
*
|
||||
* Purpose: Get the count of urgent cases
|
||||
*/
|
||||
export const selectUrgentUrgencyCount = createSelector(
|
||||
[selectUrgencyLevels],
|
||||
(urgencyLevels) => urgencyLevels.urgent
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Routine Urgency Count
|
||||
*
|
||||
* Purpose: Get the count of routine cases
|
||||
*/
|
||||
export const selectRoutineUrgencyCount = createSelector(
|
||||
[selectUrgencyLevels],
|
||||
(urgencyLevels) => urgencyLevels.routine
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// FEEDBACK ANALYSIS SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Feedback Analysis Data
|
||||
*
|
||||
* Purpose: Get the feedback analysis data
|
||||
*/
|
||||
export const selectFeedbackAnalysis = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.feedback_analysis || { positive: 0, negative: 0, total: 0 }
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Positive Feedback Count
|
||||
*
|
||||
* Purpose: Get the count of positive feedbacks
|
||||
*/
|
||||
export const selectPositiveFeedbackCount = createSelector(
|
||||
[selectFeedbackAnalysis],
|
||||
(feedbackAnalysis) => feedbackAnalysis.positive
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Negative Feedback Count
|
||||
*
|
||||
* Purpose: Get the count of negative feedbacks
|
||||
*/
|
||||
export const selectNegativeFeedbackCount = createSelector(
|
||||
[selectFeedbackAnalysis],
|
||||
(feedbackAnalysis) => feedbackAnalysis.negative
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Total Feedback Count
|
||||
*
|
||||
* Purpose: Get the total count of feedbacks
|
||||
*/
|
||||
export const selectTotalFeedbackCount = createSelector(
|
||||
[selectFeedbackAnalysis],
|
||||
(feedbackAnalysis) => feedbackAnalysis.total
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// TIME ANALYSIS SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Time Analysis Data
|
||||
*
|
||||
* Purpose: Get the time-based analysis data
|
||||
*/
|
||||
export const selectTimeAnalysis = createSelector(
|
||||
[selectAIDashboardStats],
|
||||
(stats) => stats?.time_analysis || { today: 0, this_week: 0, this_month: 0, this_year: 0 }
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Today's Count
|
||||
*
|
||||
* Purpose: Get the count for today
|
||||
*/
|
||||
export const selectTodayCount = createSelector(
|
||||
[selectTimeAnalysis],
|
||||
(timeAnalysis) => timeAnalysis.today
|
||||
);
|
||||
|
||||
/**
|
||||
* Select This Week's Count
|
||||
*
|
||||
* Purpose: Get the count for this week
|
||||
*/
|
||||
export const selectThisWeekCount = createSelector(
|
||||
[selectTimeAnalysis],
|
||||
(timeAnalysis) => timeAnalysis.this_week
|
||||
);
|
||||
|
||||
/**
|
||||
* Select This Month's Count
|
||||
*
|
||||
* Purpose: Get the count for this month
|
||||
*/
|
||||
export const selectThisMonthCount = createSelector(
|
||||
[selectTimeAnalysis],
|
||||
(timeAnalysis) => timeAnalysis.this_month
|
||||
);
|
||||
|
||||
/**
|
||||
* Select This Year's Count
|
||||
*
|
||||
* Purpose: Get the count for this year
|
||||
*/
|
||||
export const selectThisYearCount = createSelector(
|
||||
[selectTimeAnalysis],
|
||||
(timeAnalysis) => timeAnalysis.this_year
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// LAST UPDATED SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Last Updated Timestamp
|
||||
*
|
||||
* Purpose: Get the last updated timestamp
|
||||
*/
|
||||
export const selectLastUpdated = createSelector(
|
||||
[selectAIDashboardState],
|
||||
(aiDashboard) => aiDashboard.lastUpdated
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// COMPUTED SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Dashboard Message
|
||||
*
|
||||
* Purpose: Get the dashboard message or default message
|
||||
*/
|
||||
export const selectDashboardMessage = createSelector(
|
||||
[selectAIDashboardData],
|
||||
(dashboardData) => dashboardData?.message || 'Loading statistics...'
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Is Dashboard Empty
|
||||
*
|
||||
* Purpose: Check if dashboard has no data
|
||||
*/
|
||||
export const selectIsDashboardEmpty = createSelector(
|
||||
[selectAIDashboardData],
|
||||
(dashboardData) => !dashboardData || !dashboardData.data
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Has Dashboard Data
|
||||
*
|
||||
* Purpose: Check if dashboard has data
|
||||
*/
|
||||
export const selectHasDashboardData = createSelector(
|
||||
[selectIsDashboardEmpty],
|
||||
(isEmpty) => !isEmpty
|
||||
);
|
||||
|
||||
/*
|
||||
* End of File: aiDashboardSelectors.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
368
app/modules/Dashboard/redux/aiDashboardSlice.ts
Normal file
368
app/modules/Dashboard/redux/aiDashboardSlice.ts
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
* File: aiDashboardSlice.ts
|
||||
* Description: AI Analysis Dashboard state management slice
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { dashboardAPI } from '../services/dashboardAPI';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* AI Dashboard Statistics Interface
|
||||
*
|
||||
* Purpose: Defines the structure of AI dashboard statistics data
|
||||
*/
|
||||
export interface AIDashboardStats {
|
||||
total_predictions: number;
|
||||
total_patients: number;
|
||||
total_feedbacks: number;
|
||||
prediction_breakdown: Record<string, number>;
|
||||
critical_findings: Record<string, number>;
|
||||
midline_shift_stats: Record<string, number>;
|
||||
hemorrhage_stats: Record<string, number>;
|
||||
mass_lesion_stats: Record<string, number>;
|
||||
edema_stats: Record<string, number>;
|
||||
fracture_stats: Record<string, number>;
|
||||
feedback_analysis: {
|
||||
positive: number;
|
||||
negative: number;
|
||||
total: number;
|
||||
};
|
||||
hospital_distribution: Record<string, number>;
|
||||
time_analysis: {
|
||||
today: number;
|
||||
this_week: number;
|
||||
this_month: number;
|
||||
this_year: number;
|
||||
};
|
||||
urgency_levels: {
|
||||
critical: number;
|
||||
urgent: number;
|
||||
routine: number;
|
||||
};
|
||||
confidence_scores: {
|
||||
high: number;
|
||||
medium: number;
|
||||
low: number;
|
||||
};
|
||||
feedback_rate_percentage: number;
|
||||
predictions_with_feedback: number;
|
||||
predictions_without_feedback: number;
|
||||
average_feedback_per_prediction: string;
|
||||
critical_case_percentage: number;
|
||||
average_confidence_score: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI Dashboard Summary Interface
|
||||
*
|
||||
* Purpose: Defines the structure of the AI dashboard summary data
|
||||
*/
|
||||
export interface AIDashboardSummary {
|
||||
total_cases: number;
|
||||
critical_cases: number;
|
||||
routine_cases: number;
|
||||
feedback_coverage: string;
|
||||
critical_case_rate: string;
|
||||
average_confidence: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete AI Dashboard Data Interface
|
||||
*
|
||||
* Purpose: Defines the complete structure of the AI dashboard API response
|
||||
*/
|
||||
export interface AIDashboardData {
|
||||
success: boolean;
|
||||
data: AIDashboardStats;
|
||||
summary: AIDashboardSummary;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI Dashboard State Interface
|
||||
*
|
||||
* Purpose: Defines the state structure for AI dashboard
|
||||
*/
|
||||
export interface AIDashboardState {
|
||||
// Dashboard data
|
||||
dashboardData: AIDashboardData | null;
|
||||
|
||||
// Loading states
|
||||
isLoading: boolean;
|
||||
isRefreshing: boolean;
|
||||
|
||||
// Error handling
|
||||
error: string | null;
|
||||
|
||||
// Filters and preferences
|
||||
selectedTimeRange: 'today' | 'week' | 'month' | 'year';
|
||||
selectedHospital: string | null;
|
||||
selectedDepartment: string | null;
|
||||
|
||||
// Last updated timestamp
|
||||
lastUpdated: string | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ASYNC THUNKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch AI Dashboard Statistics Async Thunk
|
||||
*
|
||||
* Purpose: Fetch AI analysis dashboard statistics from API
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @returns Promise with AI dashboard statistics data or error
|
||||
*/
|
||||
export const fetchAIDashboardStatistics = createAsyncThunk(
|
||||
'aiDashboard/fetchStatistics',
|
||||
async (token: string, { rejectWithValue }) => {
|
||||
try {
|
||||
const response :any = await dashboardAPI.getDashboardStatistics(token);
|
||||
console.log('statistics response',response);
|
||||
|
||||
if (response.ok && response.data ) {
|
||||
return response.data as AIDashboardData;
|
||||
} else {
|
||||
return rejectWithValue(response.problem || 'Failed to fetch dashboard statistics');
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue('Network error occurred while fetching dashboard statistics');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Refresh AI Dashboard Statistics Async Thunk
|
||||
*
|
||||
* Purpose: Refresh AI dashboard statistics data
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @returns Promise with refreshed AI dashboard statistics or error
|
||||
*/
|
||||
export const refreshAIDashboardStatistics = createAsyncThunk(
|
||||
'aiDashboard/refreshStatistics',
|
||||
async (token: string, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await dashboardAPI.getDashboardStatistics(token);
|
||||
|
||||
if (response.ok && response.data) {
|
||||
return response.data as AIDashboardData;
|
||||
} else {
|
||||
return rejectWithValue(response.problem || 'Failed to refresh dashboard statistics');
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue('Network error occurred while refreshing dashboard statistics');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Fetch Time-based Analysis Async Thunk
|
||||
*
|
||||
* Purpose: Fetch time-based analysis data for specific time range
|
||||
*
|
||||
* @param params - Parameters including token and time range
|
||||
* @returns Promise with time-based analysis data or error
|
||||
*/
|
||||
export const fetchTimeBasedAnalysis = createAsyncThunk(
|
||||
'aiDashboard/fetchTimeAnalysis',
|
||||
async (params: { token: string; timeRange: 'today' | 'week' | 'month' | 'year' }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await dashboardAPI.getTimeBasedAnalysis(params.token, params.timeRange);
|
||||
|
||||
if (response.ok && response.data) {
|
||||
return response.data as AIDashboardData;
|
||||
} else {
|
||||
return rejectWithValue(response.problem || 'Failed to fetch time-based analysis');
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue('Network error occurred while fetching time-based analysis');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// INITIAL STATE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initial AI Dashboard State
|
||||
*
|
||||
* Purpose: Define the initial state for AI dashboard
|
||||
*
|
||||
* Features:
|
||||
* - AI dashboard statistics data
|
||||
* - Loading states for async operations
|
||||
* - Error handling and messages
|
||||
* - Filter preferences
|
||||
* - Last updated tracking
|
||||
*/
|
||||
const initialState: AIDashboardState = {
|
||||
// Dashboard data
|
||||
dashboardData: null,
|
||||
|
||||
// Loading states
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
|
||||
// Error handling
|
||||
error: null,
|
||||
|
||||
// Filters and preferences
|
||||
selectedTimeRange: 'today',
|
||||
selectedHospital: null,
|
||||
selectedDepartment: null,
|
||||
|
||||
// Last updated timestamp
|
||||
lastUpdated: null,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// AI DASHBOARD SLICE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* AI Dashboard Slice
|
||||
*
|
||||
* Purpose: Redux slice for AI dashboard state management
|
||||
*
|
||||
* Features:
|
||||
* - AI dashboard statistics management
|
||||
* - Time-based filtering
|
||||
* - Hospital and department filtering
|
||||
* - Error handling
|
||||
* - Loading states
|
||||
*/
|
||||
const aiDashboardSlice = createSlice({
|
||||
name: 'aiDashboard',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* Clear Error Action
|
||||
*
|
||||
* Purpose: Clear AI dashboard errors
|
||||
*/
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Time Range Filter Action
|
||||
*
|
||||
* Purpose: Set time range filter for statistics
|
||||
*/
|
||||
setTimeRange: (state, action: PayloadAction<'today' | 'week' | 'month' | 'year'>) => {
|
||||
state.selectedTimeRange = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Hospital Filter Action
|
||||
*
|
||||
* Purpose: Set hospital filter for statistics
|
||||
*/
|
||||
setHospital: (state, action: PayloadAction<string | null>) => {
|
||||
state.selectedHospital = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Department Filter Action
|
||||
*
|
||||
* Purpose: Set department filter for statistics
|
||||
*/
|
||||
setDepartment: (state, action: PayloadAction<string | null>) => {
|
||||
state.selectedDepartment = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Dashboard Data Action
|
||||
*
|
||||
* Purpose: Update dashboard data manually
|
||||
*/
|
||||
updateDashboardData: (state, action: PayloadAction<Partial<AIDashboardData>>) => {
|
||||
if (state.dashboardData) {
|
||||
state.dashboardData = { ...state.dashboardData, ...action.payload };
|
||||
state.lastUpdated = new Date().toLocaleDateString();
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Fetch AI Dashboard Statistics
|
||||
builder
|
||||
.addCase(fetchAIDashboardStatistics.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchAIDashboardStatistics.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.dashboardData = action.payload;
|
||||
state.lastUpdated = new Date().toLocaleDateString();
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchAIDashboardStatistics.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Refresh AI Dashboard Statistics
|
||||
builder
|
||||
.addCase(refreshAIDashboardStatistics.pending, (state) => {
|
||||
state.isRefreshing = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(refreshAIDashboardStatistics.fulfilled, (state, action) => {
|
||||
state.isRefreshing = false;
|
||||
state.dashboardData = action.payload;
|
||||
state.lastUpdated = new Date().toLocaleDateString();
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(refreshAIDashboardStatistics.rejected, (state, action) => {
|
||||
state.isRefreshing = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Fetch Time-based Analysis
|
||||
builder
|
||||
.addCase(fetchTimeBasedAnalysis.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchTimeBasedAnalysis.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.dashboardData = action.payload;
|
||||
state.lastUpdated = new Date().toLocaleDateString();
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchTimeBasedAnalysis.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export const {
|
||||
clearError,
|
||||
setTimeRange,
|
||||
setHospital,
|
||||
setDepartment,
|
||||
updateDashboardData,
|
||||
} = aiDashboardSlice.actions;
|
||||
|
||||
export default aiDashboardSlice.reducer;
|
||||
|
||||
/*
|
||||
* End of File: aiDashboardSlice.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
31
app/modules/Dashboard/redux/index.ts
Normal file
31
app/modules/Dashboard/redux/index.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Dashboard Redux exports
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Dashboard Slice
|
||||
export { default as dashboardReducer } from './dashboardSlice';
|
||||
export * from './dashboardSlice';
|
||||
|
||||
// AI Dashboard Slice
|
||||
// export { default as aiDashboardReducer } from './aiDashboardSlice';
|
||||
// export * from './aiDashboardSlice';
|
||||
|
||||
// // UI Slice
|
||||
// export { default as uiReducer } from './uiSlice';
|
||||
// export * from './uiSlice';
|
||||
|
||||
// // Alerts Slice
|
||||
// export { default as alertsReducer } from './alertsSlice';
|
||||
// export * from './alertsSlice';
|
||||
|
||||
// // AI Dashboard Selectors
|
||||
// export * from './aiDashboardSelectors';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
@ -14,10 +14,13 @@ import {
|
||||
TouchableOpacity,
|
||||
RefreshControl,
|
||||
FlatList,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { DashboardHeader } from '../components/DashboardHeader';
|
||||
import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview';
|
||||
import { FeedbackAnalysisPieChart } from '../components/FeedbackAnalysisPieChart';
|
||||
import { useAIDashboard } from '../hooks/useAIDashboard';
|
||||
|
||||
/**
|
||||
* DashboardScreenProps Interface
|
||||
@ -120,108 +123,18 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// CUSTOM HOOKS
|
||||
// ============================================================================
|
||||
|
||||
// Refresh state for pull-to-refresh functionality
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// Dashboard data state
|
||||
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// ============================================================================
|
||||
// MOCK DATA GENERATION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* generateMockDashboardData Function
|
||||
*
|
||||
* Purpose: Generate mock dashboard data based on the provided JSON structure
|
||||
*
|
||||
* Returns: DashboardData object with AI analysis statistics
|
||||
*/
|
||||
const generateMockDashboardData = (): DashboardData => ({
|
||||
success: true,
|
||||
data: {
|
||||
total_predictions: 24,
|
||||
total_patients: 9,
|
||||
total_feedbacks: 6,
|
||||
prediction_breakdown: {
|
||||
"Other": 24
|
||||
},
|
||||
critical_findings: {},
|
||||
midline_shift_stats: {},
|
||||
hemorrhage_stats: {},
|
||||
mass_lesion_stats: {},
|
||||
edema_stats: {},
|
||||
fracture_stats: {},
|
||||
feedback_analysis: {
|
||||
positive: 6,
|
||||
negative: 0,
|
||||
total: 6
|
||||
},
|
||||
hospital_distribution: {
|
||||
"b491dfc2-521b-4eb1-8d88-02b0940ea1ff": 24
|
||||
},
|
||||
time_analysis: {
|
||||
today: 24,
|
||||
this_week: 24,
|
||||
this_month: 24,
|
||||
this_year: 24
|
||||
},
|
||||
urgency_levels: {
|
||||
critical: 0,
|
||||
urgent: 0,
|
||||
routine: 24
|
||||
},
|
||||
confidence_scores: {
|
||||
high: 23,
|
||||
medium: 1,
|
||||
low: 0
|
||||
},
|
||||
feedback_rate_percentage: 25,
|
||||
predictions_with_feedback: 2,
|
||||
predictions_without_feedback: 22,
|
||||
average_feedback_per_prediction: "0.25",
|
||||
critical_case_percentage: 0,
|
||||
average_confidence_score: 0.89
|
||||
},
|
||||
summary: {
|
||||
total_cases: 24,
|
||||
critical_cases: 0,
|
||||
routine_cases: 24,
|
||||
feedback_coverage: "25.00%",
|
||||
critical_case_rate: "0.00%",
|
||||
average_confidence: "0.89"
|
||||
},
|
||||
message: "Statistics generated for 24 predictions"
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useEffect for initial data loading
|
||||
*
|
||||
* Purpose: Load initial mock data when component mounts
|
||||
*/
|
||||
useEffect(() => {
|
||||
const loadInitialData = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate API call delay
|
||||
setTimeout(() => {}, 1000);
|
||||
|
||||
// Generate and set mock data
|
||||
setDashboardData(generateMockDashboardData());
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
loadInitialData();
|
||||
}, []);
|
||||
// Use custom hook for AI dashboard functionality
|
||||
const {
|
||||
dashboardData,
|
||||
isLoading,
|
||||
isRefreshing,
|
||||
error,
|
||||
dashboardMessage,
|
||||
refreshDashboardStatistics
|
||||
} = useAIDashboard();
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
@ -233,21 +146,86 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
* Purpose: Handle pull-to-refresh functionality to update dashboard data
|
||||
*/
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
|
||||
// Simulate API call with 1-second delay
|
||||
setTimeout(() => {}, 1000);
|
||||
|
||||
// Update data with fresh mock data
|
||||
setDashboardData(generateMockDashboardData());
|
||||
|
||||
setRefreshing(false);
|
||||
// Refresh dashboard statistics from API
|
||||
refreshDashboardStatistics();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* renderErrorState Function
|
||||
*
|
||||
* Purpose: Render error state when there's a critical error
|
||||
*/
|
||||
const renderErrorState = () => {
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.errorContainer}>
|
||||
<Text style={styles.errorTitle}>Connection Error</Text>
|
||||
<Text style={styles.errorMessage}>
|
||||
Unable to connect to the dashboard service
|
||||
</Text>
|
||||
<View style={styles.errorInfo}>
|
||||
<Text style={styles.errorInfoText}>• Check your internet connection</Text>
|
||||
<Text style={styles.errorInfoText}>• Verify server status</Text>
|
||||
<Text style={styles.errorInfoText}>• Try again in a few moments</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
|
||||
<Text style={styles.retryButtonText}>Retry Connection</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* renderNetworkStatus Function
|
||||
*
|
||||
* Purpose: Render network status indicator
|
||||
*/
|
||||
const renderNetworkStatus = () => {
|
||||
if (!error && dashboardData) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.networkStatusContainer}>
|
||||
<Text style={styles.networkStatusText}>
|
||||
{error ? '⚠️ Connection Issue' : '🔄 Checking Connection...'}
|
||||
</Text>
|
||||
<Text style={styles.networkStatusSubtext}>
|
||||
{error ? 'Please check your internet connection' : 'Verifying dashboard service'}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* renderNoDataState Function
|
||||
*
|
||||
* Purpose: Render no data state when dashboard loads but has no meaningful data
|
||||
*/
|
||||
const renderNoDataState = () => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.noDataContainer}>
|
||||
<Text style={styles.noDataTitle}>Dashboard is Ready</Text>
|
||||
<Text style={styles.noDataMessage}>
|
||||
Your AI Analysis Dashboard is ready, but there's no data to display yet.
|
||||
</Text>
|
||||
<View style={styles.noDataInfo}>
|
||||
<Text style={styles.noDataInfoText}>• AI predictions will appear here once scans are processed</Text>
|
||||
<Text style={styles.noDataInfoText}>• Check back after medical scans are uploaded</Text>
|
||||
<Text style={styles.noDataInfoText}>• The system will automatically populate data</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
|
||||
<Text style={styles.retryButtonText}>Check for Updates</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* renderStatsCard Function
|
||||
*
|
||||
@ -273,31 +251,171 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
* Purpose: Render confidence score breakdown section
|
||||
*/
|
||||
const renderConfidenceBreakdown = () => {
|
||||
if (!dashboardData?.data.confidence_scores) return null;
|
||||
// Check if dashboard data exists
|
||||
if (!dashboardData) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>Dashboard data not available</Text>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
|
||||
<Text style={styles.retryButtonText}>Retry</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if confidence scores data exists
|
||||
if (!dashboardData.data?.confidence_scores) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>Confidence data not available</Text>
|
||||
<Text style={styles.emptyStateSubtext}>AI confidence scores are not currently accessible</Text>
|
||||
<View style={styles.emptyStateInfo}>
|
||||
<Text style={styles.emptyStateInfoText}>• AI system may be initializing</Text>
|
||||
<Text style={styles.emptyStateInfoText}>• Check system status</Text>
|
||||
<Text style={styles.emptyStateInfoText}>• Refresh in a few minutes</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const { high, medium, low } = dashboardData.data.confidence_scores;
|
||||
|
||||
// Check if the object is empty or if all values are undefined/null/zero
|
||||
if (!high && !medium && !low) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No data found</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if all required fields exist and are numbers
|
||||
if (typeof high !== 'number' || typeof medium !== 'number' || typeof low !== 'number') {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No confidence data available</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const total = high + medium + low;
|
||||
|
||||
// If no predictions, show empty state
|
||||
if (total === 0) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No predictions available yet</Text>
|
||||
<Text style={styles.emptyStateSubtext}>AI predictions will appear here once the system processes medical scans</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate percentages for better visualization
|
||||
const highPercent = Math.round((high / total) * 100);
|
||||
const mediumPercent = Math.round((medium / total) * 100);
|
||||
const lowPercent = Math.round((low / total) * 100);
|
||||
|
||||
// Helper function to get bar opacity
|
||||
const getBarOpacity = (count: number) => {
|
||||
if (count === 0) return 0.3; // Dimmed for zero values
|
||||
return 0.9; // Full opacity for non-zero values
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||
<View style={styles.confidenceContainer}>
|
||||
{/* High Confidence */}
|
||||
<View style={styles.confidenceItem}>
|
||||
<View style={[styles.confidenceBar, { backgroundColor: theme.colors.success, height: (high / total) * 100 }]} />
|
||||
<Text style={styles.confidenceLabel}>High</Text>
|
||||
<Text style={styles.confidenceValue}>{high}</Text>
|
||||
<View style={styles.confidenceHeader}>
|
||||
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.success }]} />
|
||||
<Text style={styles.confidenceLabel}>High Confidence</Text>
|
||||
<Text style={styles.confidencePercentage}>{highPercent}%</Text>
|
||||
</View>
|
||||
<View style={styles.confidenceBarContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.confidenceBar,
|
||||
{
|
||||
backgroundColor: theme.colors.success,
|
||||
width: high === 0 ? 4 : `${Math.max(highPercent, 5)}%`,
|
||||
opacity: getBarOpacity(high)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.confidenceValue}>{high} predictions</Text>
|
||||
</View>
|
||||
|
||||
{/* Medium Confidence */}
|
||||
<View style={styles.confidenceItem}>
|
||||
<View style={[styles.confidenceBar, { backgroundColor: theme.colors.warning, height: (medium / total) * 100 }]} />
|
||||
<Text style={styles.confidenceLabel}>Medium</Text>
|
||||
<Text style={styles.confidenceValue}>{medium}</Text>
|
||||
<View style={styles.confidenceHeader}>
|
||||
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.warning }]} />
|
||||
<Text style={styles.confidenceLabel}>Medium Confidence</Text>
|
||||
<Text style={styles.confidencePercentage}>{mediumPercent}%</Text>
|
||||
</View>
|
||||
<View style={styles.confidenceBarContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.confidenceBar,
|
||||
{
|
||||
backgroundColor: theme.colors.warning,
|
||||
width: medium === 0 ? 4 : `${Math.max(mediumPercent, 5)}%`,
|
||||
opacity: getBarOpacity(medium)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.confidenceValue}>{medium} predictions</Text>
|
||||
</View>
|
||||
|
||||
{/* Low Confidence */}
|
||||
<View style={styles.confidenceItem}>
|
||||
<View style={[styles.confidenceBar, { backgroundColor: theme.colors.error, height: (low / total) * 100 }]} />
|
||||
<Text style={styles.confidenceLabel}>Low</Text>
|
||||
<Text style={styles.confidenceValue}>{low}</Text>
|
||||
<View style={styles.confidenceHeader}>
|
||||
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.error }]} />
|
||||
<Text style={styles.confidenceLabel}>Low Confidence</Text>
|
||||
<Text style={styles.confidencePercentage}>{lowPercent}%</Text>
|
||||
</View>
|
||||
<View style={styles.confidenceBarContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.confidenceBar,
|
||||
{
|
||||
backgroundColor: theme.colors.error,
|
||||
width: low === 0 ? 4 : `${Math.max(lowPercent, 5)}%`,
|
||||
opacity: getBarOpacity(low)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.confidenceValue}>{low} predictions</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Summary Stats */}
|
||||
<View style={styles.confidenceSummary}>
|
||||
<Text style={styles.summaryText}>
|
||||
Total Predictions: <Text style={styles.summaryValue}>{total}</Text>
|
||||
</Text>
|
||||
<Text style={styles.summaryText}>
|
||||
High Confidence Rate: <Text style={[styles.summaryValue, { color: theme.colors.success }]}>{highPercent}%</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@ -311,6 +429,30 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
if (!dashboardData?.data.urgency_levels) return null;
|
||||
|
||||
const { critical, urgent, routine } = dashboardData.data.urgency_levels;
|
||||
|
||||
// Check if the object is empty or if all values are undefined/null/zero
|
||||
if (!critical && !urgent && !routine) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No data found</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if all values are zero (no cases)
|
||||
if (critical === 0 && urgent === 0 && routine === 0) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No cases recorded yet</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
@ -336,46 +478,69 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* renderFeedbackAnalysis Function
|
||||
*
|
||||
* Purpose: Render feedback analysis section
|
||||
* Purpose: Render feedback analysis section with pie chart
|
||||
*/
|
||||
const renderFeedbackAnalysis = () => {
|
||||
if (!dashboardData?.data.feedback_analysis) return null;
|
||||
const renderFeedbackAnalysis = () => {
|
||||
if (!dashboardData?.data.feedback_analysis) return null;
|
||||
|
||||
const { positive, negative, total } = dashboardData.data.feedback_analysis;
|
||||
const positivePercentage = total > 0 ? ((positive / total) * 100).toFixed(1) : '0';
|
||||
const negativePercentage = total > 0 ? ((negative / total) * 100).toFixed(1) : '0';
|
||||
const { positive, negative, total } = dashboardData.data.feedback_analysis;
|
||||
|
||||
// Check if the object is empty or if all values are undefined/null/zero
|
||||
if (!positive && !negative && !total) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No data found</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if all values are zero (no feedback)
|
||||
if (positive === 0 && negative === 0 && total === 0) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No feedback recorded yet</Text>
|
||||
<Text style={styles.emptyStateSubtext}>Feedback analysis will appear once users provide feedback</Text>
|
||||
<View style={styles.emptyStateInfo}>
|
||||
<Text style={styles.emptyStateInfoText}>• No user feedback has been submitted yet</Text>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
||||
<View style={styles.feedbackContainer}>
|
||||
<View style={styles.feedbackItem}>
|
||||
<View style={[styles.feedbackIndicator, { backgroundColor: theme.colors.success }]} />
|
||||
<Text style={styles.feedbackLabel}>Positive</Text>
|
||||
<Text style={styles.feedbackValue}>{positive}</Text>
|
||||
<Text style={styles.feedbackPercentage}>({positivePercentage}%)</Text>
|
||||
</View>
|
||||
<View style={styles.feedbackItem}>
|
||||
<View style={[styles.feedbackIndicator, { backgroundColor: theme.colors.error }]} />
|
||||
<Text style={styles.feedbackLabel}>Negative</Text>
|
||||
<Text style={styles.feedbackValue}>{negative}</Text>
|
||||
<Text style={styles.feedbackPercentage}>({negativePercentage}%)</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.feedbackSummary}>
|
||||
<Text style={styles.feedbackSummaryText}>
|
||||
Feedback Coverage: {dashboardData.data.feedback_rate_percentage}%
|
||||
</Text>
|
||||
<Text style={styles.feedbackSummaryText}>
|
||||
Average Feedback per Prediction: {dashboardData.data.average_feedback_per_prediction}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
||||
|
||||
{/* Pie Chart */}
|
||||
<FeedbackAnalysisPieChart
|
||||
data={{ positive, negative, total }}
|
||||
title="Feedback Distribution"
|
||||
width={Dimensions.get('window').width - (theme.spacing.md * 2)}
|
||||
height={220}
|
||||
/>
|
||||
|
||||
{/* Additional Feedback Metrics */}
|
||||
<View style={styles.feedbackMetrics}>
|
||||
<Text style={styles.feedbackMetricsText}>
|
||||
Feedback Coverage: {dashboardData.data.feedback_rate_percentage}%
|
||||
</Text>
|
||||
<Text style={styles.feedbackMetricsText}>
|
||||
Average Feedback per Prediction: {dashboardData.data.average_feedback_per_prediction}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* renderTimeAnalysis Function
|
||||
@ -386,6 +551,31 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
if (!dashboardData?.data.time_analysis) return null;
|
||||
|
||||
const { today, this_week, this_month, this_year } = dashboardData.data.time_analysis;
|
||||
|
||||
// Check if the object is empty or if all values are undefined/null/zero
|
||||
if (!today && !this_week && !this_month && !this_year) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No data found</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if all values are zero (no activity)
|
||||
if (today === 0 && this_week === 0 && this_month === 0 && this_year === 0) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
||||
<View style={styles.emptyStateContainer}>
|
||||
<Text style={styles.emptyStateText}>No activity recorded yet</Text>
|
||||
<Text style={styles.emptyStateSubtext}>Time-based statistics will appear once AI predictions are made</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
@ -423,7 +613,7 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
<View style={styles.headerTop}>
|
||||
<Text style={styles.dashboardTitle}>AI Analysis Dashboard</Text>
|
||||
<Text style={styles.dashboardSubtitle}>
|
||||
{dashboardData?.message || 'Loading statistics...'}
|
||||
{dashboardMessage}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@ -458,14 +648,19 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// LOADING STATE
|
||||
// MAIN RENDER
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Loading state render
|
||||
*
|
||||
* Purpose: Show loading indicator while data is being generated
|
||||
*/
|
||||
// Show error state if there's a critical error
|
||||
if (error) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{renderErrorState()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Show loading state while data is being fetched
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.loadingContainer}>
|
||||
@ -474,9 +669,10 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN RENDER
|
||||
// ============================================================================
|
||||
// Show no data state if dashboard loads but has no meaningful data
|
||||
if (!dashboardData || !dashboardData.data) {
|
||||
return renderNoDataState();
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@ -486,7 +682,7 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={handleRefresh}
|
||||
colors={[theme.colors.primary]}
|
||||
tintColor={theme.colors.primary}
|
||||
@ -641,39 +837,73 @@ const styles = StyleSheet.create({
|
||||
|
||||
// Confidence breakdown container
|
||||
confidenceContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'flex-end',
|
||||
height: 120,
|
||||
gap: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Confidence item styling
|
||||
confidenceItem: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md,
|
||||
marginBottom: theme.spacing.sm,
|
||||
...theme.shadows.small,
|
||||
},
|
||||
|
||||
// Confidence bar styling
|
||||
confidenceBar: {
|
||||
width: 40,
|
||||
height: 12,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
marginBottom: theme.spacing.sm,
|
||||
minHeight: 4,
|
||||
backgroundColor: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Confidence label styling
|
||||
confidenceLabel: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
color: theme.colors.textPrimary,
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Confidence value styling
|
||||
confidenceValue: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
marginTop: theme.spacing.xs,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// Confidence header styling
|
||||
confidenceHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Confidence indicator styling
|
||||
confidenceIndicator: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 8,
|
||||
marginRight: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Confidence percentage styling
|
||||
confidencePercentage: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
|
||||
// Confidence bar container styling
|
||||
confidenceBarContainer: {
|
||||
width: '100%',
|
||||
height: 12,
|
||||
backgroundColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
marginBottom: theme.spacing.sm,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
||||
// Urgency container styling
|
||||
@ -770,6 +1000,24 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Feedback metrics container styling
|
||||
feedbackMetrics: {
|
||||
marginTop: theme.spacing.md,
|
||||
paddingTop: theme.spacing.md,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Feedback metrics text styling
|
||||
feedbackMetricsText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Time container styling
|
||||
timeContainer: {
|
||||
@ -797,11 +1045,205 @@ const styles = StyleSheet.create({
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Confidence summary styling
|
||||
confidenceSummary: {
|
||||
marginTop: theme.spacing.lg,
|
||||
paddingTop: theme.spacing.md,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md,
|
||||
marginHorizontal: -theme.spacing.md,
|
||||
},
|
||||
|
||||
// Summary text styling
|
||||
summaryText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Summary value styling
|
||||
summaryValue: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Bottom spacing for tab bar
|
||||
bottomSpacing: {
|
||||
height: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Empty state container styling
|
||||
emptyStateContainer: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Empty state text styling
|
||||
emptyStateText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Empty state subtext styling
|
||||
emptyStateSubtext: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Empty state info styling
|
||||
emptyStateInfo: {
|
||||
marginTop: theme.spacing.sm,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Empty state info text styling
|
||||
emptyStateInfoText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Retry button styling
|
||||
retryButton: {
|
||||
marginTop: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.sm,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Retry button text styling
|
||||
retryButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Error container styling
|
||||
errorContainer: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: theme.spacing.lg,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
marginHorizontal: theme.spacing.md,
|
||||
marginBottom: theme.spacing.md,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Error title styling
|
||||
errorTitle: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Error message styling
|
||||
errorMessage: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Error info styling
|
||||
errorInfo: {
|
||||
marginTop: theme.spacing.sm,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Error info text styling
|
||||
errorInfoText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// No data container styling
|
||||
noDataContainer: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: theme.spacing.lg,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
marginHorizontal: theme.spacing.md,
|
||||
marginBottom: theme.spacing.md,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// No data title styling
|
||||
noDataTitle: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// No data message styling
|
||||
noDataMessage: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// No data info styling
|
||||
noDataInfo: {
|
||||
marginTop: theme.spacing.sm,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
},
|
||||
|
||||
// No data info text styling
|
||||
noDataInfoText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Network status container styling
|
||||
networkStatusContainer: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: theme.spacing.md,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
marginHorizontal: theme.spacing.md,
|
||||
marginBottom: theme.spacing.md,
|
||||
...theme.shadows.small,
|
||||
},
|
||||
|
||||
// Network status text styling
|
||||
networkStatusText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.warning,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Network status subtext styling
|
||||
networkStatusSubtext: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.warning,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
165
app/modules/Dashboard/services/dashboardAPI.ts
Normal file
165
app/modules/Dashboard/services/dashboardAPI.ts
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* File: dashboardAPI.ts
|
||||
* Description: API service for dashboard operations using apisauce
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { create } from 'apisauce';
|
||||
import { API_CONFIG, buildHeaders } from '../../../shared/utils';
|
||||
|
||||
const api = create({
|
||||
baseURL: API_CONFIG.BASE_URL
|
||||
});
|
||||
|
||||
/**
|
||||
* Dashboard API Service
|
||||
*
|
||||
* Purpose: Handle all dashboard-related API operations
|
||||
*
|
||||
* Features:
|
||||
* - Get AI analysis dashboard statistics
|
||||
* - Get feedback statistics for AI cases
|
||||
* - Get real-time dashboard metrics
|
||||
* - Get time-based analysis data
|
||||
*/
|
||||
export const dashboardAPI = {
|
||||
/**
|
||||
* Get AI Analysis Dashboard Statistics
|
||||
*
|
||||
* Purpose: Fetch comprehensive dashboard statistics for AI analysis
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @returns Promise with dashboard statistics data
|
||||
*/
|
||||
getDashboardStatistics: (token: string) => {
|
||||
return api.get('/api/ai-cases/feedbacks/statistics', {}, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Real-time Dashboard Metrics
|
||||
*
|
||||
* Purpose: Fetch real-time dashboard metrics for live updates
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @returns Promise with real-time dashboard metrics
|
||||
*/
|
||||
getRealTimeMetrics: (token: string) => {
|
||||
return api.get('/api/ai-cases/feedbacks/statistics/realtime', {}, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Time-based Analysis Data
|
||||
*
|
||||
* Purpose: Fetch time-based analysis data for trend visualization
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param timeRange - Time range for analysis (today, week, month, year)
|
||||
* @returns Promise with time-based analysis data
|
||||
*/
|
||||
getTimeBasedAnalysis: (token: string, timeRange: 'today' | 'week' | 'month' | 'year') => {
|
||||
return api.get(`/api/ai-cases/feedbacks/statistics/time-analysis/${timeRange}`, {}, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Hospital-specific Statistics
|
||||
*
|
||||
* Purpose: Fetch statistics for a specific hospital
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param hospitalId - Hospital identifier
|
||||
* @returns Promise with hospital-specific statistics
|
||||
*/
|
||||
getHospitalStatistics: (token: string, hospitalId: string) => {
|
||||
return api.get(`/api/ai-cases/feedbacks/statistics/hospital/${hospitalId}`, {}, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Department Performance Metrics
|
||||
*
|
||||
* Purpose: Fetch performance metrics for specific departments
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param department - Department name
|
||||
* @returns Promise with department performance data
|
||||
*/
|
||||
getDepartmentMetrics: (token: string, department: string) => {
|
||||
return api.get(`/api/ai-cases/feedbacks/statistics/department/${department}`, {}, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Confidence Score Distribution
|
||||
*
|
||||
* Purpose: Fetch confidence score distribution for AI predictions
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param timeRange - Optional time range filter
|
||||
* @returns Promise with confidence score distribution data
|
||||
*/
|
||||
getConfidenceDistribution: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
|
||||
const params = timeRange ? { timeRange } : {};
|
||||
return api.get('/api/ai-cases/feedbacks/statistics/confidence-distribution', params, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Urgency Level Distribution
|
||||
*
|
||||
* Purpose: Fetch urgency level distribution for AI cases
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param timeRange - Optional time range filter
|
||||
* @returns Promise with urgency level distribution data
|
||||
*/
|
||||
getUrgencyDistribution: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
|
||||
const params = timeRange ? { timeRange } : {};
|
||||
return api.get('/api/ai-cases/feedbacks/statistics/urgency-distribution', params, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Feedback Analysis Data
|
||||
*
|
||||
* Purpose: Fetch feedback analysis and coverage metrics
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param timeRange - Optional time range filter
|
||||
* @returns Promise with feedback analysis data
|
||||
*/
|
||||
getFeedbackAnalysis: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
|
||||
const params = timeRange ? { timeRange } : {};
|
||||
return api.get('/api/ai-cases/feedbacks/statistics/feedback-analysis', params, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Critical Findings Statistics
|
||||
*
|
||||
* Purpose: Fetch statistics for critical findings and cases
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param timeRange - Optional time range filter
|
||||
* @returns Promise with critical findings statistics
|
||||
*/
|
||||
getCriticalFindingsStats: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
|
||||
const params = timeRange ? { timeRange } : {};
|
||||
return api.get('/api/ai-cases/feedbacks/statistics/critical-findings', params, buildHeaders({ token }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Prediction Breakdown Statistics
|
||||
*
|
||||
* Purpose: Fetch breakdown of AI predictions by category
|
||||
*
|
||||
* @param token - Authentication token
|
||||
* @param timeRange - Optional time range filter
|
||||
* @returns Promise with prediction breakdown data
|
||||
*/
|
||||
getPredictionBreakdown: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
|
||||
const params = timeRange ? { timeRange } : {};
|
||||
return api.get('/api/ai-cases/feedbacks/statistics/prediction-breakdown', params, buildHeaders({ token }));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: dashboardAPI.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
14
app/modules/Dashboard/services/index.ts
Normal file
14
app/modules/Dashboard/services/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Dashboard services exports
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export * from './dashboardAPI';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
@ -798,7 +798,6 @@ const PatientDetailsScreen: React.FC<PatientDetailsScreenProps> = ({ navigation,
|
||||
|
||||
{patientData.series_summary.map((series, seriesIndex) => {
|
||||
// Get predictions for this series
|
||||
{console.log('series.png_preview', series)}
|
||||
const seriesPredictions = patientData.predictions_by_series[series.series_num] || [];
|
||||
const hasPredictions = seriesPredictions.length > 0;
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
// Import all slice reducers from their respective modules
|
||||
import authReducer from '../modules/Auth/redux/authSlice';
|
||||
import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
|
||||
import aiDashboardReducer from '../modules/Dashboard/redux/aiDashboardSlice';
|
||||
import patientCareReducer from '../modules/PatientCare/redux/patientCareSlice';
|
||||
import alertsReducer from '../modules/Dashboard/redux/alertsSlice';
|
||||
import settingsReducer from '../modules/Settings/redux/settingsSlice';
|
||||
@ -57,6 +58,7 @@ const persistConfig = {
|
||||
'ui', // UI state (loading, modals, etc.)
|
||||
'alerts', // Temporary alerts and notifications
|
||||
'dashboard', // Real-time dashboard data
|
||||
'aiDashboard', // AI dashboard statistics (fetched fresh each time)
|
||||
'hospital', // Hospital data (fetched fresh each time)
|
||||
],
|
||||
|
||||
@ -85,6 +87,7 @@ const persistConfig = {
|
||||
* Structure:
|
||||
* - auth: Authentication and user management
|
||||
* - dashboard: ER dashboard data and statistics
|
||||
* - aiDashboard: AI analysis dashboard statistics
|
||||
* - patientCare: Patient information and medical records
|
||||
* - aiPrediction: AI prediction cases and analysis
|
||||
* - alerts: Critical alerts and notifications
|
||||
@ -94,6 +97,7 @@ const persistConfig = {
|
||||
const rootReducer = combineReducers({
|
||||
auth: authReducer,
|
||||
dashboard: dashboardReducer,
|
||||
aiDashboard: aiDashboardReducer,
|
||||
patientCare: patientCareReducer,
|
||||
aiPrediction: aiPredictionReducer,
|
||||
alerts: alertsReducer,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user