1295 lines
39 KiB
TypeScript
1295 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 } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
TouchableOpacity,
|
|
RefreshControl,
|
|
FlatList,
|
|
Dimensions,
|
|
Alert,
|
|
} from 'react-native';
|
|
import Icon from 'react-native-vector-icons/Feather';
|
|
import { theme } from '../../../theme/theme';
|
|
import { DashboardHeader } from '../components/DashboardHeader';
|
|
import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview';
|
|
import { FeedbackAnalysisPieChart } from '../components/FeedbackAnalysisPieChart';
|
|
import { PredictionsList } from '../components/PredictionsList';
|
|
import { useAIDashboard } from '../hooks/useAIDashboard';
|
|
import { selectUserDisplayName, selectUserFirstName } from '../../Auth/redux/authSelectors';
|
|
import { useAppSelector } from '../../../store/hooks';
|
|
import { CompositeNavigationProp } from '@react-navigation/native';
|
|
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
|
|
import { StackNavigationProp } from '@react-navigation/stack';
|
|
import { MainTabParamList } from '../../../navigation/navigationTypes';
|
|
import { PatientCareStackParamList } from '../../PatientCare/navigation/navigationTypes';
|
|
|
|
/**
|
|
* DashboardScreenProps Interface
|
|
*
|
|
* Purpose: Defines the props required by the DashboardScreen component
|
|
*
|
|
* Props:
|
|
* - navigation: Composite navigation object for tab and stack navigation
|
|
*/
|
|
type DashboardScreenNavigationProp = CompositeNavigationProp<
|
|
BottomTabNavigationProp<MainTabParamList, 'Dashboard'>,
|
|
StackNavigationProp<PatientCareStackParamList>
|
|
>;
|
|
|
|
interface DashboardScreenProps {
|
|
navigation: DashboardScreenNavigationProp;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @param iconName - Icon name for the stats card
|
|
* @returns Statistics card component
|
|
*/
|
|
const renderStatsCard = (title: string, value: string | number, subtitle?: string, color?: string, iconName?: string) => (
|
|
<View style={[styles.statsCard, color && { borderLeftColor: color, borderLeftWidth: 4 }]}>
|
|
{/* Icon and Title Row */}
|
|
<View style={styles.statsCardHeader}>
|
|
{iconName && (
|
|
<View style={[styles.statsCardIcon, { backgroundColor: color ? color + '20' : theme.colors.backgroundAccent }]}>
|
|
<Icon name={iconName} size={20} color={color || theme.colors.primary} />
|
|
</View>
|
|
)}
|
|
<Text style={styles.statsCardTitle}>{title}</Text>
|
|
</View>
|
|
<Text style={[styles.statsCardValue, color && { color }]}>{value}</Text>
|
|
{subtitle && <Text style={styles.statsCardSubtitle}>{subtitle}</Text>}
|
|
</View>
|
|
);
|
|
|
|
/**
|
|
* renderConfidenceBreakdown Function
|
|
*
|
|
* Purpose: Render confidence score breakdown section
|
|
*/
|
|
|
|
|
|
/**
|
|
* renderUrgencyBreakdown Function
|
|
*
|
|
* Purpose: Render urgency level breakdown section with animated colored circles
|
|
*/
|
|
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}>
|
|
{/* Critical Cases Circle */}
|
|
<View style={styles.urgencyCircleItem}>
|
|
<View
|
|
style={[
|
|
styles.circleContainer,
|
|
{ borderColor: theme.colors.error }
|
|
]}
|
|
>
|
|
<Text style={styles.circleValue}>{critical}</Text>
|
|
</View>
|
|
<Text style={styles.urgencyLabel}>Critical</Text>
|
|
</View>
|
|
|
|
{/* Urgent Cases Circle */}
|
|
<View style={styles.urgencyCircleItem}>
|
|
<View
|
|
style={[
|
|
styles.circleContainer,
|
|
{ borderColor: theme.colors.warning }
|
|
]}
|
|
>
|
|
<Text style={styles.circleValue}>{urgent}</Text>
|
|
</View>
|
|
<Text style={styles.urgencyLabel}>Urgent</Text>
|
|
</View>
|
|
|
|
{/* Routine Cases Circle */}
|
|
<View style={styles.urgencyCircleItem}>
|
|
<View
|
|
style={[
|
|
styles.circleContainer,
|
|
{ borderColor: theme.colors.success }
|
|
]}
|
|
>
|
|
<Text style={styles.circleValue}>{routine}</Text>
|
|
</View>
|
|
<Text style={styles.urgencyLabel}>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: {dashboardData.data.average_feedback_per_prediction}
|
|
</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,
|
|
'activity'
|
|
)}
|
|
{renderStatsCard(
|
|
'Total Patients',
|
|
dashboardData?.data.total_patients || 0,
|
|
'Unique patients',
|
|
theme.colors.info,
|
|
'users'
|
|
)}
|
|
{renderStatsCard(
|
|
'Feedback Rate',
|
|
`${dashboardData?.data.feedback_rate_percentage || 0}%`,
|
|
'User feedback coverage',
|
|
theme.colors.success,
|
|
'message-circle'
|
|
)}
|
|
{renderStatsCard(
|
|
'Avg Confidence',
|
|
(dashboardData?.data.average_confidence_score || 0).toFixed(2),
|
|
'AI prediction confidence',
|
|
theme.colors.warning,
|
|
'trending-up'
|
|
)}
|
|
</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()}
|
|
|
|
|
|
|
|
{/* Urgency level breakdown */}
|
|
{renderUrgencyBreakdown()}
|
|
|
|
{/* Feedback analysis */}
|
|
{renderFeedbackAnalysis()}
|
|
|
|
{/* Time-based analysis */}
|
|
{renderTimeAnalysis()}
|
|
|
|
{/* AI Predictions List - Moved to main ScrollView */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>AI Predictions</Text>
|
|
<Text style={styles.sectionSubtitle}>
|
|
Review AI predictions with and without user feedback
|
|
</Text>
|
|
</View>
|
|
|
|
{/* PredictionsList rendered directly */}
|
|
<View style={styles.predictionsContainer}>
|
|
<PredictionsList
|
|
onPredictionPress={(prediction) => {
|
|
try {
|
|
// Navigate to FeedbackDetailScreen with required parameters
|
|
|
|
|
|
navigation.navigate('Patients', {
|
|
screen: 'FeedbackDetail',
|
|
params: {
|
|
patientId: prediction.patid,
|
|
patientName: prediction.patientdetails.Name || 'Unknown Patient',
|
|
seriesNumber: prediction.prediction.processing_info.filename || 'Unknown Series',
|
|
seriesData: {
|
|
series_num: prediction.prediction.processing_info.filename || 'Unknown Series',
|
|
series_description: prediction.prediction.finding_type || 'AI Analysis',
|
|
total_images: prediction.prediction.processing_info.frame_count || 0,
|
|
png_preview: prediction.preview || '',
|
|
modality: prediction.patientdetails.Modality || 'Unknown'
|
|
},
|
|
patientData: {
|
|
patid: prediction.patid,
|
|
hospital_id: prediction.hospital_id,
|
|
patient_info: {
|
|
name: prediction.patientdetails.Name || 'Unknown Patient',
|
|
age: prediction.patientdetails.PatAge || 'Unknown',
|
|
sex: prediction.patientdetails.PatSex || 'Unknown',
|
|
date: prediction.patientdetails.Date || 'Unknown',
|
|
institution: prediction.patientdetails.InstName || 'Unknown Institution',
|
|
modality: prediction.patientdetails.Modality || 'Unknown Modality',
|
|
status: prediction.patientdetails.Status || 'Unknown',
|
|
report_status: prediction.patientdetails.ReportStatus || 'Unknown',
|
|
file_name: prediction.prediction.processing_info.filename || 'Unknown',
|
|
file_type: prediction.prediction.processing_info.file_type || 'Unknown',
|
|
frame_count: prediction.prediction.processing_info.frame_count || 0
|
|
},
|
|
series_summary: [{
|
|
series_num: prediction.prediction.processing_info.filename || 'Unknown Series',
|
|
series_description: prediction.prediction.finding_type || 'AI Analysis',
|
|
total_images: prediction.prediction.processing_info.frame_count || 0,
|
|
png_preview: prediction.preview || '',
|
|
modality: prediction.patientdetails.Modality || 'Unknown'
|
|
}],
|
|
processing_metadata: prediction.processing_metadata,
|
|
total_predictions: 1,
|
|
first_processed_at: prediction.processed_at,
|
|
last_processed_at: prediction.processed_at
|
|
},
|
|
feedbackData: prediction.feedbacks || [],
|
|
onFeedbackSubmitted: () => {
|
|
// Refresh dashboard data when feedback is submitted
|
|
console.log('Feedback submitted, refreshing dashboard...');
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('Navigation successful to FeedbackDetailScreen');
|
|
} catch (error) {
|
|
console.error('Navigation error:', error);
|
|
// Fallback: show alert or handle error gracefully
|
|
Alert.alert(
|
|
'Navigation Error',
|
|
'Unable to open feedback details. Please try again.',
|
|
[{ text: 'OK' }]
|
|
);
|
|
}
|
|
}}
|
|
/>
|
|
</View>
|
|
|
|
{/* 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 header styling (icon + title row)
|
|
statsCardHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: theme.spacing.xs,
|
|
gap: theme.spacing.sm,
|
|
},
|
|
|
|
// Stats card icon styling
|
|
statsCardIcon: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: theme.borderRadius.small,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
|
|
// Stats card title styling
|
|
statsCardTitle: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
flex: 1,
|
|
},
|
|
|
|
// 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,
|
|
},
|
|
|
|
// Section subtitle styling
|
|
sectionSubtitle: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
// Predictions container styling
|
|
predictionsContainer: {
|
|
height: 500, // Increased height for better FlatList performance
|
|
borderRadius: theme.borderRadius.medium,
|
|
overflow: 'hidden',
|
|
marginHorizontal: theme.spacing.md,
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
// 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',
|
|
alignItems: 'flex-start',
|
|
paddingHorizontal: theme.spacing.sm,
|
|
},
|
|
|
|
// Circle Container styling
|
|
circleContainer: {
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 40,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
borderWidth: 8,
|
|
backgroundColor: theme.colors.background,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 8,
|
|
elevation: 8,
|
|
},
|
|
|
|
// Circle Value styling
|
|
circleValue: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Urgency Label styling
|
|
urgencyLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
marginTop: theme.spacing.sm,
|
|
textAlign: 'center',
|
|
},
|
|
|
|
// Urgency Circle Item styling
|
|
urgencyCircleItem: {
|
|
alignItems: 'center',
|
|
flex: 1,
|
|
},
|
|
|
|
// 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.
|
|
*/ |