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.
|
||||||
|
*/
|
||||||
@ -4,3 +4,4 @@ export { DashboardHeader } from './DashboardHeader';
|
|||||||
export { QuickActions } from './QuickActions';
|
export { QuickActions } from './QuickActions';
|
||||||
export { DepartmentStats } from './DepartmentStats';
|
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 QuickActions } from './components/QuickActions';
|
||||||
export { default as DepartmentStats } from './components/DepartmentStats';
|
export { default as DepartmentStats } from './components/DepartmentStats';
|
||||||
|
|
||||||
|
// Export hooks
|
||||||
|
export * from './hooks';
|
||||||
|
|
||||||
// Export Redux
|
// Export Redux
|
||||||
export {
|
export {
|
||||||
fetchDashboardData,
|
fetchDashboardData,
|
||||||
@ -51,6 +54,36 @@ export {
|
|||||||
updateDashboardData,
|
updateDashboardData,
|
||||||
} from './redux/dashboardSlice';
|
} 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 {
|
export {
|
||||||
fetchAlerts,
|
fetchAlerts,
|
||||||
acknowledgeAlert,
|
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,
|
TouchableOpacity,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
FlatList,
|
FlatList,
|
||||||
|
Dimensions,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { theme } from '../../../theme/theme';
|
import { theme } from '../../../theme/theme';
|
||||||
import { DashboardHeader } from '../components/DashboardHeader';
|
import { DashboardHeader } from '../components/DashboardHeader';
|
||||||
import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview';
|
import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview';
|
||||||
|
import { FeedbackAnalysisPieChart } from '../components/FeedbackAnalysisPieChart';
|
||||||
|
import { useAIDashboard } from '../hooks/useAIDashboard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DashboardScreenProps Interface
|
* DashboardScreenProps Interface
|
||||||
@ -120,108 +123,18 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
navigation,
|
navigation,
|
||||||
}) => {
|
}) => {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// STATE MANAGEMENT
|
// CUSTOM HOOKS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Refresh state for pull-to-refresh functionality
|
// Use custom hook for AI dashboard functionality
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const {
|
||||||
|
dashboardData,
|
||||||
// Dashboard data state
|
isLoading,
|
||||||
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
|
isRefreshing,
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
error,
|
||||||
|
dashboardMessage,
|
||||||
// ============================================================================
|
refreshDashboardStatistics
|
||||||
// MOCK DATA GENERATION
|
} = useAIDashboard();
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// EVENT HANDLERS
|
// EVENT HANDLERS
|
||||||
@ -233,21 +146,86 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
* Purpose: Handle pull-to-refresh functionality to update dashboard data
|
* Purpose: Handle pull-to-refresh functionality to update dashboard data
|
||||||
*/
|
*/
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
setRefreshing(true);
|
// Refresh dashboard statistics from API
|
||||||
|
refreshDashboardStatistics();
|
||||||
// Simulate API call with 1-second delay
|
|
||||||
setTimeout(() => {}, 1000);
|
|
||||||
|
|
||||||
// Update data with fresh mock data
|
|
||||||
setDashboardData(generateMockDashboardData());
|
|
||||||
|
|
||||||
setRefreshing(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// RENDER FUNCTIONS
|
// 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
|
* renderStatsCard Function
|
||||||
*
|
*
|
||||||
@ -273,31 +251,171 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
* Purpose: Render confidence score breakdown section
|
* Purpose: Render confidence score breakdown section
|
||||||
*/
|
*/
|
||||||
const renderConfidenceBreakdown = () => {
|
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;
|
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;
|
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 (
|
return (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
||||||
<View style={styles.confidenceContainer}>
|
<View style={styles.confidenceContainer}>
|
||||||
|
{/* High Confidence */}
|
||||||
<View style={styles.confidenceItem}>
|
<View style={styles.confidenceItem}>
|
||||||
<View style={[styles.confidenceBar, { backgroundColor: theme.colors.success, height: (high / total) * 100 }]} />
|
<View style={styles.confidenceHeader}>
|
||||||
<Text style={styles.confidenceLabel}>High</Text>
|
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.success }]} />
|
||||||
<Text style={styles.confidenceValue}>{high}</Text>
|
<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>
|
</View>
|
||||||
|
|
||||||
|
{/* Medium Confidence */}
|
||||||
<View style={styles.confidenceItem}>
|
<View style={styles.confidenceItem}>
|
||||||
<View style={[styles.confidenceBar, { backgroundColor: theme.colors.warning, height: (medium / total) * 100 }]} />
|
<View style={styles.confidenceHeader}>
|
||||||
<Text style={styles.confidenceLabel}>Medium</Text>
|
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.warning }]} />
|
||||||
<Text style={styles.confidenceValue}>{medium}</Text>
|
<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>
|
</View>
|
||||||
|
|
||||||
|
{/* Low Confidence */}
|
||||||
<View style={styles.confidenceItem}>
|
<View style={styles.confidenceItem}>
|
||||||
<View style={[styles.confidenceBar, { backgroundColor: theme.colors.error, height: (low / total) * 100 }]} />
|
<View style={styles.confidenceHeader}>
|
||||||
<Text style={styles.confidenceLabel}>Low</Text>
|
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.error }]} />
|
||||||
<Text style={styles.confidenceValue}>{low}</Text>
|
<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>
|
||||||
</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>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -312,6 +430,30 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
|
|
||||||
const { critical, urgent, routine } = dashboardData.data.urgency_levels;
|
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 (
|
return (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
||||||
@ -336,46 +478,69 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* renderFeedbackAnalysis Function
|
* renderFeedbackAnalysis Function
|
||||||
*
|
*
|
||||||
* Purpose: Render feedback analysis section
|
* Purpose: Render feedback analysis section with pie chart
|
||||||
*/
|
*/
|
||||||
const renderFeedbackAnalysis = () => {
|
const renderFeedbackAnalysis = () => {
|
||||||
if (!dashboardData?.data.feedback_analysis) return null;
|
if (!dashboardData?.data.feedback_analysis) return null;
|
||||||
|
|
||||||
const { positive, negative, total } = dashboardData.data.feedback_analysis;
|
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';
|
|
||||||
|
|
||||||
return (
|
// Check if the object is empty or if all values are undefined/null/zero
|
||||||
<View style={styles.section}>
|
if (!positive && !negative && !total) {
|
||||||
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
return (
|
||||||
<View style={styles.feedbackContainer}>
|
<View style={styles.section}>
|
||||||
<View style={styles.feedbackItem}>
|
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
||||||
<View style={[styles.feedbackIndicator, { backgroundColor: theme.colors.success }]} />
|
<View style={styles.emptyStateContainer}>
|
||||||
<Text style={styles.feedbackLabel}>Positive</Text>
|
<Text style={styles.emptyStateText}>No data found</Text>
|
||||||
<Text style={styles.feedbackValue}>{positive}</Text>
|
</View>
|
||||||
<Text style={styles.feedbackPercentage}>({positivePercentage}%)</Text>
|
</View>
|
||||||
</View>
|
);
|
||||||
<View style={styles.feedbackItem}>
|
}
|
||||||
<View style={[styles.feedbackIndicator, { backgroundColor: theme.colors.error }]} />
|
|
||||||
<Text style={styles.feedbackLabel}>Negative</Text>
|
// Check if all values are zero (no feedback)
|
||||||
<Text style={styles.feedbackValue}>{negative}</Text>
|
if (positive === 0 && negative === 0 && total === 0) {
|
||||||
<Text style={styles.feedbackPercentage}>({negativePercentage}%)</Text>
|
return (
|
||||||
</View>
|
<View style={styles.section}>
|
||||||
</View>
|
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
||||||
<View style={styles.feedbackSummary}>
|
<View style={styles.emptyStateContainer}>
|
||||||
<Text style={styles.feedbackSummaryText}>
|
<Text style={styles.emptyStateText}>No feedback recorded yet</Text>
|
||||||
Feedback Coverage: {dashboardData.data.feedback_rate_percentage}%
|
<Text style={styles.emptyStateSubtext}>Feedback analysis will appear once users provide feedback</Text>
|
||||||
</Text>
|
<View style={styles.emptyStateInfo}>
|
||||||
<Text style={styles.feedbackSummaryText}>
|
<Text style={styles.emptyStateInfoText}>• No user feedback has been submitted yet</Text>
|
||||||
Average Feedback per Prediction: {dashboardData.data.average_feedback_per_prediction}
|
|
||||||
</Text>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</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
|
* renderTimeAnalysis Function
|
||||||
@ -387,6 +552,31 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
|
|
||||||
const { today, this_week, this_month, this_year } = dashboardData.data.time_analysis;
|
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 (
|
return (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
||||||
@ -423,7 +613,7 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
<View style={styles.headerTop}>
|
<View style={styles.headerTop}>
|
||||||
<Text style={styles.dashboardTitle}>AI Analysis Dashboard</Text>
|
<Text style={styles.dashboardTitle}>AI Analysis Dashboard</Text>
|
||||||
<Text style={styles.dashboardSubtitle}>
|
<Text style={styles.dashboardSubtitle}>
|
||||||
{dashboardData?.message || 'Loading statistics...'}
|
{dashboardMessage}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -458,14 +648,19 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// LOADING STATE
|
// MAIN RENDER
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
// Show error state if there's a critical error
|
||||||
* Loading state render
|
if (error) {
|
||||||
*
|
return (
|
||||||
* Purpose: Show loading indicator while data is being generated
|
<View style={styles.container}>
|
||||||
*/
|
{renderErrorState()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading state while data is being fetched
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
@ -474,9 +669,10 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// Show no data state if dashboard loads but has no meaningful data
|
||||||
// MAIN RENDER
|
if (!dashboardData || !dashboardData.data) {
|
||||||
// ============================================================================
|
return renderNoDataState();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@ -486,7 +682,7 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
refreshing={refreshing}
|
refreshing={isRefreshing}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
colors={[theme.colors.primary]}
|
colors={[theme.colors.primary]}
|
||||||
tintColor={theme.colors.primary}
|
tintColor={theme.colors.primary}
|
||||||
@ -641,39 +837,73 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
// Confidence breakdown container
|
// Confidence breakdown container
|
||||||
confidenceContainer: {
|
confidenceContainer: {
|
||||||
flexDirection: 'row',
|
gap: theme.spacing.md,
|
||||||
justifyContent: 'space-around',
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
height: 120,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Confidence item styling
|
// Confidence item styling
|
||||||
confidenceItem: {
|
confidenceItem: {
|
||||||
alignItems: 'center',
|
backgroundColor: theme.colors.backgroundAlt,
|
||||||
flex: 1,
|
borderRadius: theme.borderRadius.medium,
|
||||||
|
padding: theme.spacing.md,
|
||||||
|
marginBottom: theme.spacing.sm,
|
||||||
|
...theme.shadows.small,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Confidence bar styling
|
// Confidence bar styling
|
||||||
confidenceBar: {
|
confidenceBar: {
|
||||||
width: 40,
|
height: 12,
|
||||||
borderRadius: theme.borderRadius.small,
|
borderRadius: theme.borderRadius.small,
|
||||||
marginBottom: theme.spacing.sm,
|
backgroundColor: theme.colors.primary,
|
||||||
minHeight: 4,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Confidence label styling
|
// Confidence label styling
|
||||||
confidenceLabel: {
|
confidenceLabel: {
|
||||||
fontSize: theme.typography.fontSize.bodySmall,
|
fontSize: theme.typography.fontSize.bodyMedium,
|
||||||
fontFamily: theme.typography.fontFamily.medium,
|
fontFamily: theme.typography.fontFamily.medium,
|
||||||
color: theme.colors.textSecondary,
|
color: theme.colors.textPrimary,
|
||||||
marginBottom: theme.spacing.xs,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Confidence value styling
|
// Confidence value styling
|
||||||
confidenceValue: {
|
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,
|
fontSize: theme.typography.fontSize.bodyMedium,
|
||||||
fontFamily: theme.typography.fontFamily.bold,
|
fontFamily: theme.typography.fontFamily.bold,
|
||||||
color: theme.colors.textPrimary,
|
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
|
// Urgency container styling
|
||||||
@ -771,6 +1001,24 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: theme.spacing.xs,
|
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
|
// Time container styling
|
||||||
timeContainer: {
|
timeContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -798,10 +1046,204 @@ const styles = StyleSheet.create({
|
|||||||
color: theme.colors.textPrimary,
|
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
|
// Bottom spacing for tab bar
|
||||||
bottomSpacing: {
|
bottomSpacing: {
|
||||||
height: theme.spacing.xl,
|
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) => {
|
{patientData.series_summary.map((series, seriesIndex) => {
|
||||||
// Get predictions for this series
|
// Get predictions for this series
|
||||||
{console.log('series.png_preview', series)}
|
|
||||||
const seriesPredictions = patientData.predictions_by_series[series.series_num] || [];
|
const seriesPredictions = patientData.predictions_by_series[series.series_num] || [];
|
||||||
const hasPredictions = seriesPredictions.length > 0;
|
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 all slice reducers from their respective modules
|
||||||
import authReducer from '../modules/Auth/redux/authSlice';
|
import authReducer from '../modules/Auth/redux/authSlice';
|
||||||
import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
|
import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
|
||||||
|
import aiDashboardReducer from '../modules/Dashboard/redux/aiDashboardSlice';
|
||||||
import patientCareReducer from '../modules/PatientCare/redux/patientCareSlice';
|
import patientCareReducer from '../modules/PatientCare/redux/patientCareSlice';
|
||||||
import alertsReducer from '../modules/Dashboard/redux/alertsSlice';
|
import alertsReducer from '../modules/Dashboard/redux/alertsSlice';
|
||||||
import settingsReducer from '../modules/Settings/redux/settingsSlice';
|
import settingsReducer from '../modules/Settings/redux/settingsSlice';
|
||||||
@ -57,6 +58,7 @@ const persistConfig = {
|
|||||||
'ui', // UI state (loading, modals, etc.)
|
'ui', // UI state (loading, modals, etc.)
|
||||||
'alerts', // Temporary alerts and notifications
|
'alerts', // Temporary alerts and notifications
|
||||||
'dashboard', // Real-time dashboard data
|
'dashboard', // Real-time dashboard data
|
||||||
|
'aiDashboard', // AI dashboard statistics (fetched fresh each time)
|
||||||
'hospital', // Hospital data (fetched fresh each time)
|
'hospital', // Hospital data (fetched fresh each time)
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -85,6 +87,7 @@ const persistConfig = {
|
|||||||
* Structure:
|
* Structure:
|
||||||
* - auth: Authentication and user management
|
* - auth: Authentication and user management
|
||||||
* - dashboard: ER dashboard data and statistics
|
* - dashboard: ER dashboard data and statistics
|
||||||
|
* - aiDashboard: AI analysis dashboard statistics
|
||||||
* - patientCare: Patient information and medical records
|
* - patientCare: Patient information and medical records
|
||||||
* - aiPrediction: AI prediction cases and analysis
|
* - aiPrediction: AI prediction cases and analysis
|
||||||
* - alerts: Critical alerts and notifications
|
* - alerts: Critical alerts and notifications
|
||||||
@ -94,6 +97,7 @@ const persistConfig = {
|
|||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
auth: authReducer,
|
auth: authReducer,
|
||||||
dashboard: dashboardReducer,
|
dashboard: dashboardReducer,
|
||||||
|
aiDashboard: aiDashboardReducer,
|
||||||
patientCare: patientCareReducer,
|
patientCare: patientCareReducer,
|
||||||
aiPrediction: aiPredictionReducer,
|
aiPrediction: aiPredictionReducer,
|
||||||
alerts: alertsReducer,
|
alerts: alertsReducer,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user