NeoScan_Radiologist/app/modules/AIPrediction/components/StatsOverview.tsx

455 lines
13 KiB
TypeScript

/*
* File: StatsOverview.tsx
* Description: Statistics overview component for AI predictions dashboard
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import type { AIPredictionStats } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface StatsOverviewProps {
stats: AIPredictionStats;
onStatsPress?: (statType: string) => void;
isLoading?: boolean;
style?: any;
}
interface StatCardProps {
title: string;
value: string | number;
subtitle?: string;
iconName: string;
color: string;
onPress?: () => void;
trend?: number;
isPercentage?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const CARD_WIDTH = (width - 48) / 2; // Two cards per row with margins
// ============================================================================
// STAT CARD COMPONENT
// ============================================================================
/**
* StatCard Component
*
* Purpose: Individual statistics card
*/
const StatCard: React.FC<StatCardProps> = ({
title,
value,
subtitle,
iconName,
color,
onPress,
trend,
isPercentage = false,
}) => {
const displayValue = typeof value === 'number'
? isPercentage
? `${Math.round(value * 100)}%`
: value.toLocaleString()
: value;
return (
<TouchableOpacity
style={[styles.statCard, { borderLeftColor: color }]}
onPress={onPress}
disabled={!onPress}
accessibilityRole="button"
accessibilityLabel={`${title}: ${displayValue}${subtitle ? `, ${subtitle}` : ''}`}
>
{/* Card Header */}
<View style={styles.cardHeader}>
<View style={{flexDirection: 'row', alignItems: 'center', gap: theme.spacing.sm}}>
<View style={[styles.iconContainer, { backgroundColor: color + '20' }]}>
<Icon name={iconName} size={20} color={color} />
</View>
<Text style={styles.statValue}>{displayValue}</Text>
</View>
{trend !== undefined && (
<View style={styles.trendContainer}>
<Icon
name={trend >= 0 ? 'trending-up' : 'trending-down'}
size={14}
color={trend >= 0 ? theme.colors.success : theme.colors.error}
/>
<Text style={[
styles.trendText,
{ color: trend >= 0 ? theme.colors.success : theme.colors.error }
]}>
{Math.abs(trend).toFixed(1)}%
</Text>
</View>
)}
</View>
{/* Card Content */}
<View style={styles.cardContent}>
<Text style={styles.statTitle} numberOfLines={2}>{title}</Text>
{subtitle && (
<Text style={styles.statSubtitle} numberOfLines={1}>{subtitle}</Text>
)}
</View>
</TouchableOpacity>
);
};
// ============================================================================
// STATS OVERVIEW COMPONENT
// ============================================================================
/**
* StatsOverview Component
*
* Purpose: Display comprehensive AI predictions statistics
*
* Features:
* - Total cases overview
* - Critical and urgent case counts
* - Review progress tracking
* - Average confidence metrics
* - Trend indicators
* - Interactive stat cards
* - Responsive grid layout
* - Modern card design
* - Accessibility support
*/
const StatsOverview: React.FC<StatsOverviewProps> = ({
stats,
onStatsPress,
isLoading = false,
style,
}) => {
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Stat Press
*
* Purpose: Handle statistics card press
*/
const handleStatPress = (statType: string) => {
if (onStatsPress) {
onStatsPress(statType);
}
};
// ============================================================================
// RENDER
// ============================================================================
if (isLoading) {
return (
<View style={[styles.container, style]}>
<View style={styles.header}>
<Text style={styles.sectionTitle}>AI Predictions Overview</Text>
</View>
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading statistics...</Text>
</View>
</View>
);
}
return (
<View style={[styles.container, style]}>
{/* Section Header */}
<View style={styles.header}>
<Text style={styles.sectionTitle}>AI Predictions Overview</Text>
<TouchableOpacity
style={styles.viewAllButton}
onPress={() => handleStatPress('all')}
accessibilityRole="button"
accessibilityLabel="View all statistics"
>
<Text style={styles.viewAllText}>View All</Text>
<Icon name="arrow-right" size={16} color={theme.colors.primary} />
</TouchableOpacity>
</View>
{/* Statistics Grid */}
<View style={styles.statsGrid}>
{/* Total Cases */}
<StatCard
title="Total Cases"
value={stats.totalCases}
subtitle="All predictions"
iconName="database"
color={theme.colors.primary}
onPress={() => handleStatPress('total')}
/>
{/* Critical Cases */}
<StatCard
title="Critical Cases"
value={stats.criticalCases}
subtitle="Require attention"
iconName="alert-triangle"
color={theme.colors.error}
onPress={() => handleStatPress('critical')}
/>
{/* Urgent Cases */}
<StatCard
title="Urgent Cases"
value={stats.urgentCases}
subtitle="High priority"
iconName="clock"
color={theme.colors.warning}
onPress={() => handleStatPress('urgent')}
/>
{/* Reviewed Cases */}
<StatCard
title="Reviewed Cases"
value={stats.reviewedCases}
subtitle="Completed reviews"
iconName="check-circle"
color={theme.colors.success}
onPress={() => handleStatPress('reviewed')}
/>
{/* Pending Cases */}
<StatCard
title="Pending Reviews"
value={stats.pendingCases}
subtitle="Awaiting review"
iconName="eye"
color={theme.colors.info}
onPress={() => handleStatPress('pending')}
/>
{/* Average Confidence */}
<StatCard
title="Avg Confidence"
value={stats.averageConfidence}
subtitle="AI accuracy"
iconName="trending-up"
color={theme.colors.primary}
onPress={() => handleStatPress('confidence')}
isPercentage={true}
/>
{/* Today's Cases */}
<StatCard
title="Today's Cases"
value={stats.todaysCases}
subtitle="New predictions"
iconName="calendar"
color={theme.colors.info}
onPress={() => handleStatPress('today')}
/>
{/* Weekly Trend */}
<StatCard
title="Weekly Trend"
value={`${stats.weeklyTrend >= 0 ? '+' : ''}${stats.weeklyTrend.toFixed(1)}%`}
subtitle="vs last week"
iconName={stats.weeklyTrend >= 0 ? 'trending-up' : 'trending-down'}
color={stats.weeklyTrend >= 0 ? theme.colors.success : theme.colors.error}
onPress={() => handleStatPress('trend')}
trend={stats.weeklyTrend}
/>
</View>
{/* Summary Section */}
<View style={styles.summarySection}>
<View style={styles.summaryCard}>
<View style={styles.summaryHeader}>
<Icon name="activity" size={20} color={theme.colors.primary} />
<Text style={styles.summaryTitle}>Quick Insights</Text>
</View>
<View style={styles.summaryContent}>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Review Progress:</Text>
<Text style={styles.summaryValue}>
{Math.round((stats.reviewedCases / stats.totalCases) * 100)}%
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Critical Rate:</Text>
<Text style={styles.summaryValue}>
{Math.round((stats.criticalCases / stats.totalCases) * 100)}%
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Daily Average:</Text>
<Text style={styles.summaryValue}>
{Math.round(stats.totalCases / 7)} cases
</Text>
</View>
</View>
</View>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
paddingVertical: theme.spacing.lg,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.lg,
},
sectionTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
viewAllButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
viewAllText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
loadingContainer: {
paddingVertical: theme.spacing.xxl,
alignItems: 'center',
},
loadingText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textMuted,
},
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: theme.spacing.md,
gap: theme.spacing.md,
},
statCard: {
width: CARD_WIDTH,
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
borderLeftWidth: 4,
padding: theme.spacing.md,
...theme.shadows.medium,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing.sm,
},
iconContainer: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
},
trendContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
trendText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
},
cardContent: {
gap: theme.spacing.xs,
},
statValue: {
fontSize: theme.typography.fontSize.displayMedium,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
statTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
statSubtitle: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textMuted,
},
summarySection: {
paddingHorizontal: theme.spacing.md,
marginTop: theme.spacing.lg,
},
summaryCard: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.lg,
...theme.shadows.small,
},
summaryHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
marginBottom: theme.spacing.md,
},
summaryTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
summaryContent: {
gap: theme.spacing.sm,
},
summaryItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
summaryLabel: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
},
summaryValue: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
});
export default StatsOverview;
/*
* End of File: StatsOverview.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/