345 lines
9.0 KiB
TypeScript
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.
|
|
*/
|