diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e189252..bd883f7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + - NeoScan_Physician + NeoScanPhysician diff --git a/app/assets/images/coming-soon.jpg b/app/assets/images/coming-soon.jpg new file mode 100644 index 0000000..7e042c6 Binary files /dev/null and b/app/assets/images/coming-soon.jpg differ diff --git a/app/modules/Auth/components/signup/DocumentUploadStep.tsx b/app/modules/Auth/components/signup/DocumentUploadStep.tsx index 4d437eb..81072c7 100644 --- a/app/modules/Auth/components/signup/DocumentUploadStep.tsx +++ b/app/modules/Auth/components/signup/DocumentUploadStep.tsx @@ -324,7 +324,7 @@ const DocumentUploadStep: React.FC = ({ Upload your ID document - Please upload a clear photo of your government-issued ID for verification. + Please upload a clear photo of your hospital-issued ID for verification. {/* Document Upload Area */} diff --git a/app/modules/Auth/screens/LoginScreen.tsx b/app/modules/Auth/screens/LoginScreen.tsx index f7720e8..30b7495 100644 --- a/app/modules/Auth/screens/LoginScreen.tsx +++ b/app/modules/Auth/screens/LoginScreen.tsx @@ -313,7 +313,7 @@ const styles = StyleSheet.create({ // Eye icon for password visibility eyeIcon: { - paddingLeft: theme.spacing.sm, + padding: theme.spacing.sm, }, // Base button styling diff --git a/app/modules/Auth/screens/ResetPasswordScreen.tsx b/app/modules/Auth/screens/ResetPasswordScreen.tsx index 227d9d1..fabaeea 100644 --- a/app/modules/Auth/screens/ResetPasswordScreen.tsx +++ b/app/modules/Auth/screens/ResetPasswordScreen.tsx @@ -1,6 +1,6 @@ /* * File: ResetPasswordScreen.tsx - * Description: Password reset screen for onboarding flow + * Description: Password reset screen for onboarding flow with document upload support * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ @@ -16,7 +16,15 @@ import { KeyboardAvoidingView, Platform, Alert, + Image, + PermissionsAndroid, } from 'react-native'; +import { + launchImageLibrary, + launchCamera, + ImagePickerResponse, + MediaType, +} from 'react-native-image-picker'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { updateOnboarded, logout } from '../redux/authSlice'; import { authAPI } from '../services/authAPI'; @@ -49,18 +57,41 @@ interface PasswordRule { isValid: boolean; } +/** + * ImageData Interface + * + * Purpose: Defines the structure for image data + */ +interface ImageData { + uri: string; + name: string; + type: string; + size?: number; +} + +/** + * OnboardingStep Type + * + * Purpose: Defines the different onboarding steps + */ +type OnboardingStep = 'document' | 'password'; + /** * ResetPasswordScreen Component * * Purpose: Password reset screen for users who need to set their initial password * * Features: - * 1. Password and confirm password input fields - * 2. Real-time password validation with visual feedback - * 3. Password visibility toggles - * 4. Password strength requirements display - * 5. Integration with Redux for onboarding status - * 6. API integration for password change + * 1. Two flows based on platform: + * - platform = 'web': Document upload first, then password reset + * - platform = 'app': Password reset only + * 2. Document upload with camera/gallery selection + * 3. Password and confirm password input fields + * 4. Real-time password validation with visual feedback + * 5. Password visibility toggles + * 6. Password strength requirements display + * 7. Integration with Redux for onboarding status + * 8. API integration for document upload and password change * * Password Requirements: * - At least 8 characters @@ -84,6 +115,13 @@ export const ResetPasswordScreen: React.FC = ({ const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false); const [loading, setLoading] = useState(false); + // Document upload states + const [selectedImage, setSelectedImage] = useState(null); + const [documentUploaded, setDocumentUploaded] = useState(false); + + // Current step state + const [currentStep, setCurrentStep] = useState('document'); + // Redux state const dispatch = useAppDispatch(); const user = useAppSelector((state) => state.auth.user); @@ -141,6 +179,19 @@ export const ResetPasswordScreen: React.FC = ({ updatePasswordRules(password); }, [password, confirmPassword]); + /** + * useEffect for determining initial step + * + * Purpose: Set the initial step based on user signup platform + */ + useEffect(() => { + if (user?.platform === 'app') { + setCurrentStep('password'); + } else { + setCurrentStep('document'); + } + }, [user?.platform]); + // ============================================================================ // HELPER FUNCTIONS // ============================================================================ @@ -181,6 +232,237 @@ export const ResetPasswordScreen: React.FC = ({ ); }; + /** + * Request Camera Permission + * + * Purpose: Request camera permission for Android devices + * + * @returns Promise - Whether permission was granted + */ + const requestCameraPermission = async (): Promise => { + if (Platform.OS === 'android') { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.CAMERA, + { + title: 'Camera Permission', + message: 'This app needs camera permission to capture images.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + } + ); + return granted === PermissionsAndroid.RESULTS.GRANTED; + } catch (err) { + console.warn('Camera permission error:', err); + return false; + } + } + return true; + }; + + /** + * Format File Size + * + * Purpose: Convert bytes to human readable format + * + * @param bytes - File size in bytes + * @returns Formatted file size string + */ + const formatFileSize = (bytes?: number): string => { + if (!bytes) return ''; + + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + if (bytes === 0) return '0 Bytes'; + + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; + }; + + /** + * Get File Type Display + * + * Purpose: Get display name for file type + * + * @param type - MIME type + * @returns Display name for file type + */ + const getFileTypeDisplay = (type: string): string => { + if (type.includes('jpeg') || type.includes('jpg')) return 'JPEG'; + if (type.includes('png')) return 'PNG'; + if (type.includes('gif')) return 'GIF'; + if (type.includes('webp')) return 'WebP'; + return 'Image'; + }; + + // ============================================================================ + // DOCUMENT UPLOAD HANDLERS + // ============================================================================ + + /** + * Handle Image Picker Selection + * + * Purpose: Show options for camera or gallery selection + */ + const handleImagePicker = () => { + Alert.alert( + 'Select Image', + 'Choose how you want to upload your document', + [ + { + text: 'Camera', + onPress: () => handleCameraCapture(), + }, + { + text: 'Gallery', + onPress: () => handleGalleryPicker(), + }, + { + text: 'Cancel', + style: 'cancel', + }, + ] + ); + }; + + /** + * Handle Camera Capture + * + * Purpose: Launch camera to capture image + */ + const handleCameraCapture = async () => { + const hasPermission = await requestCameraPermission(); + + if (!hasPermission) { + showError('Permission Error', 'Camera permission is required to capture images.'); + return; + } + + const options = { + mediaType: 'photo' as MediaType, + maxWidth: 2000, + maxHeight: 2000, + includeBase64: false, + }; + + launchCamera(options, (response: ImagePickerResponse) => { + if (response.didCancel) { + return; + } + + if (response.errorMessage) { + showError('Camera Error', response.errorMessage); + return; + } + + if (response.assets && response.assets[0]) { + const asset = response.assets[0]; + const imageData: ImageData = { + uri: asset.uri!, + name: asset.fileName || `document_${Date.now()}.jpg`, + type: asset.type || 'image/jpeg', + size: asset.fileSize, + }; + + setSelectedImage(imageData); + showSuccess('Success', 'Document captured successfully!'); + } + }); + }; + + /** + * Handle Gallery Picker + * + * Purpose: Launch image library to select image + */ + const handleGalleryPicker = () => { + const options = { + mediaType: 'photo' as MediaType, + maxWidth: 2000, + maxHeight: 2000, + includeBase64: false, + }; + + launchImageLibrary(options, (response: ImagePickerResponse) => { + if (response.didCancel) { + return; + } + + if (response.errorMessage) { + showError('Gallery Error', response.errorMessage); + return; + } + + if (response.assets && response.assets[0]) { + const asset = response.assets[0]; + const imageData: ImageData = { + uri: asset.uri!, + name: asset.fileName || `document_${Date.now()}.jpg`, + type: asset.type || 'image/jpeg', + size: asset.fileSize, + }; + + setSelectedImage(imageData); + showSuccess('Success', 'Document selected from gallery!'); + } + }); + }; + + /** + * Handle Remove Image + * + * Purpose: Remove selected image + */ + const handleRemoveImage = () => { + setSelectedImage(null); + setDocumentUploaded(false); + }; + + /** + * Handle Document Upload + * + * Purpose: Upload document to server + */ + const handleDocumentUpload = async () => { + if (!selectedImage) { + showError('Validation Error', 'Please upload a document to continue.'); + return; + } + + setLoading(true); + + try { + const formData = new FormData(); + const file = { + uri: selectedImage.uri, + name: selectedImage.name, + type: selectedImage.type, + }; + formData.append('id_photo', file as any); + + const response: any = await authAPI.uploadDocument(formData, user?.access_token); + console.log('upload response',response) + + if (response.data && response.data.message) { + if (response.data.success) { + showSuccess(response.data.message); + setDocumentUploaded(true); + // Move to password step after successful upload + setCurrentStep('password'); + } else { + showError(response.data.message); + } + } else { + showError('Error while uploading document'); + } + } catch (error: any) { + console.error('Document upload error:', error); + showError('Failed to upload document. Please try again.'); + } finally { + setLoading(false); + } + }; + // ============================================================================ // EVENT HANDLERS // ============================================================================ @@ -319,6 +601,191 @@ export const ResetPasswordScreen: React.FC = ({ ); + /** + * renderDocumentUploadStep Function + * + * Purpose: Render document upload step + * + * @returns JSX element for document upload step + */ + const renderDocumentUploadStep = () => ( + <> + {/* Icon */} + + + + + {/* Title and Subtitle */} + Upload Your ID Document + + Please upload a clear photo of your hospital-issued ID for verification + + + {/* Document Upload Area */} + + + {selectedImage ? ( + + + + + + Document Selected + {selectedImage.name} + + {getFileTypeDisplay(selectedImage.type)} + {selectedImage.size && ( + • {formatFileSize(selectedImage.size)} + )} + + + ) : ( + <> + + Tap to upload document + JPG, PNG supported + + )} + + + + {selectedImage && ( + + Change Document + + )} + + {/* Upload Button */} + + {loading ? ( + + Uploading Document... + + ) : ( + Upload Document + )} + + + ); + + /** + * renderPasswordResetStep Function + * + * Purpose: Render password reset step + * + * @returns JSX element for password reset step + */ + const renderPasswordResetStep = () => ( + <> + {/* Icon */} + + + + + {/* Title and Subtitle */} + Set Your Password + + Create a strong password to complete your account setup + + + {/* Password Input */} + + + + setPasswordVisible(!isPasswordVisible)} + style={styles.eyeIcon} + disabled={loading} + > + + + + + {/* Confirm Password Input */} + + + + setConfirmPasswordVisible(!isConfirmPasswordVisible)} + style={styles.eyeIcon} + disabled={loading} + > + + + + + {/* Password Rules Section */} + + Password Requirements: + + {passwordRules.map(renderPasswordRule)} + + + + {/* Reset Button */} + + {loading ? ( + + Setting Password... + + ) : ( + Set Password + )} + + + ); + // ============================================================================ // RENDER SECTION // ============================================================================ @@ -338,97 +805,33 @@ export const ResetPasswordScreen: React.FC = ({ - Set Your Password + + {currentStep === 'document' ? 'Upload Document' : 'Set Password'} + - {/* Icon */} - - - - - {/* Title and Subtitle */} - Set your password - - Create a strong password to complete your account setup - - - {/* Password Input */} - - - - setPasswordVisible(!isPasswordVisible)} - style={styles.eyeIcon} - disabled={loading} - > - - - - - {/* Confirm Password Input */} - - - - setConfirmPasswordVisible(!isConfirmPasswordVisible)} - style={styles.eyeIcon} - disabled={loading} - > - - - - - {/* Password Rules Section */} - - Password Requirements: - - {passwordRules.map(renderPasswordRule)} - - - - {/* Reset Button */} - - {loading ? ( - - Setting Password... + {/* Step Indicator */} + {user?.platform === 'web' && ( + + + + 1 + + Document - ) : ( - Set Password - )} - + + + + 2 + + Password + + + )} + + {/* Content based on current step */} + {currentStep === 'document' ? renderDocumentUploadStep() : renderPasswordResetStep()} ); @@ -483,6 +886,72 @@ const styles = StyleSheet.create({ width: 40, }, + // Step indicator + stepIndicator: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.spacing.xl, + paddingHorizontal: theme.spacing.lg, + }, + + // Step container + stepContainer: { + alignItems: 'center', + }, + + // Step circle + stepCircle: { + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 2, + borderColor: theme.colors.border, + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.spacing.xs, + }, + + // Step circle active + stepCircleActive: { + backgroundColor: theme.colors.primary, + borderColor: theme.colors.primary, + }, + + // Step number + stepNumber: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + }, + + // Step number active + stepNumberActive: { + color: theme.colors.background, + }, + + // Step label + stepLabel: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Step label active + stepLabelActive: { + color: theme.colors.primary, + fontFamily: theme.typography.fontFamily.medium, + }, + + // Step line + stepLine: { + flex: 1, + height: 2, + backgroundColor: theme.colors.border, + marginHorizontal: theme.spacing.md, + }, + // Icon container iconContainer: { alignItems: 'center', @@ -507,6 +976,119 @@ const styles = StyleSheet.create({ marginBottom: theme.spacing.xl, }, + // Upload container + uploadContainer: { + backgroundColor: theme.colors.backgroundAlt, + borderWidth: 2, + borderColor: theme.colors.border, + borderStyle: 'dashed', + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.xl, + marginBottom: theme.spacing.md, + minHeight: 200, + }, + + // Upload content + uploadContent: { + alignItems: 'center', + }, + + // Image preview container + imagePreviewContainer: { + alignItems: 'center', + width: '100%', + }, + + // Image preview + imagePreview: { + width: '100%', + height: 150, + borderRadius: theme.borderRadius.medium, + marginBottom: theme.spacing.sm, + resizeMode: 'contain', + }, + + // Image overlay (remove button) + imageOverlay: { + position: 'absolute', + top: 0, + right: 20, + backgroundColor: 'rgba(0, 0, 0, 0.4)', + borderRadius: 17, + width: 34, + height: 34, + justifyContent: 'center', + alignItems: 'center', + }, + + // Upload text + uploadText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginTop: theme.spacing.sm, + }, + + // Upload subtext + uploadSubtext: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginTop: theme.spacing.xs, + }, + + // Uploaded text + uploadedText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.success, + marginTop: theme.spacing.sm, + }, + + // File name + fileName: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginTop: theme.spacing.xs, + textAlign: 'center', + maxWidth: '80%', + }, + + // File details + fileDetails: { + flexDirection: 'row', + alignItems: 'center', + marginTop: theme.spacing.xs, + }, + + // File type + fileType: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textMuted, + }, + + // File size + fileSize: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textMuted, + }, + + // Change button + changeButton: { + alignSelf: 'center', + marginBottom: theme.spacing.xl, + }, + + // Change button text + changeButtonText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.primary, + }, + // Input container inputContainer: { flexDirection: 'row', @@ -607,6 +1189,12 @@ const styles = StyleSheet.create({ marginBottom: theme.spacing.md, }, + // Upload button + uploadButton: { + backgroundColor: theme.colors.primary, + ...theme.shadows.primary, + }, + // Reset button resetButton: { backgroundColor: theme.colors.primary, diff --git a/app/modules/Auth/services/authAPI.ts b/app/modules/Auth/services/authAPI.ts index 1cea369..5d2d87f 100644 --- a/app/modules/Auth/services/authAPI.ts +++ b/app/modules/Auth/services/authAPI.ts @@ -27,7 +27,9 @@ export const authAPI = { //change password changepassword: (payload:{password:string,token:string | undefined}) => api.post('/api/auth/onboarding/change-password', {password:payload.password},buildHeaders({token:payload.token})), //validate username - validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders()) + validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders()), + //upload document for onboarding + uploadDocument: (formData:any, token:string | undefined) => api.post('/api/auth/onboarding/upload-id-photo', formData,buildHeaders({token:token, contentType: 'multipart/form-data' })) // Add more endpoints as needed }; diff --git a/app/modules/Dashboard/components/BrainPredictionsOverview.tsx b/app/modules/Dashboard/components/BrainPredictionsOverview.tsx new file mode 100644 index 0000000..40e2643 --- /dev/null +++ b/app/modules/Dashboard/components/BrainPredictionsOverview.tsx @@ -0,0 +1,616 @@ +/* + * File: BrainPredictionsOverview.tsx + * Description: Component to display patient overview statistics based on different AI prediction scenarios for brain conditions + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React from 'react'; +import { + View, + Text, + StyleSheet, + Dimensions, + ScrollView, +} from 'react-native'; +import { PieChart } from 'react-native-chart-kit'; +import { theme } from '../../../theme/theme'; +import { ERDashboard } from '../../../shared/types'; + +/** + * BrainPredictionsOverviewProps Interface + * + * Purpose: Defines the props required by the BrainPredictionsOverview component + * + * Props: + * - dashboard: ERDashboard object containing brain prediction statistics + */ +interface BrainPredictionsOverviewProps { + dashboard: ERDashboard; +} + +/** + * BrainPredictionsOverview Component + * + * Purpose: Display patient overview statistics based on different AI prediction scenarios + * + * Features: + * 1. Shows distribution of patients by brain condition predictions + * 2. Displays AI confidence levels for each prediction type + * 3. Shows critical vs non-critical brain conditions + * 4. Provides summary statistics for quick overview + * 5. Visualizes data using react-native-chart-kit pie chart + * + * Prediction Categories: + * - Hemorrhagic Conditions (IPH, IVH, SAH, SDH, EDH) + * - Ischemic Conditions (Stroke) + * - Structural Changes (Mass effect, Midline shift) + * - Normal Findings + */ +export const BrainPredictionsOverview: React.FC = ({ + dashboard, +}) => { + // ============================================================================ + // MOCK PREDICTION DATA + // ============================================================================ + + /** + * Mock prediction statistics based on brain conditions + * In a real app, this would come from the AI analysis API + */ + const predictionStats = { + // Hemorrhagic Conditions + intraparenchymal: { count: 2, confidence: 94, critical: true }, + intraventricular: { count: 1, confidence: 87, critical: true }, + subarachnoid: { count: 1, confidence: 87, critical: true }, + subdural: { count: 1, confidence: 89, critical: true }, + epidural: { count: 1, confidence: 96, critical: true }, + + // Ischemic Conditions + ischemic_stroke: { count: 1, confidence: 92, critical: true }, + + // Structural Changes + mass_effect: { count: 2, confidence: 91, critical: true }, + midline_shift: { count: 1, confidence: 96, critical: true }, + + // Normal Findings + normal_brain: { count: 3, confidence: 98, critical: false }, + + // Pending Analysis + pending_analysis: { count: 6, confidence: 0, critical: false }, + }; + + // ============================================================================ + // HELPER FUNCTIONS + // ============================================================================ + + /** + * getPredictionLabel Function + * + * Purpose: Get human-readable label for prediction type + * + * @param predictionType - Type of brain prediction + * @returns Human-readable label + */ + const getPredictionLabel = (predictionType: string) => { + if (!predictionType) { + return 'Unknown'; // Default label for undefined types + } + + const labels: { [key: string]: string } = { + intraparenchymal: 'IPH', + intraventricular: 'IVH', + subarachnoid: 'SAH', + subdural: 'SDH', + epidural: 'EDH', + ischemic_stroke: 'Stroke', + mass_effect: 'Mass Effect', + midline_shift: 'Midline Shift', + normal_brain: 'Normal', + pending_analysis: 'Pending', + }; + + return labels[predictionType] || predictionType; + }; + + /** + * getPredictionColor Function + * + * Purpose: Get color based on prediction type and criticality + * + * @param predictionType - Type of brain prediction + * @param isCritical - Whether the condition is critical + * @returns Color string for styling + */ + const getPredictionColor = (predictionType: string, isCritical: boolean) => { + if (!predictionType) { + return theme.colors.textSecondary; // Default color for undefined types + } + + // Define distinct colors for each prediction class + const predictionColors: { [key: string]: string } = { + intraparenchymal: '#E53E3E', // Red for IPH + intraventricular: '#C53030', // Dark Red for IVH + subarachnoid: '#F56565', // Light Red for SAH + subdural: '#FC8181', // Pink Red for SDH + epidural: '#E53E3E', // Red for EDH + ischemic_stroke: '#3182CE', // Blue for Stroke + mass_effect: '#805AD5', // Purple for Mass Effect + midline_shift: '#553C9A', // Dark Purple for Midline Shift + normal_brain: '#38A169', // Green for Normal + pending_analysis: '#D69E2E', // Yellow for Pending + }; + + return predictionColors[predictionType] || theme.colors.textSecondary; + }; + + // ============================================================================ + // COMPUTED VALUES + // ============================================================================ + + /** + * Calculate total patients + */ + const totalPatients = Object.values(predictionStats).reduce((sum, stats) => sum + stats.count, 0); + + /** + * Calculate total critical conditions + */ + const totalCritical = Object.values(predictionStats) + .filter(stats => stats.critical) + .reduce((sum, stats) => sum + stats.count, 0); + + /** + * Calculate total normal findings + */ + const totalNormal = predictionStats.normal_brain.count; + + /** + * Calculate total pending analysis + */ + const totalPending = predictionStats.pending_analysis.count; + + /** + * Calculate average confidence for critical conditions + */ + const criticalConditions = Object.values(predictionStats).filter(stats => stats.critical && stats.confidence > 0); + const averageConfidence = criticalConditions.length > 0 + ? Math.round(criticalConditions.reduce((sum, stats) => sum + stats.confidence, 0) / criticalConditions.length) + : 0; + + /** + * Get all prediction classes by count + */ + const allPredictions = Object.entries(predictionStats) + .filter(([_, stats]) => stats.count > 0) + .sort((a, b) => b[1].count - a[1].count); + + // ============================================================================ + // CHART DATA PREPARATION + // ============================================================================ + + /** + * Prepare chart data for react-native-chart-kit PieChart + */ + const chartData = Object.entries(predictionStats) + .filter(([_, stats]) => stats && stats.count > 0) // Filter out undefined or zero count entries + .map(([predictionType, stats]) => ({ + name: getPredictionLabel(predictionType), + population: stats.count, // react-native-chart-kit uses 'population' property + color: getPredictionColor(predictionType, stats.critical), + legendFontColor: theme.colors.textPrimary, + legendFontSize: 12, + confidence: stats.confidence, + isCritical: stats.critical, + })) + .filter(item => item.population > 0) // Additional filter to ensure no zero count items + .sort((a, b) => b.population - a.population); // Sort by count descending - removed limit to show all classes + + /** + * Chart configuration for react-native-chart-kit + */ + const chartConfig = { + backgroundColor: theme.colors.background, + backgroundGradientFrom: theme.colors.background, + backgroundGradientTo: theme.colors.background, + decimalPlaces: 0, + color: (opacity = 1) => `rgba(33, 150, 243, ${opacity})`, + labelColor: (opacity = 1) => `rgba(33, 33, 33, ${opacity})`, + style: { + borderRadius: 16, + }, + propsForDots: { + r: '6', + strokeWidth: '2', + stroke: theme.colors.primary, + }, + useShadowColorFromDataset: false, + }; + + // ============================================================================ + // RENDER FUNCTIONS + // ============================================================================ + + + + /** + * renderLegend Function + * + * Purpose: Render chart legend with confidence levels in grid layout + * + * @returns Legend component + */ + const renderLegend = () => ( + + Prediction Classes Breakdown + + {chartData.map((item, index) => ( + + + + {item.name} + + {item.population} patients + {item.confidence > 0 && ( + {item.confidence}% + )} + + ))} + + + ); + + // ============================================================================ + // MAIN RENDER + // ============================================================================ + return ( + + {/* Section Header */} + + Brain AI Predictions Overview + + Patient distribution by AI-detected conditions + + + + {/* Pie Chart Section */} + + + + {chartData.length > 0 ? ( + + + {(() => { + try { + return ( + + ); + } catch (error) { + console.warn('PieChart error:', error); + return ( + + Chart temporarily unavailable + + ); + } + })()} + + {renderLegend()} + + ) : ( + + No data available for chart + + )} + + + + {/* Summary Statistics */} + + + Total Patients + {totalPatients} + + + Critical Cases + + {totalCritical} + + + + Pending Analysis + + {totalPending} + + + + Avg Confidence + + {averageConfidence}% + + + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.large, + padding: theme.spacing.md, + marginVertical: theme.spacing.sm, + borderWidth: 1, + borderColor: theme.colors.border, + }, + + // Header section + header: { + marginBottom: theme.spacing.lg, + }, + + // Main title + title: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Statistics grid + statsGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: theme.spacing.xs, + marginBottom: theme.spacing.lg, + }, + + // Individual stat card + statCard: { + flex: 1, + minWidth: '30%', + maxWidth: '32%', + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.sm, + borderWidth: 1, + borderColor: theme.colors.border, + borderLeftWidth: 3, + alignItems: 'center', + }, + + // Stat value + statValue: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Stat title + statTitle: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.semibold, + color: theme.colors.textPrimary, + textAlign: 'center', + marginBottom: theme.spacing.xs, + }, + + // Stat subtitle + statSubtitle: { + fontSize: theme.typography.fontSize.caption, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + }, + + // Chart section + chartSection: { + marginBottom: theme.spacing.lg, + }, + + // Chart title + chartTitle: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.semibold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Chart subtitle + chartSubtitle: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.md, + }, + + // Chart scroll view + chartScrollView: { + // Removed maxHeight to allow full content to be visible + }, + + // Chart content + chartContent: { + alignItems: 'center', + paddingHorizontal: theme.spacing.sm, + }, + + // Pie chart container + pieChartContainer: { + alignItems: 'center', + marginBottom: theme.spacing.lg, + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md, + }, + + // Legend container + legendContainer: { + width: '100%', + paddingHorizontal: theme.spacing.sm, + backgroundColor: theme.colors.backgroundAlt, + borderRadius: theme.borderRadius.medium, + padding: theme.spacing.md, + marginTop: theme.spacing.md, + }, + + // Legend title + legendTitle: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.semibold, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.md, + textAlign: 'center', + }, + + // Legend grid container + legendGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: theme.spacing.sm, + }, + + // Legend grid item + legendGridItem: { + width: '48%', + backgroundColor: theme.colors.background, + borderRadius: theme.borderRadius.small, + padding: theme.spacing.sm, + borderWidth: 1, + borderColor: theme.colors.border, + marginBottom: theme.spacing.xs, + }, + + // Legend item header + legendItemHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: theme.spacing.xs, + }, + + // Legend color + legendColor: { + width: 12, + height: 12, + borderRadius: 6, + marginRight: theme.spacing.xs, + }, + + // Legend label + legendLabel: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.semibold, + color: theme.colors.textPrimary, + flex: 1, + }, + + // Legend count + legendCount: { + fontSize: theme.typography.fontSize.caption, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textPrimary, + marginBottom: theme.spacing.xs, + }, + + // Legend confidence + legendConfidence: { + fontSize: theme.typography.fontSize.caption, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // No data container + noDataContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: theme.spacing.md, + }, + + // No data text + noDataText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + }, + + // Summary container + summaryContainer: { + flexDirection: 'row', + justifyContent: 'space-around', + paddingTop: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + + // Summary item + summaryItem: { + alignItems: 'center', + }, + + // Summary label + summaryLabel: { + fontSize: theme.typography.fontSize.bodySmall, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginBottom: theme.spacing.xs, + }, + + // Summary value + summaryValue: { + fontSize: theme.typography.fontSize.displaySmall, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + }, + + // Error container + errorContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: theme.spacing.md, + backgroundColor: theme.colors.backgroundAlt, + borderRadius: theme.borderRadius.medium, + }, + + // Error text + errorText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + }, +}); + +/* + * End of File: BrainPredictionsOverview.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/modules/Dashboard/components/CriticalAlerts.tsx b/app/modules/Dashboard/components/CriticalAlerts.tsx index 157b4f9..3a2a4d6 100644 --- a/app/modules/Dashboard/components/CriticalAlerts.tsx +++ b/app/modules/Dashboard/components/CriticalAlerts.tsx @@ -119,8 +119,9 @@ const styles = StyleSheet.create({ borderRadius: theme.borderRadius.medium, padding: theme.spacing.md, marginRight: theme.spacing.md, - minWidth: 280, + // minWidth: 280, ...theme.shadows.small, + width:280 }, alertHeader: { flexDirection: 'row', diff --git a/app/modules/Dashboard/components/index.ts b/app/modules/Dashboard/components/index.ts new file mode 100644 index 0000000..b000aea --- /dev/null +++ b/app/modules/Dashboard/components/index.ts @@ -0,0 +1,6 @@ +export { PatientCard } from './PatientCard'; +export { CriticalAlerts } from './CriticalAlerts'; +export { DashboardHeader } from './DashboardHeader'; +export { QuickActions } from './QuickActions'; +export { DepartmentStats } from './DepartmentStats'; +export { BrainPredictionsOverview } from './BrainPredictionsOverview'; \ No newline at end of file diff --git a/app/modules/Dashboard/screens/ERDashboardScreen.tsx b/app/modules/Dashboard/screens/ERDashboardScreen.tsx index 822619d..be7c4d4 100644 --- a/app/modules/Dashboard/screens/ERDashboardScreen.tsx +++ b/app/modules/Dashboard/screens/ERDashboardScreen.tsx @@ -1,6 +1,6 @@ /* * File: ERDashboardScreen.tsx - * Description: Main ER dashboard screen displaying patient list, critical alerts, and department statistics + * Description: Brain AI Analysis Dashboard - Main dashboard for neurological AI predictions and brain imaging analysis * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ @@ -22,7 +22,7 @@ import { PatientCard } from '../components/PatientCard'; import { CriticalAlerts } from '../components/CriticalAlerts'; import { DashboardHeader } from '../components/DashboardHeader'; import { QuickActions } from '../components/QuickActions'; -import { DepartmentStats } from '../components/DepartmentStats'; +import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview'; /** * ERDashboardScreenProps Interface @@ -36,30 +36,52 @@ interface ERDashboardScreenProps { navigation: any; } +/** + * Brain AI Prediction Types + * + * Purpose: Defines the different types of brain conditions that AI can predict + */ +type BrainCondition = + | 'Intraparenchymal hemorrhage (IPH)' + | 'Intraventricular hemorrhage (IVH)' + | 'Subarachnoid hemorrhage (SAH)' + | 'Subdural hematoma (SDH)' + | 'Epidural hematoma (EDH)' + | 'Mass effect' + | 'Midline shift' + | 'Intracranial hemorrhage (ICH)' + | 'Stroke (ischemic)' + | 'Normal brain'; + /** * ERDashboardScreen Component * - * Purpose: Main dashboard screen for Emergency Department physicians + * Purpose: Brain AI Analysis Dashboard for Emergency Department physicians * * Dashboard Features: - * 1. Real-time patient overview with filtering capabilities - * 2. Critical alerts display for immediate attention - * 3. Quick action buttons for common tasks - * 4. Department statistics and bed occupancy + * 1. Real-time brain scan analysis with AI predictions + * 2. Critical neurological alerts for immediate attention + * 3. Quick action buttons for brain imaging tasks + * 4. Department statistics focused on neurological cases * 5. Pull-to-refresh functionality for live updates - * 6. Internal data generation for demonstration + * 6. AI prediction confidence scores and analysis * - * Patient Filtering: - * - All: Shows all patients in the system - * - Critical: Shows only patients with critical priority - * - Active: Shows only patients with active status + * Brain Condition Filtering: + * - All: Shows all brain scan cases + * - Critical: Shows only cases with critical brain conditions + * - Pending: Shows cases awaiting AI analysis * - * Data Flow: - * 1. Generate mock data internally for demonstration - * 2. Filter patients based on selected filter - * 3. Display critical alerts at the top - * 4. Show patient cards in scrollable list - * 5. Handle refresh and navigation interactions + * AI Prediction Classes: + * - Intraparenchymal hemorrhage (IPH) + * - Intraventricular hemorrhage (IVH) + * - Subarachnoid hemorrhage (SAH) + * - Subdural hematoma (SDH) + * - Epidural hematoma (EDH) + * - Mass effect + * - Midline shift + * - Intracranial hemorrhage (ICH) + * - Stroke (ischemic) + * - Normal brain */ export const ERDashboardScreen: React.FC = ({ navigation, @@ -71,8 +93,8 @@ export const ERDashboardScreen: React.FC = ({ // Refresh state for pull-to-refresh functionality const [refreshing, setRefreshing] = useState(false); - // Patient filter state to control which patients are displayed - const [selectedFilter, setSelectedFilter] = useState<'all' | 'critical' | 'active'>('all'); + // Patient filter state to control which brain cases are displayed + const [selectedFilter, setSelectedFilter] = useState<'all' | 'critical' | 'pending'>('all'); // Dashboard data state const [dashboard, setDashboard] = useState(null); @@ -87,31 +109,31 @@ export const ERDashboardScreen: React.FC = ({ /** * generateMockDashboard Function * - * Purpose: Generate mock dashboard data for demonstration + * Purpose: Generate mock dashboard data focused on brain AI analysis * - * Returns: ERDashboard object with realistic ER statistics + * Returns: ERDashboard object with brain imaging statistics */ const generateMockDashboard = (): ERDashboard => ({ - totalPatients: 24, // Total number of patients in ER - criticalPatients: 3, // Number of patients requiring immediate attention - pendingScans: 8, // Number of scans waiting for review - recentReports: 12, // Number of reports generated recently - bedOccupancy: 85, // Percentage of beds currently occupied + totalPatients: 18, // Total number of brain scan cases + criticalPatients: 5, // Number of critical brain conditions + pendingScans: 6, // Number of scans awaiting AI analysis + recentReports: 12, // Number of AI analysis reports generated + bedOccupancy: 78, // Percentage of neurological beds occupied departmentStats: { - emergency: 8, // Patients in emergency department - trauma: 4, // Patients in trauma department - cardiac: 3, // Patients in cardiac department - neurology: 2, // Patients in neurology department - pediatrics: 5, // Patients in pediatrics department - icu: 2, // Patients in ICU + emergency: 8, // Emergency brain cases + trauma: 4, // Traumatic brain injury cases + cardiac: 3, // Stroke cases (using cardiac as placeholder) + neurology: 2, // General neurology cases + pediatrics: 1, // Neurosurgery cases (using pediatrics as placeholder) + icu: 0, // ICU brain cases }, shiftInfo: { currentShift: 'DAY' as const, // Current shift (DAY/NIGHT) startTime: new Date(), // Shift start time endTime: new Date(), // Shift end time - attendingPhysician: 'Dr. Smith', // Lead physician on duty - residents: ['Dr. Johnson', 'Dr. Williams'], // Resident physicians - nurses: ['Nurse Brown', 'Nurse Davis'], // Nursing staff + attendingPhysician: 'Dr. Smith', // Lead neurologist on duty + residents: ['Dr. Johnson', 'Dr. Williams'], // Neurology residents + nurses: ['Nurse Brown', 'Nurse Davis'], // Neurology nursing staff }, lastUpdated: new Date(), // Last time dashboard was updated }); @@ -119,30 +141,30 @@ export const ERDashboardScreen: React.FC = ({ /** * generateMockPatients Function * - * Purpose: Generate mock patient data for demonstration + * Purpose: Generate mock patient data focused on brain conditions and AI predictions * - * Returns: Array of Patient objects with realistic medical data + * Returns: Array of Patient objects with brain-related medical data */ const generateMockPatients = (): Patient[] => [ { - id: '1', // Unique patient identifier - mrn: 'MRN001', // Medical Record Number + id: '1', + mrn: 'MRN001', firstName: 'John', lastName: 'Doe', dateOfBirth: new Date('1985-03-15'), gender: 'MALE' as const, age: 38, - bedNumber: 'A1', // Assigned bed number - roomNumber: '101', // Room number + bedNumber: 'A1', + roomNumber: '101', admissionDate: new Date('2024-01-15'), - status: 'ACTIVE' as const, // Current patient status - priority: 'CRITICAL' as const, // Priority level for treatment + status: 'ACTIVE' as const, + priority: 'CRITICAL' as const, department: 'Emergency', attendingPhysician: 'Dr. Smith', allergies: [ { id: '1', - name: 'Penicillin', + name: 'Contrast Dye', severity: 'SEVERE' as const, reaction: 'Anaphylaxis' }, @@ -150,24 +172,24 @@ export const ERDashboardScreen: React.FC = ({ medications: [ { id: '1', - name: 'Morphine', - dosage: '2mg', - frequency: 'Every 4 hours', - route: 'IV', // Administration route + name: 'Mannitol', + dosage: '100ml', + frequency: 'Every 6 hours', + route: 'IV', startDate: new Date(), status: 'ACTIVE' as const, prescribedBy: 'Dr. Smith', }, ], vitalSigns: { - bloodPressure: { systolic: 140, diastolic: 90, timestamp: new Date() }, + bloodPressure: { systolic: 180, diastolic: 110, timestamp: new Date() }, heartRate: { value: 95, timestamp: new Date() }, temperature: { value: 37.2, timestamp: new Date() }, respiratoryRate: { value: 18, timestamp: new Date() }, oxygenSaturation: { value: 98, timestamp: new Date() }, }, medicalHistory: [], - currentDiagnosis: 'Chest pain, rule out MI', // Current medical diagnosis + currentDiagnosis: 'AI Predicted: Intraparenchymal hemorrhage (IPH) - 94% confidence', lastUpdated: new Date(), }, { @@ -182,20 +204,31 @@ export const ERDashboardScreen: React.FC = ({ roomNumber: '102', admissionDate: new Date('2024-01-15'), status: 'ACTIVE' as const, - priority: 'HIGH' as const, + priority: 'CRITICAL' as const, department: 'Trauma', attendingPhysician: 'Dr. Johnson', allergies: [], - medications: [], + medications: [ + { + id: '2', + name: 'Dexamethasone', + dosage: '4mg', + frequency: 'Every 6 hours', + route: 'IV', + startDate: new Date(), + status: 'ACTIVE' as const, + prescribedBy: 'Dr. Johnson', + }, + ], vitalSigns: { - bloodPressure: { systolic: 120, diastolic: 80, timestamp: new Date() }, + bloodPressure: { systolic: 160, diastolic: 90, timestamp: new Date() }, heartRate: { value: 88, timestamp: new Date() }, temperature: { value: 36.8, timestamp: new Date() }, respiratoryRate: { value: 16, timestamp: new Date() }, oxygenSaturation: { value: 99, timestamp: new Date() }, }, medicalHistory: [], - currentDiagnosis: 'Multiple trauma from MVA', // MVA = Motor Vehicle Accident + currentDiagnosis: 'AI Predicted: Subdural hematoma (SDH) with mass effect - 89% confidence', lastUpdated: new Date(), }, { @@ -210,8 +243,8 @@ export const ERDashboardScreen: React.FC = ({ roomNumber: '103', admissionDate: new Date('2024-01-15'), status: 'PENDING' as const, - priority: 'MEDIUM' as const, - department: 'Cardiac', + priority: 'HIGH' as const, + department: 'Stroke', attendingPhysician: 'Dr. Williams', allergies: [ { @@ -223,25 +256,25 @@ export const ERDashboardScreen: React.FC = ({ ], medications: [ { - id: '2', - name: 'Nitroglycerin', - dosage: '0.4mg', - frequency: 'As needed', - route: 'Sublingual', + id: '3', + name: 'tPA', + dosage: '0.9mg/kg', + frequency: 'Single dose', + route: 'IV', startDate: new Date(), status: 'ACTIVE' as const, prescribedBy: 'Dr. Williams', }, ], vitalSigns: { - bloodPressure: { systolic: 160, diastolic: 100, timestamp: new Date() }, + bloodPressure: { systolic: 140, diastolic: 85, timestamp: new Date() }, heartRate: { value: 110, timestamp: new Date() }, temperature: { value: 36.9, timestamp: new Date() }, respiratoryRate: { value: 20, timestamp: new Date() }, oxygenSaturation: { value: 95, timestamp: new Date() }, }, medicalHistory: [], - currentDiagnosis: 'Hypertension, chest pain', + currentDiagnosis: 'AI Predicted: Stroke (ischemic) - 92% confidence', lastUpdated: new Date(), }, { @@ -257,15 +290,15 @@ export const ERDashboardScreen: React.FC = ({ admissionDate: new Date('2024-01-15'), status: 'ACTIVE' as const, priority: 'CRITICAL' as const, - department: 'Neurology', + department: 'Neurosurgery', attendingPhysician: 'Dr. Davis', allergies: [], medications: [ { - id: '3', - name: 'Mannitol', - dosage: '100ml', - frequency: 'Every 6 hours', + id: '4', + name: 'Phenytoin', + dosage: '100mg', + frequency: 'Every 8 hours', route: 'IV', startDate: new Date(), status: 'ACTIVE' as const, @@ -273,14 +306,14 @@ export const ERDashboardScreen: React.FC = ({ }, ], vitalSigns: { - bloodPressure: { systolic: 180, diastolic: 110, timestamp: new Date() }, + bloodPressure: { systolic: 190, diastolic: 120, timestamp: new Date() }, heartRate: { value: 60, timestamp: new Date() }, temperature: { value: 38.5, timestamp: new Date() }, respiratoryRate: { value: 12, timestamp: new Date() }, oxygenSaturation: { value: 92, timestamp: new Date() }, }, medicalHistory: [], - currentDiagnosis: 'Suspected intracranial hemorrhage', + currentDiagnosis: 'AI Predicted: Epidural hematoma (EDH) with midline shift - 96% confidence', lastUpdated: new Date(), }, { @@ -296,7 +329,7 @@ export const ERDashboardScreen: React.FC = ({ admissionDate: new Date('2024-01-15'), status: 'ACTIVE' as const, priority: 'HIGH' as const, - department: 'Pediatrics', + department: 'Neurology', attendingPhysician: 'Dr. Brown', allergies: [ { @@ -308,14 +341,42 @@ export const ERDashboardScreen: React.FC = ({ ], medications: [], vitalSigns: { - bloodPressure: { systolic: 110, diastolic: 70, timestamp: new Date() }, + bloodPressure: { systolic: 130, diastolic: 80, timestamp: new Date() }, heartRate: { value: 85, timestamp: new Date() }, temperature: { value: 37.8, timestamp: new Date() }, respiratoryRate: { value: 22, timestamp: new Date() }, oxygenSaturation: { value: 97, timestamp: new Date() }, }, medicalHistory: [], - currentDiagnosis: 'Fever of unknown origin', + currentDiagnosis: 'AI Predicted: Subarachnoid hemorrhage (SAH) - 87% confidence', + lastUpdated: new Date(), + }, + { + id: '6', + mrn: 'MRN006', + firstName: 'Emily', + lastName: 'Johnson', + dateOfBirth: new Date('1988-12-05'), + gender: 'FEMALE' as const, + age: 35, + bedNumber: 'F6', + roomNumber: '106', + admissionDate: new Date('2024-01-15'), + status: 'PENDING' as const, + priority: 'MEDIUM' as const, + department: 'Emergency', + attendingPhysician: 'Dr. Wilson', + allergies: [], + medications: [], + vitalSigns: { + bloodPressure: { systolic: 120, diastolic: 75, timestamp: new Date() }, + heartRate: { value: 72, timestamp: new Date() }, + temperature: { value: 37.0, timestamp: new Date() }, + respiratoryRate: { value: 16, timestamp: new Date() }, + oxygenSaturation: { value: 99, timestamp: new Date() }, + }, + medicalHistory: [], + currentDiagnosis: 'AI Analysis Pending - CT scan uploaded', lastUpdated: new Date(), }, ]; @@ -323,31 +384,31 @@ export const ERDashboardScreen: React.FC = ({ /** * generateMockAlerts Function * - * Purpose: Generate mock alert data for demonstration + * Purpose: Generate mock alert data focused on brain AI predictions * - * Returns: Array of Alert objects with realistic medical alerts + * Returns: Array of Alert objects with brain-related alerts */ const generateMockAlerts = (): AlertType[] => [ { id: '1', - type: 'CRITICAL_FINDING' as const, // Type of alert - priority: 'CRITICAL' as const, // Priority level - title: 'Critical Finding Detected', - message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.', - patientId: '4', // Associated patient - patientName: 'Sarah Wilson', - bedNumber: 'D4', - timestamp: new Date(), // When alert was generated - isRead: false, // Whether alert has been read - isAcknowledged: false, // Whether alert has been acknowledged - actionRequired: true, // Whether action is required + type: 'CRITICAL_FINDING' as const, + priority: 'CRITICAL' as const, + title: 'Critical Brain Finding Detected', + message: 'AI has detected Intraparenchymal hemorrhage (IPH) with 94% confidence. Immediate neurosurgical consultation required.', + patientId: '1', + patientName: 'John Doe', + bedNumber: 'A1', + timestamp: new Date(), + isRead: false, + isAcknowledged: false, + actionRequired: true, }, { id: '2', type: 'VITAL_SIGNS_ALERT' as const, priority: 'HIGH' as const, - title: 'Vital Sign Alert', - message: 'Blood pressure elevated: 180/110. Patient requires immediate attention.', + title: 'Elevated ICP Alert', + message: 'Patient Sarah Wilson showing signs of elevated intracranial pressure. BP: 190/120, HR: 60. Immediate intervention needed.', patientId: '4', patientName: 'Sarah Wilson', bedNumber: 'D4', @@ -360,16 +421,30 @@ export const ERDashboardScreen: React.FC = ({ id: '3', type: 'MEDICATION_ALERT' as const, priority: 'MEDIUM' as const, - title: 'Medication Due', - message: 'Morphine due for patient John Doe in 15 minutes.', - patientId: '1', - patientName: 'John Doe', - bedNumber: 'A1', + title: 'AI Analysis Complete', + message: 'Brain CT analysis complete for Emily Johnson. Results: Normal brain - 98% confidence. No intervention required.', + patientId: '6', + patientName: 'Emily Johnson', + bedNumber: 'F6', timestamp: new Date(Date.now() - 600000), // 10 minutes ago isRead: true, isAcknowledged: true, actionRequired: false, }, + { + id: '4', + type: 'CRITICAL_FINDING' as const, + priority: 'CRITICAL' as const, + title: 'Stroke Alert - Time Sensitive', + message: 'Michael Brown diagnosed with ischemic stroke. tPA window closing. Immediate thrombolytic therapy required.', + patientId: '3', + patientName: 'Michael Brown', + bedNumber: 'C3', + timestamp: new Date(Date.now() - 120000), // 2 minutes ago + isRead: false, + isAcknowledged: false, + actionRequired: true, + }, ]; // ============================================================================ @@ -380,19 +455,13 @@ export const ERDashboardScreen: React.FC = ({ * useEffect for initial data loading * * Purpose: Load initial mock data when component mounts - * - * Flow: - * 1. Set loading state to true - * 2. Generate mock data - * 3. Set data in state - * 4. Set loading state to false */ useEffect(() => { const loadInitialData = async () => { setIsLoading(true); // Simulate API call delay - await new Promise(resolve => setTimeout(resolve, 1000)); + setTimeout(() => {}, 1000); // Generate and set mock data setDashboard(generateMockDashboard()); @@ -413,25 +482,19 @@ export const ERDashboardScreen: React.FC = ({ * handleRefresh Function * * Purpose: Handle pull-to-refresh functionality to update dashboard data - * - * Flow: - * 1. Set refreshing state to true (show loading indicator) - * 2. Simulate API call with delay - * 3. Update data with fresh mock data - * 4. Set refreshing state to false (hide loading indicator) */ const handleRefresh = async () => { - setRefreshing(true); // Show refresh indicator + setRefreshing(true); // Simulate API call with 1-second delay - await new Promise(resolve => setTimeout(resolve, 1000)); + setTimeout(() => {}, 1000); // Update data with fresh mock data setDashboard(generateMockDashboard()); setPatients(generateMockPatients()); setAlerts(generateMockAlerts()); - setRefreshing(false); // Hide refresh indicator + setRefreshing(false); }; /** @@ -442,9 +505,8 @@ export const ERDashboardScreen: React.FC = ({ * @param patient - Patient object that was pressed */ const handlePatientPress = (patient: Patient) => { - // TODO: Navigate to patient details screen console.log('Patient pressed:', patient.firstName, patient.lastName); - Alert.alert('Patient Details', `Navigate to ${patient.firstName} ${patient.lastName}'s details`); + Alert.alert('Brain Analysis Details', `Navigate to ${patient.firstName} ${patient.lastName}'s brain scan analysis`); }; /** @@ -455,9 +517,8 @@ export const ERDashboardScreen: React.FC = ({ * @param alert - Alert object that was pressed */ const handleAlertPress = (alert: AlertType) => { - // TODO: Navigate to alert details or patient details console.log('Alert pressed:', alert.title); - Alert.alert('Alert Details', alert.message); + Alert.alert('Brain Alert Details', alert.message); }; // ============================================================================ @@ -470,18 +531,18 @@ export const ERDashboardScreen: React.FC = ({ * Purpose: Filter patients based on selected filter criteria * * Filter Options: - * - 'all': Show all patients regardless of status or priority - * - 'critical': Show only patients with CRITICAL priority - * - 'active': Show only patients with ACTIVE status + * - 'all': Show all brain scan cases + * - 'critical': Show only cases with CRITICAL priority + * - 'pending': Show only cases with PENDING status (awaiting AI analysis) */ const filteredPatients = patients.filter(patient => { switch (selectedFilter) { case 'critical': - return patient.priority === 'CRITICAL'; // Only critical priority patients - case 'active': - return patient.status === 'ACTIVE'; // Only active status patients + return patient.priority === 'CRITICAL'; + case 'pending': + return patient.status === 'PENDING'; default: - return true; // All patients + return true; } }); @@ -489,21 +550,15 @@ export const ERDashboardScreen: React.FC = ({ * criticalAlerts - Computed property * * Purpose: Extract critical alerts from all alerts for immediate display - * - * Filter: Only alerts with CRITICAL priority that require immediate attention */ const criticalAlerts = alerts.filter(alert => alert.priority === 'CRITICAL'); /** * pendingScans - Computed property * - * Purpose: Identify patients with pending scans or high priority needs - * - * Filter: Patients with PENDING status or HIGH priority + * Purpose: Identify patients with pending AI analysis */ - const pendingScans = patients.filter(patient => - patient.status === 'PENDING' || patient.priority === 'HIGH' - ); + const pendingScans = patients.filter(patient => patient.status === 'PENDING'); // ============================================================================ // RENDER FUNCTIONS @@ -520,7 +575,7 @@ export const ERDashboardScreen: React.FC = ({ const renderPatientCard = ({ item }: { item: Patient }) => ( handlePatientPress(item)} // Navigate to patient details + onPress={() => handlePatientPress(item)} /> ); @@ -528,13 +583,6 @@ export const ERDashboardScreen: React.FC = ({ * renderHeader Function * * Purpose: Render the dashboard header section with all dashboard components - * - * Components included: - * - DashboardHeader: Shows shift info and key statistics - * - CriticalAlerts: Displays urgent alerts requiring attention - * - QuickActions: Provides quick access to common functions - * - DepartmentStats: Shows department-wise patient distribution - * - Filter buttons: Allow filtering of patient list */ const renderHeader = () => ( @@ -545,26 +593,25 @@ export const ERDashboardScreen: React.FC = ({ {criticalAlerts.length > 0 && ( )} - {/* Quick action buttons for common tasks */} + {/* Quick action buttons for brain imaging tasks */} { - // TODO: Implement quick action handlers console.log('Quick action:', action); }} /> - {/* Department statistics showing patient distribution */} - {dashboard && } + {/* Department statistics showing brain case distribution */} + {dashboard && } - {/* Patient filter section with filter buttons */} + {/* Brain case filter section with filter buttons */} - Patients + Brain Scan Cases - {/* All patients filter button */} + {/* All cases filter button */} = ({ - {/* Critical patients filter button */} + {/* Critical cases filter button */} = ({ - {/* Active patients filter button */} + {/* Pending AI analysis filter button */} setSelectedFilter('active')} + onPress={() => setSelectedFilter('pending')} > - Active ({patients.filter(p => p.status === 'ACTIVE').length}) + Pending AI ({patients.filter(p => p.status === 'PENDING').length}) @@ -628,7 +675,7 @@ export const ERDashboardScreen: React.FC = ({ if (isLoading) { return ( - Loading Dashboard... + Loading Brain AI Dashboard... ); } @@ -641,20 +688,20 @@ export const ERDashboardScreen: React.FC = ({ {/* FlatList for efficient rendering of patient cards */} item.id} // Unique key for each patient - ListHeaderComponent={renderHeader} // Header with dashboard components - contentContainerStyle={styles.listContainer} // Container styling + data={filteredPatients} + renderItem={renderPatientCard} + keyExtractor={(item) => item.id} + ListHeaderComponent={renderHeader} + contentContainerStyle={styles.listContainer} refreshControl={ } - showsVerticalScrollIndicator={false} // Hide scroll indicator for cleaner look + showsVerticalScrollIndicator={false} /> ); @@ -688,18 +735,18 @@ const styles = StyleSheet.create({ // Container for the FlatList content listContainer: { - paddingBottom: theme.spacing.lg, // Add bottom padding for tab bar + paddingBottom: theme.spacing.lg, }, // Header section containing dashboard components header: { - paddingHorizontal: theme.spacing.md, // Horizontal padding for content + paddingHorizontal: theme.spacing.md, }, // Container for patient filter section filterContainer: { - marginTop: theme.spacing.lg, // Top margin for separation - marginBottom: theme.spacing.md, // Bottom margin for list spacing + marginTop: theme.spacing.lg, + marginBottom: theme.spacing.md, }, // Section title styling @@ -712,8 +759,8 @@ const styles = StyleSheet.create({ // Container for filter buttons filterButtons: { - flexDirection: 'row', // Horizontal layout - gap: theme.spacing.sm, // Space between buttons + flexDirection: 'row', + gap: theme.spacing.sm, }, // Individual filter button styling @@ -728,8 +775,8 @@ const styles = StyleSheet.create({ // Active filter button styling filterButtonActive: { - backgroundColor: theme.colors.primary, // Primary color background - borderColor: theme.colors.primary, // Primary color border + backgroundColor: theme.colors.primary, + borderColor: theme.colors.primary, }, // Filter button text styling @@ -741,7 +788,7 @@ const styles = StyleSheet.create({ // Active filter button text styling filterButtonTextActive: { - color: theme.colors.background, // White text on primary background + color: theme.colors.background, }, }); diff --git a/app/modules/Settings/screens/SettingsScreen.tsx b/app/modules/Settings/screens/SettingsScreen.tsx index 5c0295a..58f2643 100644 --- a/app/modules/Settings/screens/SettingsScreen.tsx +++ b/app/modules/Settings/screens/SettingsScreen.tsx @@ -445,7 +445,7 @@ export const SettingsScreen: React.FC = ({ {user.display_name || `${user.first_name} ${user.last_name}`} {user.email} - {user.dashboard_role} + Physician diff --git a/app/navigation/MainTabNavigator.tsx b/app/navigation/MainTabNavigator.tsx index 69b5ce4..53ced11 100644 --- a/app/navigation/MainTabNavigator.tsx +++ b/app/navigation/MainTabNavigator.tsx @@ -12,6 +12,7 @@ import { DashboardStackNavigator } from '../modules/Dashboard/navigation'; import { SettingsStackNavigator } from '../modules/Settings/navigation'; import { MainTabParamList } from './navigationTypes'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; +import { ComingSoonScreen } from '../shared/components'; // Create the bottom tab navigator const Tab = createBottomTabNavigator(); @@ -82,7 +83,7 @@ export const MainTabNavigator: React.FC = () => { {/* Patients Tab - Patient list and management */} { {/* Alerts Tab - Critical notifications */} { /> {/* Reports Tab - Medical documentation */} - { ), headerShown: false, }} - /> + /> */} {/* Settings Tab - User preferences */} void; + showBackButton?: boolean; + backgroundColor?: string; + textColor?: string; + imageComponent?: React.ReactNode; +} + +// ============================================================================ +// COMING SOON SCREEN COMPONENT +// ============================================================================ + +/** + * ComingSoonScreen Component + * + * Purpose: Displays a coming soon message with space for SVG image + * + * Features: + * 1. Customizable title and subtitle + * 2. Space allocated for SVG image + * 3. Optional back button with navigation + * 4. Customizable colors and styling + * 5. Responsive design for different screen sizes + * 6. Safe area handling + * 7. Status bar management + * 8. Scrollable content for smaller screens + * + * Usage: + * - For features under development + * - Placeholder screens during app development + * - Maintenance or update screens + * - Feature announcements + */ +export const ComingSoonScreen: React.FC = ({ + title = 'Coming Soon', + subtitle = 'We\'re working hard to bring you something amazing. Stay tuned for updates!', + onBack, + showBackButton = true, + backgroundColor, + textColor, + imageComponent, +}) => { + // ============================================================================ + // HELPER FUNCTIONS + // ============================================================================ + + /** + * handleBack Function + * + * Purpose: Handle back navigation if onBack function is provided + */ + const handleBack = () => { + if (onBack) { + onBack(); + } + }; + + // ============================================================================ + // RENDER SECTION + // ============================================================================ + + return ( + + + + + {/* Header with Back Button */} + {showBackButton && onBack && ( + + + + + + )} + + {/* Main Content */} + + {/* SVG Image Container */} + + {imageComponent ? ( + imageComponent + ) : ( + + + + )} + + + {/* Title */} + + {title} + + + {/* Subtitle */} + + {subtitle} + + + + {/* Action Buttons */} + + + + Get Notified + + + + + + Learn More + + + + + + + ); +}; + +// ============================================================================ +// STYLES SECTION +// ============================================================================ + +const styles = StyleSheet.create({ + // Main container + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Scroll view + scrollView: { + flex: 1, + }, + + // Scroll content + scrollContent: { + flexGrow: 1, + paddingHorizontal: theme.spacing.lg, + }, + + // Header section + header: { + flexDirection: 'row', + alignItems: 'center', + paddingTop: theme.spacing.lg, + paddingBottom: theme.spacing.md, + }, + + // Back button + backButton: { + padding: theme.spacing.sm, + borderRadius: theme.borderRadius.medium, + backgroundColor: theme.colors.backgroundAlt, + ...theme.shadows.small, + }, + + // Main content + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingVertical: theme.spacing.xxl, + }, + + // Image container + imageContainer: { + width: 280, + height: 280, + borderRadius: theme.borderRadius.large, + backgroundColor: theme.colors.backgroundAlt, + justifyContent: 'center', + alignItems: 'center', + marginBottom: theme.spacing.xl, + ...theme.shadows.medium, + }, + + // Image placeholder + imagePlaceholder: { + width: '100%', + height: '100%', + alignItems: 'center', + justifyContent: 'center', + }, + + // Placeholder text + placeholderText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + marginTop: theme.spacing.sm, + textAlign: 'center', + }, + + // Title + title: { + fontSize: theme.typography.fontSize.displayLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.textPrimary, + textAlign: 'center', + marginBottom: theme.spacing.md, + lineHeight: theme.typography.fontSize.displayLarge * 1.2, + }, + + // Subtitle + subtitle: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.regular, + color: theme.colors.textSecondary, + textAlign: 'center', + marginBottom: theme.spacing.xl, + lineHeight: theme.typography.fontSize.bodyLarge * 1.4, + paddingHorizontal: theme.spacing.md, + }, + + // Info container + infoContainer: { + marginBottom: theme.spacing.xl, + width: '100%', + }, + + // Info item + infoItem: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.spacing.sm, + }, + + // Info text + infoText: { + fontSize: theme.typography.fontSize.bodyMedium, + fontFamily: theme.typography.fontFamily.medium, + color: theme.colors.textSecondary, + marginLeft: theme.spacing.sm, + }, + + // Button container + buttonContainer: { + width: '100%', + gap: theme.spacing.md, + }, + + // Base button + button: { + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + borderRadius: theme.borderRadius.medium, + alignItems: 'center', + justifyContent: 'center', + minHeight: 48, + ...theme.shadows.primary, + }, + + // Primary button + primaryButton: { + backgroundColor: theme.colors.primary, + }, + + // Secondary button + secondaryButton: { + backgroundColor: 'white', + borderWidth: 2, + borderColor: theme.colors.primary, + }, + + // Button text + buttonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.background, + }, + + // Secondary button text + secondaryButtonText: { + fontSize: theme.typography.fontSize.bodyLarge, + fontFamily: theme.typography.fontFamily.bold, + color: theme.colors.primary, + }, +}); + +export default ComingSoonScreen; + +/* + * End of File: ComingSoonScreen.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ \ No newline at end of file diff --git a/app/shared/components/index.ts b/app/shared/components/index.ts index 5166a25..1eca321 100644 --- a/app/shared/components/index.ts +++ b/app/shared/components/index.ts @@ -5,7 +5,22 @@ * Copyright (c) Spurrin Innovations. All rights reserved. */ -export { CustomModal } from './CustomModal'; +// ============================================================================ +// SHARED COMPONENTS EXPORTS +// ============================================================================ + +// Custom Modal Component +export { CustomModal } from './CustomModal'; + +// Coming Soon Screen Component +export { ComingSoonScreen } from './ComingSoonScreen'; + +// ============================================================================ +// TYPE EXPORTS +// ============================================================================ + +// Export types for components +// export type { CustomModalProps } from './CustomModal'; /* * End of File: index.ts diff --git a/app/shared/types/auth.ts b/app/shared/types/auth.ts index df6396a..21db44d 100644 --- a/app/shared/types/auth.ts +++ b/app/shared/types/auth.ts @@ -43,6 +43,7 @@ export interface User { onboarding_step: number; onboarding_message: string; access_token: string; + platform: 'app'|'web'; } export type UserRole = diff --git a/ios/NeoScan_Physician/Info.plist b/ios/NeoScan_Physician/Info.plist index bdd43aa..cd25970 100644 --- a/ios/NeoScan_Physician/Info.plist +++ b/ios/NeoScan_Physician/Info.plist @@ -58,5 +58,8 @@ Roboto-Regular.ttf Roboto-SemiBold.ttf + NSCameraUsageDescription + This app needs access to the camera to take photos and videos. + diff --git a/package-lock.json b/package-lock.json index a9785d7..838064c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11968,9 +11968,9 @@ } }, "node_modules/react-native-svg": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.0.tgz", - "integrity": "sha512-iE25PxIJ6V0C6krReLquVw6R0QTsRTmEQc4K2Co3P6zsimU/jltcDBKYDy1h/5j9S/fqmMeXnpM+9LEWKJKI6A==", + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", + "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==", "license": "MIT", "dependencies": { "css-select": "^5.1.0",