470 lines
13 KiB
TypeScript
470 lines
13 KiB
TypeScript
/*
|
|
* File: PredictionCard.tsx
|
|
* Description: Prediction card component for displaying AI prediction data and patient information
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Image,
|
|
} from 'react-native';
|
|
import Icon from 'react-native-vector-icons/Feather';
|
|
import { theme } from '../../../theme/theme';
|
|
import type { PredictionData } from '../types/predictions';
|
|
|
|
// ============================================================================
|
|
// INTERFACES
|
|
// ============================================================================
|
|
|
|
interface PredictionCardProps {
|
|
prediction: PredictionData;
|
|
onPress: () => void;
|
|
}
|
|
|
|
// ============================================================================
|
|
// PREDICTION CARD COMPONENT
|
|
// ============================================================================
|
|
|
|
/**
|
|
* PredictionCard Component
|
|
*
|
|
* Purpose: Display AI prediction data with patient information in a card format
|
|
*
|
|
* Features:
|
|
* - Patient basic information
|
|
* - AI prediction results
|
|
* - Confidence scores
|
|
* - Clinical urgency
|
|
* - Feedback information
|
|
* - Modern card design
|
|
*/
|
|
export const PredictionCard: React.FC<PredictionCardProps> = ({
|
|
prediction,
|
|
onPress,
|
|
}) => {
|
|
// ============================================================================
|
|
// UTILITY FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get Urgency Color Configuration
|
|
*
|
|
* Purpose: Get color and icon based on clinical urgency
|
|
*/
|
|
const getUrgencyConfig = (urgency: string) => {
|
|
switch (urgency.toLowerCase()) {
|
|
case 'critical':
|
|
return {
|
|
color: theme.colors.error,
|
|
icon: 'alert-triangle',
|
|
bgColor: '#FFEBEE'
|
|
};
|
|
case 'urgent':
|
|
return {
|
|
color: theme.colors.warning,
|
|
icon: 'clock',
|
|
bgColor: '#FFF3E0'
|
|
};
|
|
case 'non-urgent':
|
|
return {
|
|
color: theme.colors.success,
|
|
icon: 'check-circle',
|
|
bgColor: '#E8F5E8'
|
|
};
|
|
default:
|
|
return {
|
|
color: theme.colors.primary,
|
|
icon: 'info',
|
|
bgColor: '#E3F2FD'
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get Confidence Color
|
|
*
|
|
* Purpose: Get color based on confidence score
|
|
*/
|
|
const getConfidenceColor = (confidence: number) => {
|
|
if (confidence >= 0.8) return theme.colors.success;
|
|
if (confidence >= 0.6) return theme.colors.warning;
|
|
return theme.colors.error;
|
|
};
|
|
|
|
/**
|
|
* Format Date
|
|
*
|
|
* Purpose: Format processed date for display
|
|
*/
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
});
|
|
};
|
|
|
|
// ============================================================================
|
|
// RENDER
|
|
// ============================================================================
|
|
|
|
const urgencyConfig = getUrgencyConfig(prediction.prediction.clinical_urgency);
|
|
const confidenceColor = getConfidenceColor(prediction.prediction.confidence_score);
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={styles.container}
|
|
onPress={onPress}
|
|
activeOpacity={0.7}
|
|
>
|
|
{/* Header Section */}
|
|
<View style={styles.header}>
|
|
<View style={styles.patientInfo}>
|
|
<Text style={styles.patientName} numberOfLines={1}>
|
|
{prediction.patientdetails.Name || 'Unknown Patient'}
|
|
</Text>
|
|
<Text style={styles.patientDetails} numberOfLines={1} ellipsizeMode="head">
|
|
{prediction.patientdetails.PatID}
|
|
</Text>
|
|
<Text style={styles.patientDetailsSecondary}>
|
|
{prediction.patientdetails.PatAge} • {prediction.patientdetails.PatSex}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Urgency Badge */}
|
|
<View style={[styles.urgencyBadge, { backgroundColor: urgencyConfig.bgColor }]}>
|
|
<Icon name={urgencyConfig.icon} size={16} color={urgencyConfig.color} />
|
|
<Text style={[styles.urgencyText, { color: urgencyConfig.color }]}>
|
|
{prediction.prediction.clinical_urgency}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Prediction Results Section */}
|
|
<View style={styles.predictionSection}>
|
|
<View style={styles.predictionHeader}>
|
|
<Icon name="activity" size={20} color={theme.colors.primary} />
|
|
<Text style={styles.predictionTitle}>AI Prediction</Text>
|
|
</View>
|
|
|
|
<View style={styles.predictionDetails}>
|
|
<View style={styles.predictionRow}>
|
|
<Text style={styles.predictionLabel}>Finding:</Text>
|
|
<Text style={styles.predictionValue}>
|
|
{prediction.prediction.label}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.predictionRow}>
|
|
<Text style={styles.predictionLabel}>Type:</Text>
|
|
<Text style={styles.predictionValue}>
|
|
{prediction.prediction.finding_type}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.predictionRow}>
|
|
<Text style={styles.predictionLabel}>Confidence:</Text>
|
|
<View style={styles.confidenceContainer}>
|
|
<Text style={[styles.confidenceValue, { color: confidenceColor }]}>
|
|
{(prediction.prediction.confidence_score * 100).toFixed(1)}%
|
|
</Text>
|
|
<View style={[styles.confidenceBar, { backgroundColor: confidenceColor }]} />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Medical Information Section */}
|
|
<View style={styles.medicalSection}>
|
|
<View style={styles.medicalRow}>
|
|
<View style={styles.medicalItem}>
|
|
<Icon name="home" size={14} color={theme.colors.textSecondary} />
|
|
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
|
|
{prediction.patientdetails.InstName || 'Unknown Institution'}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.medicalItem}>
|
|
<Icon name="activity" size={14} color={theme.colors.textSecondary} />
|
|
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
|
|
{prediction.patientdetails.Modality || 'Unknown Modality'}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.medicalRow}>
|
|
<View style={styles.medicalItem}>
|
|
<Icon name="calendar" size={14} color={theme.colors.textSecondary} />
|
|
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
|
|
{formatDate(prediction.processed_at)}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.medicalItem}>
|
|
<Icon name="layers" size={14} color={theme.colors.textSecondary} />
|
|
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
|
|
{prediction.prediction.processing_info.frame_count} Frames
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Feedback Section */}
|
|
{prediction.has_provided_feedback && (
|
|
<View style={styles.feedbackSection}>
|
|
<View style={styles.feedbackHeader}>
|
|
<Icon name="message-circle" size={16} color={theme.colors.success} />
|
|
<Text style={styles.feedbackTitle}>Feedback Available</Text>
|
|
</View>
|
|
|
|
<View style={styles.feedbackDetails}>
|
|
<Text style={styles.feedbackCount}>
|
|
{prediction.user_feedback_count} feedback(s)
|
|
</Text>
|
|
<Text style={styles.feedbackType}>
|
|
Latest: {prediction.latest_feedback_type}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* Footer */}
|
|
<View style={styles.footer}>
|
|
<Text style={styles.processedText}>
|
|
Processed: {formatDate(prediction.processed_at)}
|
|
</Text>
|
|
|
|
<View style={styles.actionButton}>
|
|
<Icon name="chevron-right" size={16} color={theme.colors.primary} />
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// STYLES
|
|
// ============================================================================
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.large,
|
|
padding: theme.spacing.md,
|
|
marginBottom: theme.spacing.md,
|
|
// Custom shadow properties to ensure visibility
|
|
shadowColor: '#000000',
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 4,
|
|
elevation: 3,
|
|
},
|
|
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
patientInfo: {
|
|
flex: 1,
|
|
marginRight: theme.spacing.sm,
|
|
},
|
|
|
|
patientName: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
patientDetails: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
patientDetailsSecondary: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
urgencyBadge: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: theme.spacing.sm,
|
|
paddingVertical: theme.spacing.xs,
|
|
borderRadius: theme.borderRadius.small,
|
|
gap: theme.spacing.xs,
|
|
},
|
|
|
|
urgencyText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
textTransform: 'capitalize',
|
|
},
|
|
|
|
predictionSection: {
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
predictionHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: theme.spacing.sm,
|
|
gap: theme.spacing.sm,
|
|
},
|
|
|
|
predictionTitle: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
predictionDetails: {
|
|
gap: theme.spacing.xs,
|
|
},
|
|
|
|
predictionRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
},
|
|
|
|
predictionLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
predictionValue: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
textTransform: 'capitalize',
|
|
},
|
|
|
|
confidenceContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: theme.spacing.xs,
|
|
},
|
|
|
|
confidenceValue: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
},
|
|
|
|
confidenceBar: {
|
|
width: 20,
|
|
height: 4,
|
|
borderRadius: 2,
|
|
},
|
|
|
|
medicalSection: {
|
|
marginBottom: theme.spacing.md,
|
|
paddingTop: theme.spacing.md,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
},
|
|
|
|
medicalRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
medicalItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: theme.spacing.md,
|
|
flex: 1,
|
|
maxWidth: '48%', // Prevent items from taking too much space
|
|
},
|
|
|
|
medicalText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
flex: 1, // Allow text to take remaining space
|
|
height: 20, // Uniform height for all medical text
|
|
lineHeight: 20, // Ensure text is vertically centered
|
|
},
|
|
|
|
feedbackSection: {
|
|
marginBottom: theme.spacing.md,
|
|
paddingTop: theme.spacing.md,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
},
|
|
|
|
feedbackHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: theme.spacing.xs,
|
|
gap: theme.spacing.xs,
|
|
},
|
|
|
|
feedbackTitle: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.success,
|
|
},
|
|
|
|
feedbackDetails: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
},
|
|
|
|
feedbackCount: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
feedbackType: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textPrimary,
|
|
textTransform: 'capitalize',
|
|
},
|
|
|
|
footer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingTop: theme.spacing.md,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
},
|
|
|
|
processedText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textMuted,
|
|
},
|
|
|
|
actionButton: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 16,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
});
|
|
|
|
/*
|
|
* End of File: PredictionCard.tsx
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|