1287 lines
39 KiB
TypeScript
1287 lines
39 KiB
TypeScript
/*
|
|
* File: DashboardScreen.tsx
|
|
* Description: AI Analysis Dashboard - Main dashboard for AI predictions and analysis statistics
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
TouchableOpacity,
|
|
RefreshControl,
|
|
Dimensions,
|
|
} from 'react-native';
|
|
import { theme } from '../../../theme/theme';
|
|
import { FeedbackAnalysisPieChart } from '../components/FeedbackAnalysisPieChart';
|
|
import { useAIDashboard } from '../hooks/useAIDashboard';
|
|
import { selectUserFirstName } from '../../Auth/redux/authSelectors';
|
|
import { useAppSelector } from '../../../store/hooks';
|
|
|
|
/**
|
|
* DashboardScreenProps Interface
|
|
*
|
|
* Purpose: Defines the props required by the DashboardScreen component
|
|
*
|
|
* Props:
|
|
* - navigation: React Navigation object for screen navigation
|
|
*/
|
|
interface DashboardScreenProps {
|
|
navigation: any;
|
|
}
|
|
|
|
/**
|
|
* Dashboard Stats Data Interface
|
|
*
|
|
* Purpose: Defines the structure of the dashboard statistics data
|
|
*/
|
|
interface DashboardStats {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Dashboard Summary Interface
|
|
*
|
|
* Purpose: Defines the structure of the dashboard summary data
|
|
*/
|
|
interface DashboardSummary {
|
|
total_cases: number;
|
|
critical_cases: number;
|
|
routine_cases: number;
|
|
feedback_coverage: string;
|
|
critical_case_rate: string;
|
|
average_confidence: string;
|
|
}
|
|
|
|
/**
|
|
* Complete Dashboard Data Interface
|
|
*
|
|
* Purpose: Defines the complete structure of the dashboard API response
|
|
*/
|
|
interface DashboardData {
|
|
success: boolean;
|
|
data: DashboardStats;
|
|
summary: DashboardSummary;
|
|
message: string;
|
|
}
|
|
|
|
/**
|
|
* DashboardScreen Component
|
|
*
|
|
* Purpose: AI Analysis Dashboard for physicians showing prediction statistics
|
|
*
|
|
* Dashboard Features:
|
|
* 1. AI prediction statistics and breakdown
|
|
* 2. Feedback analysis and coverage metrics
|
|
* 3. Confidence score distribution
|
|
* 4. Time-based analysis trends
|
|
* 5. Urgency level distribution
|
|
* 6. Pull-to-refresh functionality for live updates
|
|
*/
|
|
export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|
navigation,
|
|
}) => {
|
|
// ============================================================================
|
|
// CUSTOM HOOKS & SELECTORS
|
|
// ============================================================================
|
|
|
|
// Use custom hook for AI dashboard functionality
|
|
const {
|
|
dashboardData,
|
|
isLoading,
|
|
isRefreshing,
|
|
error,
|
|
dashboardMessage,
|
|
refreshDashboardStatistics
|
|
} = useAIDashboard();
|
|
|
|
// Get user display name from auth state
|
|
const userDisplayName = useAppSelector(selectUserFirstName);
|
|
|
|
// ============================================================================
|
|
// HELPER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* getPersonalizedGreeting Function
|
|
*
|
|
* Purpose: Generate a personalized greeting based on time of day and user's display name
|
|
*
|
|
* @returns Personalized greeting string
|
|
*/
|
|
const getPersonalizedGreeting = (): string => {
|
|
const currentHour = new Date().getHours();
|
|
let timeGreeting = '';
|
|
|
|
// Determine time-based greeting
|
|
if (currentHour >= 5 && currentHour < 12) {
|
|
timeGreeting = 'Good Morning';
|
|
} else if (currentHour >= 12 && currentHour < 17) {
|
|
timeGreeting = 'Good Afternoon';
|
|
} else if (currentHour >= 17 && currentHour < 21) {
|
|
timeGreeting = 'Good Evening';
|
|
} else {
|
|
timeGreeting = 'Good Evening';
|
|
}
|
|
|
|
// Create personalized greeting with fallback
|
|
const displayName = userDisplayName || 'Doctor';
|
|
return `${timeGreeting}, Dr. ${displayName}`;
|
|
};
|
|
|
|
// ============================================================================
|
|
// EVENT HANDLERS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* handleRefresh Function
|
|
*
|
|
* Purpose: Handle pull-to-refresh functionality to update dashboard data
|
|
*/
|
|
const handleRefresh = async () => {
|
|
// Refresh dashboard statistics from API
|
|
refreshDashboardStatistics();
|
|
};
|
|
|
|
// ============================================================================
|
|
// RENDER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* renderErrorState Function
|
|
*
|
|
* Purpose: Render error state when there's a critical error
|
|
*/
|
|
const renderErrorState = () => {
|
|
if (!error) return null;
|
|
|
|
return (
|
|
<View style={styles.errorContainer}>
|
|
<Text style={styles.errorTitle}>Connection Error</Text>
|
|
<Text style={styles.errorMessage}>
|
|
Unable to connect to the dashboard service
|
|
</Text>
|
|
<View style={styles.errorInfo}>
|
|
<Text style={styles.errorInfoText}>• Check your internet connection</Text>
|
|
<Text style={styles.errorInfoText}>• Verify server status</Text>
|
|
<Text style={styles.errorInfoText}>• Try again in a few moments</Text>
|
|
</View>
|
|
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
|
|
<Text style={styles.retryButtonText}>Retry Connection</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderNetworkStatus Function
|
|
*
|
|
* Purpose: Render network status indicator
|
|
*/
|
|
const renderNetworkStatus = () => {
|
|
if (!error && dashboardData) return null;
|
|
|
|
return (
|
|
<View style={styles.networkStatusContainer}>
|
|
<Text style={styles.networkStatusText}>
|
|
{error ? '⚠️ Connection Issue' : '🔄 Checking Connection...'}
|
|
</Text>
|
|
<Text style={styles.networkStatusSubtext}>
|
|
{error ? 'Please check your internet connection' : 'Verifying dashboard service'}
|
|
</Text>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderNoDataState Function
|
|
*
|
|
* Purpose: Render no data state when dashboard loads but has no meaningful data
|
|
*/
|
|
const renderNoDataState = () => {
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.noDataContainer}>
|
|
<Text style={styles.noDataTitle}>Dashboard is Ready</Text>
|
|
<Text style={styles.noDataMessage}>
|
|
Your AI Analysis Dashboard is ready, but there's no data to display yet.
|
|
</Text>
|
|
<View style={styles.noDataInfo}>
|
|
<Text style={styles.noDataInfoText}>• AI predictions will appear here once scans are processed</Text>
|
|
<Text style={styles.noDataInfoText}>• Check back after medical scans are uploaded</Text>
|
|
<Text style={styles.noDataInfoText}>• The system will automatically populate data</Text>
|
|
</View>
|
|
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
|
|
<Text style={styles.retryButtonText}>Check for Updates</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderStatsCard Function
|
|
*
|
|
* Purpose: Render individual statistics card component
|
|
*
|
|
* @param title - Card title
|
|
* @param value - Main value to display
|
|
* @param subtitle - Optional subtitle
|
|
* @param color - Optional color theme
|
|
* @returns Statistics card component
|
|
*/
|
|
const renderStatsCard = (title: string, value: string | number, subtitle?: string, color?: string) => (
|
|
<View style={[styles.statsCard, color && { borderLeftColor: color, borderLeftWidth: 4 }]}>
|
|
<Text style={styles.statsCardTitle}>{title}</Text>
|
|
<Text style={[styles.statsCardValue, color && { color }]}>{value}</Text>
|
|
{subtitle && <Text style={styles.statsCardSubtitle}>{subtitle}</Text>}
|
|
</View>
|
|
);
|
|
|
|
/**
|
|
* renderConfidenceBreakdown Function
|
|
*
|
|
* Purpose: Render confidence score breakdown section
|
|
*/
|
|
const renderConfidenceBreakdown = () => {
|
|
// Check if dashboard data exists
|
|
if (!dashboardData) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>Dashboard data not available</Text>
|
|
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
|
|
<Text style={styles.retryButtonText}>Retry</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Check if confidence scores data exists
|
|
if (!dashboardData.data?.confidence_scores) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>Confidence data not available</Text>
|
|
<Text style={styles.emptyStateSubtext}>AI confidence scores are not currently accessible</Text>
|
|
<View style={styles.emptyStateInfo}>
|
|
<Text style={styles.emptyStateInfoText}>• AI system may be initializing</Text>
|
|
<Text style={styles.emptyStateInfoText}>• Check system status</Text>
|
|
<Text style={styles.emptyStateInfoText}>• Refresh in a few minutes</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const { high, medium, low } = dashboardData.data.confidence_scores;
|
|
|
|
// Check if the object is empty or if all values are undefined/null/zero
|
|
if (!high && !medium && !low) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No data found</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Check if all required fields exist and are numbers
|
|
if (typeof high !== 'number' || typeof medium !== 'number' || typeof low !== 'number') {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No confidence data available</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const total = high + medium + low;
|
|
|
|
// If no predictions, show empty state
|
|
if (total === 0) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No predictions available yet</Text>
|
|
<Text style={styles.emptyStateSubtext}>AI predictions will appear here once the system processes medical scans</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Calculate percentages for better visualization
|
|
const highPercent = Math.round((high / total) * 100);
|
|
const mediumPercent = Math.round((medium / total) * 100);
|
|
const lowPercent = Math.round((low / total) * 100);
|
|
|
|
// Helper function to get bar opacity
|
|
const getBarOpacity = (count: number) => {
|
|
if (count === 0) return 0.3; // Dimmed for zero values
|
|
return 0.9; // Full opacity for non-zero values
|
|
};
|
|
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
|
|
<View style={styles.confidenceContainer}>
|
|
{/* High Confidence */}
|
|
<View style={styles.confidenceItem}>
|
|
<View style={styles.confidenceHeader}>
|
|
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.success }]} />
|
|
<Text style={styles.confidenceLabel}>High Confidence</Text>
|
|
<Text style={styles.confidencePercentage}>{highPercent}%</Text>
|
|
</View>
|
|
<View style={styles.confidenceBarContainer}>
|
|
<View
|
|
style={[
|
|
styles.confidenceBar,
|
|
{
|
|
backgroundColor: theme.colors.success,
|
|
width: high === 0 ? 4 : `${Math.max(highPercent, 5)}%`,
|
|
opacity: getBarOpacity(high)
|
|
}
|
|
]}
|
|
/>
|
|
</View>
|
|
<Text style={styles.confidenceValue}>{high} predictions</Text>
|
|
</View>
|
|
|
|
{/* Medium Confidence */}
|
|
<View style={styles.confidenceItem}>
|
|
<View style={styles.confidenceHeader}>
|
|
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.warning }]} />
|
|
<Text style={styles.confidenceLabel}>Medium Confidence</Text>
|
|
<Text style={styles.confidencePercentage}>{mediumPercent}%</Text>
|
|
</View>
|
|
<View style={styles.confidenceBarContainer}>
|
|
<View
|
|
style={[
|
|
styles.confidenceBar,
|
|
{
|
|
backgroundColor: theme.colors.warning,
|
|
width: medium === 0 ? 4 : `${Math.max(mediumPercent, 5)}%`,
|
|
opacity: getBarOpacity(medium)
|
|
}
|
|
]}
|
|
/>
|
|
</View>
|
|
<Text style={styles.confidenceValue}>{medium} predictions</Text>
|
|
</View>
|
|
|
|
{/* Low Confidence */}
|
|
<View style={styles.confidenceItem}>
|
|
<View style={styles.confidenceHeader}>
|
|
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.error }]} />
|
|
<Text style={styles.confidenceLabel}>Low Confidence</Text>
|
|
<Text style={styles.confidencePercentage}>{lowPercent}%</Text>
|
|
</View>
|
|
<View style={styles.confidenceBarContainer}>
|
|
<View
|
|
style={[
|
|
styles.confidenceBar,
|
|
{
|
|
backgroundColor: theme.colors.error,
|
|
width: low === 0 ? 4 : `${Math.max(lowPercent, 5)}%`,
|
|
opacity: getBarOpacity(low)
|
|
}
|
|
]}
|
|
/>
|
|
</View>
|
|
<Text style={styles.confidenceValue}>{low} predictions</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Summary Stats */}
|
|
<View style={styles.confidenceSummary}>
|
|
<Text style={styles.summaryText}>
|
|
Total Predictions: <Text style={styles.summaryValue}>{total}</Text>
|
|
</Text>
|
|
<Text style={styles.summaryText}>
|
|
High Confidence Rate: <Text style={[styles.summaryValue, { color: theme.colors.success }]}>{highPercent}%</Text>
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderUrgencyBreakdown Function
|
|
*
|
|
* Purpose: Render urgency level breakdown section
|
|
*/
|
|
const renderUrgencyBreakdown = () => {
|
|
if (!dashboardData?.data.urgency_levels) return null;
|
|
|
|
const { critical, urgent, routine } = dashboardData.data.urgency_levels;
|
|
|
|
// Check if the object is empty or if all values are undefined/null/zero
|
|
if (!critical && !urgent && !routine) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No data found</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Check if all values are zero (no cases)
|
|
if (critical === 0 && urgent === 0 && routine === 0) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No cases recorded yet</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
|
|
<View style={styles.urgencyContainer}>
|
|
<View style={styles.urgencyItem}>
|
|
<View style={[styles.urgencyIndicator, { backgroundColor: theme.colors.error }]} />
|
|
<Text style={styles.urgencyLabel}>Critical</Text>
|
|
<Text style={styles.urgencyValue}>{critical}</Text>
|
|
</View>
|
|
<View style={styles.urgencyItem}>
|
|
<View style={[styles.urgencyIndicator, { backgroundColor: theme.colors.warning }]} />
|
|
<Text style={styles.urgencyLabel}>Urgent</Text>
|
|
<Text style={styles.urgencyValue}>{urgent}</Text>
|
|
</View>
|
|
<View style={styles.urgencyItem}>
|
|
<View style={[styles.urgencyIndicator, { backgroundColor: theme.colors.success }]} />
|
|
<Text style={styles.urgencyLabel}>Routine</Text>
|
|
<Text style={styles.urgencyValue}>{routine}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderFeedbackAnalysis Function
|
|
*
|
|
* Purpose: Render feedback analysis section with pie chart
|
|
*/
|
|
const renderFeedbackAnalysis = () => {
|
|
if (!dashboardData?.data.feedback_analysis) return null;
|
|
|
|
const { positive, negative, total } = dashboardData.data.feedback_analysis;
|
|
|
|
// Check if the object is empty or if all values are undefined/null/zero
|
|
if (!positive && !negative && !total) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No data found</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Check if all values are zero (no feedback)
|
|
if (positive === 0 && negative === 0 && total === 0) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No feedback recorded yet</Text>
|
|
<Text style={styles.emptyStateSubtext}>Feedback analysis will appear once users provide feedback</Text>
|
|
<View style={styles.emptyStateInfo}>
|
|
<Text style={styles.emptyStateInfoText}>• No user feedback has been submitted yet</Text>
|
|
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Feedback Analysis</Text>
|
|
|
|
{/* 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: {isNaN(Number(dashboardData?.data?.average_feedback_per_prediction))? "N/A" : Number(dashboardData.data.average_feedback_per_prediction).toFixed(2) }
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderTimeAnalysis Function
|
|
*
|
|
* Purpose: Render time-based analysis section
|
|
*/
|
|
const renderTimeAnalysis = () => {
|
|
if (!dashboardData?.data.time_analysis) return null;
|
|
|
|
const { today, this_week, this_month, this_year } = dashboardData.data.time_analysis;
|
|
|
|
// Check if the object is empty or if all values are undefined/null/zero
|
|
if (!today && !this_week && !this_month && !this_year) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No data found</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Check if all values are zero (no activity)
|
|
if (today === 0 && this_week === 0 && this_month === 0 && this_year === 0) {
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
|
<View style={styles.emptyStateContainer}>
|
|
<Text style={styles.emptyStateText}>No activity recorded yet</Text>
|
|
<Text style={styles.emptyStateSubtext}>Time-based statistics will appear once AI predictions are made</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Time-based Analysis</Text>
|
|
<View style={styles.timeContainer}>
|
|
<View style={styles.timeItem}>
|
|
<Text style={styles.timeLabel}>Today</Text>
|
|
<Text style={styles.timeValue}>{today}</Text>
|
|
</View>
|
|
<View style={styles.timeItem}>
|
|
<Text style={styles.timeLabel}>This Week</Text>
|
|
<Text style={styles.timeValue}>{this_week}</Text>
|
|
</View>
|
|
<View style={styles.timeItem}>
|
|
<Text style={styles.timeLabel}>This Month</Text>
|
|
<Text style={styles.timeValue}>{this_month}</Text>
|
|
</View>
|
|
<View style={styles.timeItem}>
|
|
<Text style={styles.timeLabel}>This Year</Text>
|
|
<Text style={styles.timeValue}>{this_year}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* renderHeader Function
|
|
*
|
|
* Purpose: Render the dashboard header section with key metrics
|
|
*/
|
|
const renderHeader = () => (
|
|
<View style={styles.header}>
|
|
{/* Dashboard header with title and refresh button */}
|
|
<View style={styles.headerTop}>
|
|
<Text style={styles.dashboardTitle}>{getPersonalizedGreeting()}</Text>
|
|
<Text style={styles.dashboardSubtitle}>
|
|
{dashboardMessage}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Key statistics cards */}
|
|
<View style={styles.statsGrid}>
|
|
{renderStatsCard(
|
|
'Total Predictions',
|
|
dashboardData?.data.total_predictions || 0,
|
|
'AI analyses performed',
|
|
theme.colors.primary
|
|
)}
|
|
{renderStatsCard(
|
|
'Total Patients',
|
|
dashboardData?.data.total_patients || 0,
|
|
'Unique patients',
|
|
theme.colors.info
|
|
)}
|
|
{renderStatsCard(
|
|
'Feedback Rate',
|
|
`${dashboardData?.data.feedback_rate_percentage || 0}%`,
|
|
'User feedback coverage',
|
|
theme.colors.success
|
|
)}
|
|
{renderStatsCard(
|
|
'Avg Confidence',
|
|
(dashboardData?.data.average_confidence_score || 0).toFixed(2),
|
|
'AI prediction confidence',
|
|
theme.colors.warning
|
|
)}
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
// ============================================================================
|
|
// MAIN RENDER
|
|
// ============================================================================
|
|
|
|
// Show error state if there's a critical error
|
|
if (error) {
|
|
return (
|
|
<View style={styles.container}>
|
|
{renderErrorState()}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Show loading state while data is being fetched
|
|
if (isLoading) {
|
|
return (
|
|
<View style={styles.loadingContainer}>
|
|
<Text style={styles.loadingText}>Loading AI Analysis Dashboard...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Show no data state if dashboard loads but has no meaningful data
|
|
if (!dashboardData || !dashboardData.data) {
|
|
return renderNoDataState();
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Scrollable dashboard content */}
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={styles.scrollContent}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={isRefreshing}
|
|
onRefresh={handleRefresh}
|
|
colors={[theme.colors.primary]}
|
|
tintColor={theme.colors.primary}
|
|
/>
|
|
}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* Dashboard header with key metrics */}
|
|
{renderHeader()}
|
|
|
|
{/* Confidence score breakdown */}
|
|
{renderConfidenceBreakdown()}
|
|
|
|
{/* Urgency level breakdown */}
|
|
{renderUrgencyBreakdown()}
|
|
|
|
{/* Feedback analysis */}
|
|
{renderFeedbackAnalysis()}
|
|
|
|
{/* Time-based analysis */}
|
|
{renderTimeAnalysis()}
|
|
|
|
{/* Bottom spacing for tab bar */}
|
|
<View style={styles.bottomSpacing} />
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// STYLES SECTION
|
|
// ============================================================================
|
|
|
|
const styles = StyleSheet.create({
|
|
// Main container for the dashboard screen
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: theme.colors.background,
|
|
},
|
|
|
|
// Loading container for initial data loading
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: theme.colors.background,
|
|
},
|
|
|
|
// Loading text styling
|
|
loadingText: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
color: theme.colors.textSecondary,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
},
|
|
|
|
// Scroll view styling
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
|
|
// Scroll content styling
|
|
scrollContent: {
|
|
paddingBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Header section containing dashboard components
|
|
header: {
|
|
paddingHorizontal: theme.spacing.md,
|
|
paddingTop: theme.spacing.md,
|
|
},
|
|
|
|
// Header top section with title
|
|
headerTop: {
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Dashboard title styling
|
|
dashboardTitle: {
|
|
fontSize: theme.typography.fontSize.displayMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
marginTop: theme.spacing.sm,
|
|
},
|
|
|
|
// Dashboard subtitle styling
|
|
dashboardSubtitle: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
// Stats grid container
|
|
statsGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: theme.spacing.sm,
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Individual stats card styling
|
|
statsCard: {
|
|
flex: 1,
|
|
minWidth: '45%',
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.md,
|
|
borderLeftWidth: 0,
|
|
borderLeftColor: 'transparent',
|
|
...theme.shadows.primary,
|
|
},
|
|
|
|
// Stats card title styling
|
|
statsCardTitle: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Stats card value styling
|
|
statsCardValue: {
|
|
fontSize: theme.typography.fontSize.displayMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Stats card subtitle styling
|
|
statsCardSubtitle: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
},
|
|
|
|
// Section container styling
|
|
section: {
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.md,
|
|
marginHorizontal: theme.spacing.md,
|
|
marginBottom: theme.spacing.md,
|
|
...theme.shadows.primary,
|
|
},
|
|
|
|
// Section title styling
|
|
sectionTitle: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Confidence breakdown container
|
|
confidenceContainer: {
|
|
gap: theme.spacing.md,
|
|
},
|
|
|
|
// Confidence item styling
|
|
confidenceItem: {
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.md,
|
|
marginBottom: theme.spacing.sm,
|
|
...theme.shadows.small,
|
|
},
|
|
|
|
// Confidence bar styling
|
|
confidenceBar: {
|
|
height: 12,
|
|
borderRadius: theme.borderRadius.small,
|
|
backgroundColor: theme.colors.primary,
|
|
},
|
|
|
|
// Confidence label styling
|
|
confidenceLabel: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textPrimary,
|
|
flex: 1,
|
|
},
|
|
|
|
// Confidence value styling
|
|
confidenceValue: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
marginTop: theme.spacing.xs,
|
|
textAlign: 'center',
|
|
},
|
|
|
|
// Confidence header styling
|
|
confidenceHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Confidence indicator styling
|
|
confidenceIndicator: {
|
|
width: 16,
|
|
height: 16,
|
|
borderRadius: 8,
|
|
marginRight: theme.spacing.xs,
|
|
},
|
|
|
|
// Confidence percentage styling
|
|
confidencePercentage: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginLeft: 'auto',
|
|
},
|
|
|
|
// Confidence bar container styling
|
|
confidenceBarContainer: {
|
|
width: '100%',
|
|
height: 12,
|
|
backgroundColor: theme.colors.border,
|
|
borderRadius: theme.borderRadius.small,
|
|
marginBottom: theme.spacing.sm,
|
|
overflow: 'hidden',
|
|
},
|
|
|
|
// Urgency container styling
|
|
urgencyContainer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
},
|
|
|
|
// Urgency item styling
|
|
urgencyItem: {
|
|
alignItems: 'center',
|
|
flex: 1,
|
|
},
|
|
|
|
// Urgency indicator styling
|
|
urgencyIndicator: {
|
|
width: 16,
|
|
height: 16,
|
|
borderRadius: 8,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Urgency label styling
|
|
urgencyLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Urgency value styling
|
|
urgencyValue: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Feedback container styling
|
|
feedbackContainer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
// Feedback item styling
|
|
feedbackItem: {
|
|
alignItems: 'center',
|
|
flex: 1,
|
|
},
|
|
|
|
// Feedback indicator styling
|
|
feedbackIndicator: {
|
|
width: 16,
|
|
height: 16,
|
|
borderRadius: 8,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Feedback label styling
|
|
feedbackLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Feedback value styling
|
|
feedbackValue: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Feedback percentage styling
|
|
feedbackPercentage: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
},
|
|
|
|
// Feedback summary styling
|
|
feedbackSummary: {
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
paddingTop: theme.spacing.md,
|
|
},
|
|
|
|
// Feedback summary text styling
|
|
feedbackSummaryText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
textAlign: 'center',
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Feedback metrics container styling
|
|
feedbackMetrics: {
|
|
marginTop: theme.spacing.md,
|
|
paddingTop: theme.spacing.md,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
alignItems: 'center',
|
|
},
|
|
|
|
// Feedback metrics text styling
|
|
feedbackMetricsText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
textAlign: 'center',
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Time container styling
|
|
timeContainer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
},
|
|
|
|
// Time item styling
|
|
timeItem: {
|
|
alignItems: 'center',
|
|
flex: 1,
|
|
},
|
|
|
|
// Time label styling
|
|
timeLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Time value styling
|
|
timeValue: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Confidence summary styling
|
|
confidenceSummary: {
|
|
marginTop: theme.spacing.lg,
|
|
paddingTop: theme.spacing.md,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
alignItems: 'center',
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.md,
|
|
marginHorizontal: -theme.spacing.md,
|
|
},
|
|
|
|
// Summary text styling
|
|
summaryText: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Summary value styling
|
|
summaryValue: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Bottom spacing for tab bar
|
|
bottomSpacing: {
|
|
height: theme.spacing.xl,
|
|
},
|
|
|
|
// Empty state container styling
|
|
emptyStateContainer: {
|
|
alignItems: 'center',
|
|
paddingVertical: theme.spacing.lg,
|
|
},
|
|
|
|
// Empty state text styling
|
|
emptyStateText: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Empty state subtext styling
|
|
emptyStateSubtext: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
textAlign: 'center',
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Empty state info styling
|
|
emptyStateInfo: {
|
|
marginTop: theme.spacing.sm,
|
|
paddingHorizontal: theme.spacing.md,
|
|
},
|
|
|
|
// Empty state info text styling
|
|
emptyStateInfoText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Retry button styling
|
|
retryButton: {
|
|
marginTop: theme.spacing.md,
|
|
paddingVertical: theme.spacing.sm,
|
|
paddingHorizontal: theme.spacing.lg,
|
|
backgroundColor: theme.colors.primary,
|
|
borderRadius: theme.borderRadius.small,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.primary,
|
|
},
|
|
|
|
// Retry button text styling
|
|
retryButtonText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.background,
|
|
},
|
|
|
|
// Error container styling
|
|
errorContainer: {
|
|
alignItems: 'center',
|
|
paddingVertical: theme.spacing.lg,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.medium,
|
|
marginHorizontal: theme.spacing.md,
|
|
marginBottom: theme.spacing.md,
|
|
...theme.shadows.primary,
|
|
},
|
|
|
|
// Error title styling
|
|
errorTitle: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Error message styling
|
|
errorMessage: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
textAlign: 'center',
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Error info styling
|
|
errorInfo: {
|
|
marginTop: theme.spacing.sm,
|
|
paddingHorizontal: theme.spacing.md,
|
|
},
|
|
|
|
// Error info text styling
|
|
errorInfoText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// No data container styling
|
|
noDataContainer: {
|
|
alignItems: 'center',
|
|
paddingVertical: theme.spacing.lg,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.medium,
|
|
marginHorizontal: theme.spacing.md,
|
|
marginBottom: theme.spacing.md,
|
|
...theme.shadows.primary,
|
|
},
|
|
|
|
// No data title styling
|
|
noDataTitle: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// No data message styling
|
|
noDataMessage: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
textAlign: 'center',
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// No data info styling
|
|
noDataInfo: {
|
|
marginTop: theme.spacing.sm,
|
|
paddingHorizontal: theme.spacing.md,
|
|
},
|
|
|
|
// No data info text styling
|
|
noDataInfoText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Network status container styling
|
|
networkStatusContainer: {
|
|
alignItems: 'center',
|
|
paddingVertical: theme.spacing.md,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.small,
|
|
marginHorizontal: theme.spacing.md,
|
|
marginBottom: theme.spacing.md,
|
|
...theme.shadows.small,
|
|
},
|
|
|
|
// Network status text styling
|
|
networkStatusText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.warning,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Network status subtext styling
|
|
networkStatusSubtext: {
|
|
fontSize: theme.typography.fontSize.caption,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.warning,
|
|
textAlign: 'center',
|
|
},
|
|
});
|
|
|
|
/*
|
|
* End of File: DashboardScreen.tsx
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/ |