NeoScan_Physician/app/modules/PatientCare/components/PatientCard.tsx
2025-08-22 00:24:24 +05:30

599 lines
16 KiB
TypeScript

/*
* File: PatientCard.tsx
* Description: Enhanced patient card component for displaying DICOM medical case information
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
} from 'react-native';
import { theme } from '../../../theme/theme';
import Icon from 'react-native-vector-icons/Feather';
import { PatientData } from '../redux/patientCareSlice';
// ============================================================================
// INTERFACES
// ============================================================================
interface PatientCardProps {
patient: PatientData;
onPress: () => void;
onEmergencyPress?: () => void;
}
// ============================================================================
// PATIENT CARD COMPONENT
// ============================================================================
/**
* PatientCard Component
*
* Purpose: Display DICOM medical case information in a modern, enhanced card format
*
* Features:
* - Enhanced visual hierarchy with modern design
* - Improved status indicators and color coding
* - Better spacing and typography
* - Enhanced shadows and elevation
* - More intuitive information layout
* - Emergency alert for critical cases
* - Modern ER-focused design with better UX
*/
const PatientCard: React.FC<PatientCardProps> = ({
patient,
onPress,
onEmergencyPress,
}) => {
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Get Status Color Configuration
*
* Purpose: Get enhanced color and icon based on processing status
*
* @param status - Processing status
* @returns Enhanced color configuration object
*/
const getStatusConfig = (status: string) => {
switch (status.toLowerCase()) {
case 'processed':
return {
color: '#10B981',
icon: 'check-circle',
bgColor: '#ECFDF5',
borderColor: '#D1FAE5'
};
case 'pending':
return {
color: '#F59E0B',
icon: 'clock',
bgColor: '#FFFBEB',
borderColor: '#FEF3C7'
};
case 'error':
return {
color: '#EF4444',
icon: 'alert-triangle',
bgColor: '#FEF2F2',
borderColor: '#FECACA'
};
default:
return {
color: '#3B82F6',
icon: 'info',
bgColor: '#EFF6FF',
borderColor: '#DBEAFE'
};
}
};
/**
* Get Modality Color
*
* Purpose: Get enhanced color based on imaging modality
*
* @param modality - Imaging modality
* @returns Enhanced color code
*/
const getModalityColor = (modality: string) => {
switch (modality.toUpperCase()) {
case 'CT':
return '#3B82F6';
case 'MR':
return '#8B5CF6';
case 'DX':
return '#10B981';
case 'DICOM':
return '#EF4444';
default:
return theme.colors.textSecondary;
}
};
/**
* Format Date
*
* Purpose: Format date string to readable format
*
* @param dateString - ISO date string
* @returns Formatted date string
*/
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
/**
* Get Time Since Processed
*
* Purpose: Get human-readable time since last processed
*
* @param dateString - ISO date string
* @returns Formatted time string
*/
const getTimeSinceProcessed = (dateString: string) => {
const now = new Date();
const processed = new Date(dateString);
const diffInMinutes = Math.floor((now.getTime() - processed.getTime()) / (1000 * 60));
if (diffInMinutes < 1) return 'Just now';
if (diffInMinutes < 60) return `${diffInMinutes}m ago`;
if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)}h ago`;
return `${Math.floor(diffInMinutes / 1440)}d ago`;
};
// ============================================================================
// DATA EXTRACTION
// ============================================================================
const patientInfo = patient.patient_info;
const seriesCount = patient.series_summary.length;
const statusConfig = getStatusConfig(patientInfo.status);
const isCritical = patientInfo.report_status === 'Critical' || patientInfo.status === 'Error';
// ============================================================================
// RENDER HELPERS
// ============================================================================
/**
* Render Enhanced Status Badge
*
* Purpose: Render improved processing status indicator badge
*/
const renderStatusBadge = () => (
<View style={[styles.statusBadge, {
backgroundColor: statusConfig.bgColor,
borderColor: statusConfig.borderColor
}]}>
<Icon name={statusConfig.icon} size={14} color={statusConfig.color} />
<Text style={[styles.statusText, { color: statusConfig.color }]}>
{patientInfo.status}
</Text>
</View>
);
/**
* Render Enhanced Emergency Button
*
* Purpose: Render improved emergency alert button for critical cases
*/
const renderEmergencyButton = () => {
if (!isCritical) {
return null;
}
return (
<TouchableOpacity
style={styles.emergencyButton}
onPress={onEmergencyPress}
activeOpacity={0.8}
>
<Icon name="alert-triangle" size={16} color={theme.colors.background} />
<Text style={styles.emergencyButtonText}>ALERT</Text>
</TouchableOpacity>
);
};
/**
* Render Enhanced Modality Badge
*
* Purpose: Render improved modality indicator
*/
const renderModalityBadge = () => (
<View style={[styles.modalityBadge, {
backgroundColor: getModalityColor(patientInfo.modality) + '20',
borderColor: getModalityColor(patientInfo.modality)
}]}>
<Text style={[styles.modalityText, {
color: getModalityColor(patientInfo.modality)
}]}>
{patientInfo.modality || 'N/A'}
</Text>
</View>
);
// ============================================================================
// MAIN RENDER
// ============================================================================
return (
<TouchableOpacity
style={[
styles.container,
isCritical && styles.containerCritical,
{ borderLeftColor: statusConfig.color }
]}
onPress={onPress}
activeOpacity={0.8}
>
{/* Enhanced Header Section */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<View style={styles.patientNameRow}>
<Text style={styles.patientName}>
{patientInfo.name || 'Unknown Patient'}
</Text>
{renderModalityBadge()}
</View>
<Text style={styles.patientInfo}>
ID: {patient.patid} {patientInfo.age || 'N/A'}y {patientInfo.sex || 'N/A'}
</Text>
</View>
<View style={styles.headerRight}>
{renderStatusBadge()}
{renderEmergencyButton()}
</View>
</View>
{/* Enhanced Medical Information Section */}
<View style={styles.medicalSection}>
<View style={styles.infoGrid}>
<View style={styles.infoCard}>
<Icon name="file-text" size={16} color={theme.colors.primary} />
<Text style={styles.infoValue}>
{patient.total_files_processed || 0}
</Text>
<Text style={styles.infoLabel}>Files</Text>
</View>
<View style={styles.infoCard}>
<Icon name="layers" size={16} color={theme.colors.warning} />
<Text style={styles.infoValue}>
{seriesCount}
</Text>
<Text style={styles.infoLabel}>Series</Text>
</View>
<View style={styles.infoCard}>
<Icon name="clipboard" size={16} color={
patientInfo.report_status === 'Available' ? theme.colors.success : theme.colors.warning
} />
<Text style={[styles.infoValue, {
color: patientInfo.report_status === 'Available' ? theme.colors.success : theme.colors.warning
}]}>
{patientInfo.report_status || 'Pending'}
</Text>
<Text style={styles.infoLabel}>Report</Text>
</View>
</View>
{/* Enhanced Institution Row */}
<View style={styles.institutionRow}>
<View style={styles.institutionIcon}>
<Icon name="home" size={16} color={theme.colors.primary} />
</View>
<Text style={styles.institutionText}>
{patientInfo.institution || 'Unknown Institution'}
</Text>
</View>
</View>
{/* Enhanced Series Information */}
<View style={styles.seriesSection}>
<View style={styles.seriesHeader}>
<Icon name="layers" size={16} color={theme.colors.textSecondary} />
<Text style={styles.seriesLabel}>Series Details</Text>
</View>
<View style={styles.seriesInfo}>
<Text style={styles.seriesText}>
{seriesCount} Series Available
</Text>
<Text style={styles.frameText}>
{patientInfo.frame_count || 0} Total Frames
</Text>
</View>
</View>
{/* Enhanced Footer */}
<View style={styles.footer}>
<View style={styles.footerLeft}>
<Text style={styles.dateText}>
{formatDate(patientInfo.date)}
</Text>
<Text style={styles.processedText}>
Processed {getTimeSinceProcessed(patient.last_processed_at)}
</Text>
</View>
<View style={styles.footerRight}>
<Text style={styles.caseId}>Case #{patient.patid}</Text>
<View style={styles.chevronContainer}>
<Icon name="chevron-right" size={18} color={theme.colors.primary} />
</View>
</View>
</View>
</TouchableOpacity>
);
};
// ============================================================================
// ENHANCED STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
borderRadius: 16,
padding: theme.spacing.md,
marginHorizontal: theme.spacing.md,
marginVertical: theme.spacing.xs,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06,
shadowRadius: 8,
elevation: 3,
borderWidth: 1,
borderColor: theme.colors.border,
borderLeftWidth: 4,
},
containerCritical: {
borderColor: theme.colors.error,
borderWidth: 2,
backgroundColor: '#FEF2F2',
shadowColor: theme.colors.error,
shadowOpacity: 0.15,
},
// Enhanced Header Section
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.sm,
},
headerLeft: {
flex: 1,
marginRight: theme.spacing.md,
},
headerRight: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
patientNameRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.xs,
gap: theme.spacing.xs,
},
patientName: {
fontSize: 20,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
flex: 1,
},
patientInfo: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium,
lineHeight: 20,
},
// Enhanced Status Badge
statusBadge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
borderWidth: 1.5,
gap: theme.spacing.xs,
},
statusText: {
fontSize: 11,
fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// Enhanced Emergency Button
emergencyButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.error,
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
gap: theme.spacing.xs,
shadowColor: theme.colors.error,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 3,
},
emergencyButtonText: {
fontSize: 11,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.background,
letterSpacing: 0.5,
},
// Enhanced Modality Badge
modalityBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
borderWidth: 1.5,
},
modalityText: {
fontSize: 10,
fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// Enhanced Medical Section
medicalSection: {
marginBottom: theme.spacing.sm,
paddingBottom: theme.spacing.sm,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
infoGrid: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: theme.spacing.sm,
gap: theme.spacing.sm,
},
infoCard: {
flex: 1,
alignItems: 'center',
paddingVertical: theme.spacing.xs,
backgroundColor: theme.colors.backgroundAlt,
borderRadius: 12,
paddingHorizontal: theme.spacing.sm,
gap: theme.spacing.xs,
},
infoLabel: {
fontSize: 10,
color: theme.colors.textMuted,
textTransform: 'uppercase',
fontFamily: theme.typography.fontFamily.medium,
letterSpacing: 0.5,
textAlign: 'center',
},
infoValue: {
fontSize: 14,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
textAlign: 'center',
},
// Enhanced Institution Row
institutionRow: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.backgroundAlt,
padding: theme.spacing.sm,
borderRadius: 12,
gap: theme.spacing.sm,
},
institutionIcon: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: theme.colors.primary + '20',
justifyContent: 'center',
alignItems: 'center',
},
institutionText: {
fontSize: 14,
color: theme.colors.textSecondary,
flex: 1,
fontFamily: theme.typography.fontFamily.medium,
},
// Enhanced Series Section
seriesSection: {
backgroundColor: theme.colors.backgroundAlt,
borderRadius: 12,
padding: theme.spacing.sm,
marginBottom: theme.spacing.sm,
},
seriesHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.sm,
gap: theme.spacing.xs,
},
seriesLabel: {
fontSize: 13,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
seriesInfo: {
gap: theme.spacing.xs,
},
seriesText: {
fontSize: 15,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.medium,
},
frameText: {
fontSize: 13,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
},
// Enhanced Footer Section
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: theme.spacing.sm,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
footerLeft: {
flex: 1,
},
dateText: {
fontSize: 13,
color: theme.colors.textMuted,
fontFamily: theme.typography.fontFamily.medium,
marginBottom: 2,
},
processedText: {
fontSize: 12,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
},
footerRight: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
caseId: {
fontSize: 12,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium,
},
chevronContainer: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: theme.colors.primary + '20',
justifyContent: 'center',
alignItems: 'center',
},
});
export default PatientCard;
/*
* End of File: PatientCard.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/