NeoScan_Physician/app/shared/components/DicomViewerModal.tsx
2025-08-20 20:39:08 +05:30

345 lines
9.0 KiB
TypeScript

/*
* File: DicomViewerModal.tsx
* Description: Reusable modal component for DICOM image viewing
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Modal,
SafeAreaView,
StatusBar,
Dimensions,
Alert,
} from 'react-native';
import { theme } from '../../theme/theme';
import Icon from 'react-native-vector-icons/Feather';
import DicomViewer from './DicomViewer';
// ============================================================================
// INTERFACES
// ============================================================================
/**
* DicomViewerModalProps Interface
*
* Purpose: Defines the props required by the DicomViewerModal component
*
* Props:
* - visible: Whether the modal is visible
* - dicomUrl: URL of the DICOM file to display
* - onClose: Callback function when modal is closed
* - title: Optional title for the modal header
* - patientName: Optional patient name for context
* - studyDescription: Optional study description
*/
interface DicomViewerModalProps {
visible: boolean;
dicomUrl: string;
onClose: () => void;
title?: string;
patientName?: string;
studyDescription?: string;
}
// ============================================================================
// DICOM VIEWER MODAL COMPONENT
// ============================================================================
/**
* DicomViewerModal Component
*
* Purpose: Provides a full-screen modal for viewing DICOM medical images
*
* Features:
* - Full-screen DICOM image viewing
* - Modal overlay with close functionality
* - Error handling and display
* - Loading states
* - Header with patient/study information
* - Responsive design for different screen sizes
* - Proper medical image viewing environment (dark background)
*/
export const DicomViewerModal: React.FC<DicomViewerModalProps> = ({
visible,
dicomUrl,
onClose,
title = 'DICOM Viewer',
patientName,
studyDescription,
}) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle DICOM viewer load completion
*/
const handleDicomLoad = () => {
setIsLoading(false);
setHasError(false);
};
/**
* Handle DICOM viewer errors
* @param error - Error message from DICOM viewer
*/
const handleDicomError = (error: string) => {
setIsLoading(false);
setHasError(true);
// Show error alert to user
Alert.alert(
'DICOM Loading Error',
`Failed to load DICOM file: ${error}`,
[
{
text: 'Retry',
onPress: () => {
setHasError(false);
setIsLoading(true);
},
},
{
text: 'Close',
onPress: onClose,
style: 'cancel',
},
]
);
};
/**
* Handle modal close request
*/
const handleClose = () => {
// Reset states when closing
setIsLoading(false);
setHasError(false);
onClose();
};
/**
* Handle back button press (Android)
*/
const handleRequestClose = () => {
handleClose();
};
// ============================================================================
// RENDER HEADER
// ============================================================================
/**
* Render modal header with title and close button
*/
const renderHeader = () => (
<View style={styles.header}>
{/* Title and Patient Info */}
<View style={styles.headerContent}>
<Text style={styles.headerTitle}>{title}</Text>
{patientName && (
<Text style={styles.headerSubtitle}>Patient: {patientName}</Text>
)}
{studyDescription && (
<Text style={styles.headerSubtitle}>Study: {studyDescription}</Text>
)}
</View>
{/* Close Button */}
<TouchableOpacity
style={styles.closeButton}
onPress={handleClose}
activeOpacity={0.7}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Icon name="x" size={24} color={theme.colors.background} />
</TouchableOpacity>
</View>
);
// ============================================================================
// RENDER CONTENT
// ============================================================================
/**
* Render DICOM viewer content
*/
const renderContent = () => {
if (!dicomUrl) {
return (
<View style={styles.emptyContainer}>
<Icon name="file-minus" size={48} color={theme.colors.textMuted} />
<Text style={styles.emptyText}>No DICOM URL provided</Text>
<TouchableOpacity style={styles.closeButtonSecondary} onPress={handleClose}>
<Text style={styles.closeButtonText}>Close</Text>
</TouchableOpacity>
</View>
);
}
return (
<View style={styles.viewerContainer}>
<DicomViewer
dicomUrl={dicomUrl}
onLoad={handleDicomLoad}
onError={handleDicomError}
/>
</View>
);
};
// ============================================================================
// MAIN RENDER
// ============================================================================
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="fullScreen"
onRequestClose={handleRequestClose}
>
<SafeAreaView style={styles.container}>
{/* Set status bar to light content for dark background */}
<StatusBar
barStyle="light-content"
backgroundColor="#000000"
translucent={false}
/>
{/* Header */}
{renderHeader()}
{/* Content */}
{renderContent()}
</SafeAreaView>
</Modal>
);
};
// ============================================================================
// STYLES
// ============================================================================
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
const styles = StyleSheet.create({
// Main container
container: {
flex: 1,
backgroundColor: '#000000',
},
// Header section
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderBottomWidth: 1,
borderBottomColor: 'rgba(255, 255, 255, 0.1)',
},
// Header content
headerContent: {
flex: 1,
marginRight: theme.spacing.md,
},
// Header title
headerTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.background,
marginBottom: 2,
},
// Header subtitle
headerSubtitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: 'rgba(255, 255, 255, 0.8)',
lineHeight: theme.typography.fontSize.bodyMedium * 1.2,
},
// Close button
closeButton: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.2)',
},
// DICOM viewer container
viewerContainer: {
flex: 1,
backgroundColor: '#000000',
},
// Empty state container
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000000',
paddingHorizontal: theme.spacing.xl,
},
// Empty state text
emptyText: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textMuted,
textAlign: 'center',
marginTop: theme.spacing.md,
marginBottom: theme.spacing.xl,
},
// Secondary close button
closeButtonSecondary: {
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
...theme.shadows.primary,
},
// Close button text
closeButtonText: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.background,
},
});
// ============================================================================
// EXPORT
// ============================================================================
export default DicomViewerModal;
/*
* End of File: DicomViewerModal.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/