/* * File: PatientDetailsScreen.tsx * Description: Comprehensive patient details screen with DICOM image viewer and AI analysis * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. * * Features: * - Patient demographics and medical information * - Merged AI Analysis tab showing DICOM images alongside AI predictions * - Processing history and timeline * - Responsive design for different screen sizes * - Emergency actions for critical cases * - Clinical feedback system for AI predictions and DICOM images */ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, Dimensions, Image, FlatList, RefreshControl, TextInput, } from 'react-native'; import { theme } from '../../../theme/theme'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import Icon from 'react-native-vector-icons/Feather'; import { SafeAreaView } from 'react-native-safe-area-context'; import { DicomViewerModal } from '../../../shared/components'; // Import types and API import { patientAPI } from '../services/patientAPI'; import { selectUser } from '../../Auth/redux/authSelectors'; import { API_CONFIG } from '../../../shared/utils'; import { PatientDetailsScreenProps } from '../navigation/navigationTypes'; // Get screen dimensions const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); // ============================================================================ // INTERFACES // ============================================================================ interface PatientInfo { name: string; age: string; sex: string; date: string; institution: string; modality: string; status: string; report_status: string; file_name: string; file_type: string; frame_count: number; } interface SeriesSummary { series_num: string; series_description: string; total_images: number; png_preview: string; modality: string; } interface Prediction { id: number; file_path: string; prediction: { label: string; finding_type: string; clinical_urgency: string; confidence_score: number; detailed_results: any; finding_category: string; primary_severity: string; anatomical_location: string; }; processed_at: string; preview: string; } interface PatientData { patid: string; hospital_id: string; patient_info: PatientInfo; series_summary: SeriesSummary[]; processing_metadata: any; total_predictions: number; first_processed_at: string; last_processed_at: string; predictions_by_series: { [key: string]: Prediction[] }; feedback_by_series: { [key: string]: any[] }; // Add feedback data by series } // ============================================================================ // PATIENT DETAILS SCREEN COMPONENT // ============================================================================ /** * PatientDetailsScreen Component * * Purpose: Comprehensive patient details display with DICOM image viewer * * Features: * - Full patient demographic information * - Medical case details and status * - DICOM series information * - AI predictions and findings * - Image gallery with thumbnail previews * - Real-time data updates from API * - Emergency actions for critical cases * - Medical history and notes * - Modern healthcare-focused UI design * - Responsive layout for different screen sizes */ const PatientDetailsScreen: React.FC = ({ navigation, route }) => { // ============================================================================ // STATE MANAGEMENT // ============================================================================ const dispatch = useAppDispatch(); // Route parameters const { patientId, patientName } = route.params; // Redux state const user = useAppSelector(selectUser); // Local state const [patientData, setPatientData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const [error, setError] = useState(null); const [selectedImageIndex, setSelectedImageIndex] = useState(0); const [showFullImage, setShowFullImage] = useState(false); const [activeTab, setActiveTab] = useState<'overview' | 'aiAnalysis' | 'feedbacks'>('overview'); // DICOM Modal state const [dicomModalVisible, setDicomModalVisible] = useState(false); const [selectedDicomData, setSelectedDicomData] = useState<{ dicomUrl: string; seriesData: SeriesSummary; prediction?: Prediction; } | null>(null); // Additional Analysis Modal state const [analysisModalVisible, setAnalysisModalVisible] = useState(false); const [selectedSeriesForAnalysis, setSelectedSeriesForAnalysis] = useState<{ series: SeriesSummary; predictions: Prediction[]; } | null>(null); // ============================================================================ // DATA FETCHING // ============================================================================ /** * Fetch Patient Data * * Purpose: Fetch patient details from API */ const fetchPatientData = useCallback(async () => { if (!user?.access_token) { setError('Authentication token not available'); setIsLoading(false); return; } try { setIsLoading(true); setError(null); const response: any = await patientAPI.getPatientDetailsById(patientId, user.access_token); if (response.ok && response.data && response.data.data ) { setPatientData(response.data.data as PatientData); } else { setError(response.problem || 'Failed to fetch patient data'); } } catch (err: any) { setError(err.message || 'An error occurred while fetching patient data'); } finally { setIsLoading(false); } }, [patientId, user?.access_token]); // ============================================================================ // EFFECTS // ============================================================================ /** * Component Mount Effect * * Purpose: Initialize screen and fetch patient data */ useEffect(() => { fetchPatientData(); }, [fetchPatientData]); /** * Route Parameters Change Effect * * Purpose: Fetch fresh patient data whenever route parameters change * This ensures that when navigating from different screens with different patient IDs, * the screen always loads the correct patient data */ useEffect(() => { // Reset state when patientId changes setPatientData(null); setError(null); setIsLoading(true); // Fetch new patient data fetchPatientData(); }, [patientId]); // Only depend on patientId, not the entire fetchPatientData function /** * Navigation Title Effect * * Purpose: Set navigation title when patient data is loaded */ useEffect(() => { if (patientData) { navigation.setOptions({ title: patientData.patient_info.name || 'Patient Details', headerShown: false, }); } }, [navigation, patientData]); // ============================================================================ // EVENT HANDLERS // ============================================================================ /** * Handle Refresh * * Purpose: Pull-to-refresh functionality */ const handleRefresh = useCallback(async () => { setIsRefreshing(true); await fetchPatientData(); setIsRefreshing(false); }, [fetchPatientData]); /** * Handle DICOM Image Press * * Purpose: Open DICOM viewer modal for selected series * * @param seriesIndex - Index of the series */ const handleImagePress = useCallback((seriesIndex: number) => { if (!patientData || !patientData.series_summary[seriesIndex]) return; const series = patientData.series_summary[seriesIndex]; const seriesPredictions = patientData.predictions_by_series[series.series_num] || []; const firstPrediction = seriesPredictions[0]; if (firstPrediction?.preview) { const dicomUrl = API_CONFIG.BASE_URL +'/api/dicom'+ firstPrediction.file_path; console.log('dicomUrl', dicomUrl); setSelectedDicomData({ dicomUrl, seriesData: series, prediction: firstPrediction, }); setDicomModalVisible(true); } else { Alert.alert( 'No DICOM Available', 'No DICOM image is available for this series.', [{ text: 'OK' }] ); } }, [patientData]); /** * Handle Open DICOM Modal * * Purpose: Open DICOM modal with specific series and prediction data * * @param series - Series data * @param prediction - Optional prediction data */ const handleOpenDicomModal = useCallback((series: SeriesSummary, prediction?: Prediction) => { if (!patientData) return; const seriesPredictions = patientData.predictions_by_series[series.series_num] || []; const targetPrediction = prediction || seriesPredictions[0]; if (targetPrediction?.preview) { const dicomUrl = API_CONFIG.DICOM_BASE_URL + targetPrediction.preview; setSelectedDicomData({ dicomUrl, seriesData: series, prediction: targetPrediction, }); setDicomModalVisible(true); } else { Alert.alert( 'No DICOM Available', 'No DICOM image is available for this series.', [{ text: 'OK' }] ); } }, [patientData]); /** * Handle Close DICOM Modal * * Purpose: Close DICOM viewer modal and reset state */ const handleCloseDicomModal = useCallback(() => { setDicomModalVisible(false); setSelectedDicomData(null); }, []); /** * Get All Images from Series * * Purpose: Extract image paths from DICOM series */ const getAllImages = useCallback(() => { if (!patientData) return []; const images: string[] = []; patientData.series_summary.forEach(series => { if (series.png_preview) { images.push(series.png_preview); } }); return images; }, [patientData]); /** * Get Series Info for Image Index * * Purpose: Get series information for a given image index */ const getSeriesInfoForImage = useCallback((imageIndex: number) => { if (!patientData || imageIndex < 0 || imageIndex >= patientData.series_summary.length) { return { seriesNum: '1', seriesDesc: 'Unknown Series', imageInSeries: 1, totalInSeries: 1 }; } const series = patientData.series_summary[imageIndex]; return { seriesNum: series.series_num, seriesDesc: series.series_description, imageInSeries: 1, totalInSeries: series.total_images }; }, [patientData]); /** * Handle Emergency Action * * Purpose: Handle emergency actions for critical patients */ const handleEmergencyAction = useCallback(() => { if (!patientData) return; Alert.alert( 'Emergency Action Required', `Patient ${patientData.patient_info.name} requires immediate attention`, [ { text: 'Call Code Blue', style: 'destructive', onPress: () => { // TODO: Implement emergency code calling Alert.alert('Emergency', 'Code Blue activated'); }, }, { text: 'Alert Team', onPress: () => { // TODO: Implement team alerting Alert.alert('Alert', 'Team notified'); }, }, { text: 'Cancel', style: 'cancel', }, ] ); }, [patientData]); /** * Handle Back Navigation * * Purpose: Navigate back to previous screen */ const handleBackPress = useCallback(() => { navigation.goBack(); }, [navigation]); // ============================================================================ // NAVIGATION HANDLERS // ============================================================================ /** * Handle Open Additional Analysis Modal * * Purpose: Open modal to show detailed analysis results for a series * * @param series - Series data to show analysis for */ const handleOpenAnalysisModal = useCallback((series: SeriesSummary) => { if (!patientData) return; const seriesPredictions = patientData.predictions_by_series[series.series_num] || []; setSelectedSeriesForAnalysis({ series, predictions: seriesPredictions }); setAnalysisModalVisible(true); }, [patientData]); /** * Handle Close Additional Analysis Modal * * Purpose: Close the additional analysis modal and reset state */ const handleCloseAnalysisModal = useCallback(() => { setAnalysisModalVisible(false); setSelectedSeriesForAnalysis(null); }, []); /** * Handle Navigate to Feedback Detail * * Purpose: Navigate to feedback detail screen for a specific series * * @param series - Series data to view feedback for */ const handleNavigateToFeedbackDetail = useCallback((series: SeriesSummary) => { if (!patientData) return; navigation.navigate('FeedbackDetail', { patientId: patientData.patid, patientName: patientData.patient_info.name, seriesNumber: series.series_num, seriesData: series, patientData: patientData, feedbackData: patientData.feedback_by_series[series.series_num], // Initialize with empty array for new feedback // Pass the refresh function as callback so parent screen can update when feedback is submitted onFeedbackSubmitted: fetchPatientData }); }, [navigation, patientData, fetchPatientData]); // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * Get Status Color * * Purpose: Get appropriate color for patient status * * @param status - Patient status */ const getStatusColor = (status: string) => { switch (status.toLowerCase()) { case 'processed': return theme.colors.success; case 'pending': return theme.colors.warning; case 'error': return theme.colors.error; default: return theme.colors.info; } }; /** * Get Clinical Urgency Color * * Purpose: Get appropriate color for clinical urgency * * @param urgency - Clinical urgency level */ const getUrgencyColor = (urgency: string) => { switch (urgency.toLowerCase()) { case 'urgent': return theme.colors.error; case 'semi-urgent': return theme.colors.warning; case 'non-urgent': return theme.colors.success; default: return theme.colors.info; } }; /** * Get Percentage Color * * Purpose: Get appropriate color based on percentage value * * @param percentage - Percentage value (0-100) */ const getPercentageColor = (percentage: number) => { if (percentage >= 70) { return theme.colors.error; // High detection - Red } else if (percentage >= 40) { return theme.colors.warning; // Medium detection - Orange } else if (percentage >= 10) { return theme.colors.info; // Low detection - Blue } else { return theme.colors.success; // No detection - Green } }; /** * Get Percentage Value * * Purpose: Extract percentage value from prediction data with flexible field naming * * @param prediction - Prediction object * @param type - Hemorrhage type (epidural, subdural, etc.) */ const getPercentageValue = (prediction: any, type: string): number => { // First, try to get from detailed_results.hemorrhage_detection if (prediction.detailed_results?.hemorrhage_detection) { const hemorrhageData = prediction.detailed_results.hemorrhage_detection; // Map our types to the actual field names in the data const fieldMapping: { [key: string]: string } = { 'epidural': 'Epidural', 'subdural': 'Subdural', 'subarachnoid': 'Subarachnoid', 'intraparenchymal': 'Intraparenchymal', 'intraventricular': 'Intraventricular', 'midline_shift': 'Midline shift' }; const actualFieldName = fieldMapping[type]; if (actualFieldName && hemorrhageData[actualFieldName] !== undefined) { const value = hemorrhageData[actualFieldName]; // Convert decimal (0-1) to percentage (0-100) if (typeof value === 'number' && value >= 0 && value <= 1) { return value * 100; } // If it's already a percentage, return as is if (typeof value === 'number' && value >= 0 && value <= 100) { return value; } // Handle string values if (typeof value === 'string') { const numValue = parseFloat(value); if (!isNaN(numValue)) { if (numValue >= 0 && numValue <= 1) { return numValue * 100; } if (numValue >= 0 && numValue <= 100) { return numValue; } } } } } // Fallback: try different possible field names for the percentage const possibleFields = [ `${type}_percentage`, `${type}_score`, `${type}_value`, `${type}_detection`, `${type}_probability`, type ]; for (const field of possibleFields) { if (prediction[field] !== undefined && prediction[field] !== null) { const value = prediction[field]; // Handle different data types if (typeof value === 'number') { // If it's already a percentage (0-100), return as is if (value >= 0 && value <= 100) { return value; } // If it's a decimal (0-1), convert to percentage if (value >= 0 && value <= 1) { return value * 100; } // If it's a large number, assume it's already a percentage return value; } // Handle string values if (typeof value === 'string') { const numValue = parseFloat(value); if (!isNaN(numValue)) { // If it's already a percentage (0-100), return as is if (numValue >= 0 && numValue <= 100) { return numValue; } // If it's a decimal (0-1), convert to percentage if (numValue >= 0 && numValue <= 1) { return numValue * 100; } // If it's a large number, assume it's already a percentage return numValue; } } // Handle boolean values (convert to 0% or 100%) if (typeof value === 'boolean') { return value ? 100 : 0; } } } // If no valid percentage found, return 0 return 0; }; // ============================================================================ // RENDER HELPERS // ============================================================================ /** * Render Loading State * * Purpose: Render loading state while fetching data */ const renderLoadingState = () => ( Loading patient data... ); /** * Render Error State * * Purpose: Render error state when API fails */ const renderErrorState = () => ( Error Loading Patient Data {error} Retry ); /** * Render Patient Header * * Purpose: Render patient identification and status section */ const renderPatientHeader = () => { if (!patientData) return null; const isCritical = patientData.patient_info.status === 'Error' || patientData.patient_info.report_status === 'Critical'; return ( {patientData.patient_info.name || 'Unknown Patient'} ID: {patientData.patid} {patientData.patient_info.age || 'N/A'} • {patientData.patient_info.sex || 'N/A'} {patientData.patient_info.status} {isCritical && ( EMERGENCY )} ); }; /** * Render Tab Navigation * * Purpose: Render tab navigation for different sections * * Tab Structure: * - Overview: Patient demographics and medical information * - AI Analysis: Merged view of DICOM images and AI predictions (formerly separate Images and Predictions tabs) * - History: Processing history and notes */ const renderTabNavigation = () => { if (!patientData) return null; return ( {[ { key: 'overview', label: 'Overview', icon: 'info' }, { key: 'aiAnalysis', label: 'AI Analysis', icon: 'activity', count: patientData.series_summary.length }, { key: 'feedbacks', label: 'Feedbacks', icon: 'message-circle', count: patientData.total_predictions }, ].map((tab) => ( setActiveTab(tab.key as any)} > {tab.label} {tab.count !== undefined && ( {tab.count} )} ))} ); }; /** * Render Overview Tab * * Purpose: Render patient overview information */ const renderOverviewTab = () => { if (!patientData) return null; return ( {/* Patient Information */} Patient Information Patient ID {patientData.patid} Hospital ID {patientData.hospital_id.substring(0, 8)}... Name {patientData.patient_info.name} Age {patientData.patient_info.age} Sex {patientData.patient_info.sex} Date {patientData.patient_info.date} {/* Medical Information */} Medical Information Institution {patientData.patient_info.institution} Modality {patientData.patient_info.modality} Status {patientData.patient_info.status} Report Status {patientData.patient_info.report_status} File Name {patientData.patient_info.file_name} Frame Count {patientData.patient_info.frame_count} {/* Processing Information */} Processing Information First Processed {new Date(patientData.first_processed_at).toLocaleDateString()} Last Processed {new Date(patientData.last_processed_at).toLocaleDateString()} Total Predictions {patientData.total_predictions} Series Count {patientData.series_summary.length} ); }; /** * Render AI Analysis Tab * * Purpose: Render AI predictions and findings alongside their related images * * Features: * - Merged view of DICOM images and AI predictions * - Side-by-side display on larger screens, stacked on mobile * - Summary statistics at the top * - Series-based organization with integrated image and prediction data * - Responsive design for different screen sizes * - Feedback system for each series allowing physicians to provide clinical insights */ const renderAIAnalysisTab = () => { if (!patientData) return null; if (patientData.series_summary.length === 0) { return ( No Images Available No DICOM images are currently available for this patient ); } return ( AI Analysis & DICOM Images {/* Summary Statistics */} Total Series {patientData.series_summary.length} AI Predictions {patientData.total_predictions} Processing Status {patientData.patient_info.status} {patientData.series_summary.map((series, seriesIndex) => { // Get predictions for this series const seriesPredictions = patientData.predictions_by_series[series.series_num] || []; const hasPredictions = seriesPredictions.length > 0; return ( {/* Series Header */} Series {series.series_num}: {series.series_description} handleOpenAnalysisModal(series)} activeOpacity={0.7} > Additional Analysis {series.total_images} images • {series.modality} modality {hasPredictions && ` • ${seriesPredictions.length} AI predictions`} {/* Series Details */} Series Number: {series.series_num} Description: {series.series_description} Total Images: {series.total_images} Modality: {series.modality} AI Predictions: {hasPredictions ? `${seriesPredictions.length} found` : 'None'} {/* Image and Predictions Row */} {/* DICOM Image */} DICOM Preview {seriesPredictions[0]?.preview ? ( handleImagePress(seriesIndex)} > Series {series.series_num} ) : ( No Preview Available )} {/* AI Predictions */} AI Analysis Results {hasPredictions ? ( seriesPredictions.map((prediction) => ( {prediction.prediction.label} {prediction.prediction.clinical_urgency} Finding Type: {prediction.prediction.finding_type} Confidence: {(prediction.prediction.confidence_score * 100).toFixed(1)}% Category: {prediction.prediction.finding_category} Severity: {prediction.prediction.primary_severity} Location: {prediction.prediction.anatomical_location} Processed: {new Date(prediction.processed_at).toLocaleDateString()} )) ) : ( No AI predictions for this series )} ); })} ); }; /** * Render Feedbacks Tab * * Purpose: Render series feedback list with navigation to feedback details */ const renderFeedbacksTab = () => { if (!patientData) return null; if (patientData.series_summary.length === 0) { return ( No Series Available No DICOM series are currently available for this patient ); } return ( Series Feedback & Clinical Insights {/* Summary Statistics */} Total Series {patientData.series_summary.length} Total Predictions {patientData.total_predictions} Processing Status {patientData.patient_info.status} {/* Series Feedback Cards */} {patientData.series_summary.map((series, seriesIndex) => { // Get predictions for this series const seriesPredictions = patientData.predictions_by_series[series.series_num] || []; const hasPredictions = seriesPredictions.length > 0; const feedbackslength = patientData.feedback_by_series[series.series_num] || []; console.log(patientData); return ( handleNavigateToFeedbackDetail(series)} activeOpacity={0.7} > Series {series.series_num}: {series.series_description} {hasPredictions && ( {feedbackslength.length} )} {series.total_images} images • {series.modality} modality {hasPredictions && ` • ${seriesPredictions.length} AI predictions`} Series Number: {series.series_num} Description: {series.series_description} Total Images: {series.total_images} Modality: {series.modality} AI Predictions: {hasPredictions ? `${seriesPredictions.length} found` : 'None'} View Feedback Details ); })} ); }; // ============================================================================ // MAIN RENDER // ============================================================================ if (isLoading) { return ( {renderLoadingState()} ); } if (error) { return ( {renderErrorState()} ); } if (!patientData) { return ( Patient Not Found The requested patient data could not be found. ); } return ( {/* Header */} Patient Details Emergency Department {/* Patient Header */} {renderPatientHeader()} {/* Tab Navigation */} {renderTabNavigation()} {/* Tab Content */} } > {/* Overview Tab: Patient demographics and medical information */} {activeTab === 'overview' && renderOverviewTab()} {/* AI Analysis Tab: Merged view of DICOM images and AI predictions */} {activeTab === 'aiAnalysis' && renderAIAnalysisTab()} {/* Feedbacks Tab: Series feedback and clinical insights */} {activeTab === 'feedbacks' && renderFeedbacksTab()} {/* DICOM Viewer Modal */} {selectedDicomData && ( )} {/* Additional Analysis Modal */} {selectedSeriesForAnalysis && ( Additional Analysis - Series {selectedSeriesForAnalysis.series.series_num} {/* Series Information */} {selectedSeriesForAnalysis.series.series_description} {selectedSeriesForAnalysis.series.total_images} images • {selectedSeriesForAnalysis.series.modality} modality {/* AI Predictions Analysis */} {selectedSeriesForAnalysis.predictions.map((prediction) => ( {prediction.prediction.label} {prediction.prediction.clinical_urgency} Finding Type: {prediction.prediction.finding_type} Confidence: {(prediction.prediction.confidence_score * 100).toFixed(1)}% Category: {prediction.prediction.finding_type} Severity: {prediction.prediction.primary_severity} Location: {prediction.prediction.anatomical_location} {/* Additional Findings from detailed_results */} {prediction.prediction.detailed_results && ( Additional Analysis Results {/* Stroke Detection */} {prediction.prediction.detailed_results.stroke_detection && ( Stroke Detection: Normal: {((prediction.prediction.detailed_results.stroke_detection.Normal || 0) * 100).toFixed(1)}% Stroke: {((prediction.prediction.detailed_results.stroke_detection.Stroke || 0) * 100).toFixed(1)}% )} {/* Binary Hemorrhage */} {prediction.prediction.detailed_results.binary_hemorrhage && ( Hemorrhage Detection: Normal: {((prediction.prediction.detailed_results.binary_hemorrhage.Normal || 0) * 100).toFixed(1)}% Hemorrhage: {((prediction.prediction.detailed_results.binary_hemorrhage.Hemorrhage || 0) * 100).toFixed(1)}% )} )} {/* Visual Indicators Section - Show detailed findings with percentage indicators */} Detailed Findings Analysis {/* Compact Hemorrhage Type Percentage Indicators with Progress Bars */} {/* Epidural */} Epidural {getPercentageValue(prediction.prediction, 'epidural').toFixed(1)}% {/* Subdural */} Subdural {getPercentageValue(prediction.prediction, 'subdural').toFixed(1)}% {/* Intraparenchymal */} Intraparenchymal {getPercentageValue(prediction.prediction, 'intraparenchymal').toFixed(1)}% {/* Subarachnoid */} Subarachnoid {getPercentageValue(prediction.prediction, 'subarachnoid').toFixed(1)}% {/* Intraventricular */} Intraventricular {getPercentageValue(prediction.prediction, 'intraventricular').toFixed(1)}% {/* Midline Shift */} Midline Shift {getPercentageValue(prediction.prediction, 'midline_shift').toFixed(1)}% 50 ? theme.colors.error : theme.colors.warning } ]} /> {/* Summary Indicator with Overall Percentage */} Overall Assessment {/* Overall Percentage Calculation */} {(() => { const percentages = [ getPercentageValue(prediction.prediction, 'epidural'), getPercentageValue(prediction.prediction, 'subdural'), getPercentageValue(prediction.prediction, 'intraparenchymal'), getPercentageValue(prediction.prediction, 'subarachnoid'), getPercentageValue(prediction.prediction, 'intraventricular'), getPercentageValue(prediction.prediction, 'midline_shift') ]; const maxPercentage = Math.max(...percentages); const hasHemorrhage = maxPercentage > 10; // Consider >10% as detected return ( Highest Detection: {maxPercentage.toFixed(1)}% {hasHemorrhage ? 'HEMORRHAGE DETECTED' : 'NO HEMORRHAGE'} ); })()} Processed: {new Date(prediction.processed_at).toLocaleDateString()} ))} Close )} ); }; // ============================================================================ // STYLES // ============================================================================ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.colors.background, }, // Header Styles header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: theme.spacing.md, paddingVertical: theme.spacing.sm, backgroundColor: theme.colors.background, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, backButton: { padding: theme.spacing.sm, marginRight: theme.spacing.sm, }, headerTitle: { flex: 1, alignItems: 'center', }, headerTitleText: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, headerSubtitleText: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, }, refreshButton: { padding: theme.spacing.sm, marginLeft: theme.spacing.sm, }, // Patient Header Styles patientHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: theme.spacing.md, paddingVertical: theme.spacing.lg, backgroundColor: theme.colors.background, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, patientInfo: { flexDirection: 'row', alignItems: 'center', flex: 1, }, patientAvatar: { width: 60, height: 60, borderRadius: 30, backgroundColor: theme.colors.backgroundAlt, justifyContent: 'center', alignItems: 'center', marginRight: theme.spacing.md, }, patientDetails: { flex: 1, }, patientName: { fontSize: 20, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: 4, }, patientId: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, marginBottom: 8, }, patientMeta: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, patientMetaText: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, }, statusBadge: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, }, statusText: { fontSize: 12, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, textTransform: 'uppercase', }, emergencyButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.error, paddingHorizontal: 16, paddingVertical: 12, borderRadius: 8, shadowColor: theme.colors.error, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 4, }, emergencyButtonText: { color: theme.colors.background, fontSize: 12, fontFamily: theme.typography.fontFamily.bold, marginLeft: 8, textTransform: 'uppercase', }, // Tab Navigation Styles tabContainer: { flexDirection: 'row', backgroundColor: theme.colors.background, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, tabButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.sm, position: 'relative', }, activeTabButton: { borderBottomWidth: 2, borderBottomColor: theme.colors.primary, }, tabLabel: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, marginLeft: 8, }, activeTabLabel: { color: theme.colors.primary, fontFamily: theme.typography.fontFamily.bold, }, tabCount: { backgroundColor: theme.colors.primary, borderRadius: 10, paddingHorizontal: 6, paddingVertical: 2, marginLeft: 8, }, tabCountText: { color: theme.colors.background, fontSize: 10, fontFamily: theme.typography.fontFamily.bold, }, // Content Styles content: { flex: 1, }, tabContent: { padding: theme.spacing.md, }, // Section Styles section: { marginBottom: theme.spacing.lg, }, sectionTitle: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.md, }, // Info Grid Styles infoGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', }, infoItem: { width: '48%', marginBottom: theme.spacing.md, }, infoLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, marginBottom: 4, textTransform: 'uppercase', }, infoValue: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.regular, }, // Series Info Styles seriesInfo: { backgroundColor: theme.colors.backgroundAlt, padding: theme.spacing.md, borderRadius: 8, }, seriesText: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, marginBottom: 4, }, // Images Styles imagesContainer: { marginTop: theme.spacing.sm, }, seriesContainer: { marginBottom: theme.spacing.lg, }, seriesHeader: { marginBottom: theme.spacing.md, }, seriesHeaderTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4, }, seriesTitle: { fontSize: 16, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, seriesMeta: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, }, imageList: { paddingRight: theme.spacing.md, }, imageThumbnail: { width: 120, height: 120, borderRadius: 8, marginRight: theme.spacing.sm, position: 'relative', shadowColor: '#000000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, thumbnailImage: { width: '100%', height: '100%', borderRadius: 8, }, imageOverlay: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'rgba(0, 0, 0, 0.7)', paddingVertical: 4, paddingHorizontal: 8, borderBottomLeftRadius: 8, borderBottomRightRadius: 8, }, imageNumber: { color: theme.colors.background, fontSize: 10, fontFamily: theme.typography.fontFamily.bold, textAlign: 'center', }, // History Styles historyItem: { flexDirection: 'row', alignItems: 'center', marginBottom: theme.spacing.sm, }, historyText: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, marginLeft: theme.spacing.sm, }, notesText: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, lineHeight: 20, }, // Empty State Styles emptyState: { alignItems: 'center', justifyContent: 'center', paddingVertical: theme.spacing.xl, }, emptyStateTitle: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginTop: theme.spacing.md, marginBottom: theme.spacing.sm, }, emptyStateSubtitle: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, textAlign: 'center', lineHeight: 20, }, // No Image Placeholder Styles noImagePlaceholder: { width: 120, height: 120, borderRadius: 8, marginRight: theme.spacing.sm, backgroundColor: theme.colors.backgroundAlt, justifyContent: 'center', alignItems: 'center', borderWidth: 1, borderColor: theme.colors.border, borderStyle: 'dashed', }, noImageText: { fontSize: 10, color: theme.colors.textMuted, fontFamily: theme.typography.fontFamily.regular, textAlign: 'center', marginTop: 4, }, // Series Details Styles seriesDetails: { backgroundColor: theme.colors.backgroundAlt, padding: theme.spacing.md, borderRadius: 8, }, seriesDetailItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: theme.spacing.sm, }, seriesDetailLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, flex: 1, }, seriesDetailValue: { fontSize: 12, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.regular, flex: 2, textAlign: 'right', }, // Prediction Styles predictionSeries: { marginBottom: theme.spacing.lg, }, predictionSeriesTitle: { fontSize: 16, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, }, predictionCard: { backgroundColor: theme.colors.backgroundAlt, borderRadius: 8, padding: theme.spacing.md, marginBottom: theme.spacing.sm, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, predictionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: theme.spacing.sm, }, predictionLabel: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, urgencyBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, }, urgencyText: { fontSize: 10, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, textTransform: 'uppercase', }, predictionDetails: { marginBottom: theme.spacing.sm, }, predictionDetailItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: theme.spacing.xs, }, predictionDetailLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, flex: 1, }, predictionDetailValue: { fontSize: 12, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.regular, flex: 2, textAlign: 'right', }, predictionTimestamp: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, textAlign: 'right', }, // Loading and Error States loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: theme.spacing.lg, }, loadingText: { marginTop: theme.spacing.sm, fontSize: 16, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, }, errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: theme.spacing.lg, }, errorTitle: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginTop: theme.spacing.md, marginBottom: theme.spacing.sm, }, errorMessage: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, textAlign: 'center', marginBottom: theme.spacing.md, }, retryButton: { backgroundColor: theme.colors.primary, paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.lg, borderRadius: 8, shadowColor: theme.colors.primary, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 4, }, retryButtonText: { color: theme.colors.background, fontSize: 16, fontFamily: theme.typography.fontFamily.bold, }, // New styles for AI Analysis Tab imagePredictionsRow: { flexDirection: screenWidth > 600 ? 'row' : 'column', justifyContent: 'space-between', marginTop: theme.spacing.md, }, imageSection: { flex: 1, marginRight: screenWidth > 600 ? theme.spacing.md : 0, marginBottom: screenWidth > 600 ? 0 : theme.spacing.md, }, imageSectionTitle: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, }, predictionsSection: { flex: 1, }, predictionsSectionTitle: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, }, noPredictionsPlaceholder: { alignItems: 'center', justifyContent: 'center', paddingVertical: theme.spacing.md, }, noPredictionsText: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, marginTop: theme.spacing.sm, textAlign: 'center', }, // New styles for AI Analysis Tab analysisSummary: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: theme.colors.backgroundAlt, borderRadius: 8, paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.sm, marginBottom: theme.spacing.md, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, summaryItem: { alignItems: 'center', }, summaryLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, marginTop: theme.spacing.xs, }, summaryValue: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginTop: theme.spacing.xs, }, // New styles for Additional Analysis Button additionalAnalysisButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.backgroundAlt, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: theme.colors.border, }, additionalAnalysisButtonText: { fontSize: 12, color: theme.colors.primary, fontFamily: theme.typography.fontFamily.bold, marginLeft: 4, }, // Modal Styles modalOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 1000, }, analysisModal: { backgroundColor: theme.colors.background, borderRadius: 12, width: '90%', height: '80%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 4, elevation: 5, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: theme.spacing.md, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, modalTitle: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, closeButton: { padding: theme.spacing.sm, }, modalContent: { flex: 1, padding: theme.spacing.md, }, modalSeriesInfo: { marginBottom: theme.spacing.md, }, modalSeriesTitle: { fontSize: 16, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.xs, }, modalSeriesMeta: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, }, modalPredictionCard: { backgroundColor: theme.colors.backgroundAlt, borderRadius: 8, padding: theme.spacing.md, // marginBottom: theme.spacing.sm, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, marginBottom: 28, }, modalPredictionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: theme.spacing.sm, }, modalPredictionLabel: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, modalUrgencyBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, }, modalUrgencyText: { fontSize: 10, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, textTransform: 'uppercase', }, modalPredictionDetails: { marginBottom: theme.spacing.sm, }, modalPredictionDetailItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: theme.spacing.xs, }, modalPredictionDetailLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, flex: 1, }, modalPredictionDetailValue: { fontSize: 12, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.regular, flex: 2, textAlign: 'right', }, modalPredictionTimestamp: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, textAlign: 'right', }, modalAdditionalFindingsSection: { marginTop: theme.spacing.sm, paddingTop: theme.spacing.sm, borderTopWidth: 1, borderTopColor: theme.colors.border, }, modalAdditionalFindingsTitle: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.xs, }, modalAdditionalFindingItem: { marginBottom: theme.spacing.sm, }, modalAdditionalFindingLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, marginBottom: theme.spacing.xs, }, modalAdditionalFindingValues: { flexDirection: 'row', justifyContent: 'space-between', }, modalAdditionalFindingValue: { fontSize: 12, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.regular, }, modalVisualIndicatorsSection: { marginTop: theme.spacing.sm, paddingTop: theme.spacing.sm, borderTopWidth: 1, borderTopColor: theme.colors.border, }, modalVisualIndicatorsTitle: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, }, modalCompactIndicatorsContainer: { flexDirection: 'column', marginBottom: theme.spacing.sm, }, modalCompactIndicatorItem: { width: '100%', paddingVertical: theme.spacing.sm, paddingHorizontal: theme.spacing.sm, backgroundColor: theme.colors.background, borderRadius: 8, marginBottom: theme.spacing.sm, borderWidth: 1, borderColor: theme.colors.border, }, modalIndicatorHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: theme.spacing.xs, }, modalCompactIndicatorTitle: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, marginLeft: theme.spacing.sm, }, modalCompactIndicatorPercentage: { fontSize: 12, fontFamily: theme.typography.fontFamily.bold, marginLeft: theme.spacing.sm, }, modalProgressBarContainer: { height: 8, backgroundColor: theme.colors.border, borderRadius: 4, overflow: 'hidden', }, modalProgressBar: { height: '100%', borderRadius: 4, }, modalSummaryIndicator: { marginTop: theme.spacing.sm, paddingTop: theme.spacing.sm, borderTopWidth: 1, borderTopColor: theme.colors.border, }, modalSummaryIndicatorTitle: { fontSize: 14, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.xs, }, modalOverallPercentageContainer: { // flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: theme.spacing.sm, }, modalOverallPercentageLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, }, modalOverallPercentageValue: { fontSize: 18, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, modalSummaryIndicatorStatus: { flexDirection: 'row', alignItems: 'center', paddingVertical: theme.spacing.xs, paddingHorizontal: theme.spacing.sm, borderRadius: 12, marginTop: theme.spacing.xs, }, modalSummaryIndicatorText: { fontSize: 12, color: theme.colors.background, fontFamily: theme.typography.fontFamily.bold, marginLeft: theme.spacing.sm, }, modalFooter: { padding: theme.spacing.md, borderTopWidth: 1, borderTopColor: theme.colors.border, }, modalCloseButton: { backgroundColor: theme.colors.primary, paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.lg, borderRadius: 8, alignItems: 'center', }, modalCloseButtonText: { color: theme.colors.background, fontSize: 16, fontFamily: theme.typography.fontFamily.bold, }, // New styles for Feedback Series Cards feedbackSeriesCard: { backgroundColor: theme.colors.background, borderRadius: 16, padding: 20, marginBottom: theme.spacing.md, shadowColor: '#000000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4, elevation: 2, borderWidth: 1, borderColor: theme.colors.border, }, feedbackSeriesHeader: { marginBottom: theme.spacing.md, }, feedbackSeriesTitleRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4, }, feedbackSeriesTitle: { fontSize: 16, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, flex: 1, marginRight: theme.spacing.sm, }, feedbackCountBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.primary, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 12, minWidth: 24, justifyContent: 'center', }, feedbackCountText: { fontSize: 14, color: theme.colors.background, fontFamily: theme.typography.fontFamily.bold, marginLeft: 4, }, feedbackSeriesMeta: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.regular, }, feedbackSeriesDetails: { backgroundColor: theme.colors.backgroundAlt, padding: theme.spacing.md, borderRadius: 8, marginBottom: theme.spacing.md, }, feedbackSeriesDetailItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: theme.spacing.sm, }, feedbackSeriesDetailLabel: { fontSize: 12, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.medium, flex: 1, }, feedbackSeriesDetailValue: { fontSize: 12, color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.regular, flex: 2, textAlign: 'right', }, feedbackSeriesFooter: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingTop: theme.spacing.sm, borderTopWidth: 1, borderTopColor: theme.colors.border, }, feedbackSeriesFooterText: { fontSize: 14, color: theme.colors.primary, fontFamily: theme.typography.fontFamily.medium, marginLeft: theme.spacing.xs, }, }); export default PatientDetailsScreen; /* * End of File: PatientDetailsScreen.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */