code cleaned and user logout on expiry token added

This commit is contained in:
yashwin-foxy 2025-08-26 19:02:52 +05:30
parent da16f0cc25
commit 4f5d5970fb
59 changed files with 616 additions and 11336 deletions

View File

@ -1,351 +1,173 @@
---
alwaysApply: true
---
# Physician App - Application Flow Rules
Physician App Flow Guide
1. Onboarding Flow
There are two ways for a physician to join the platform:
A. Self-Registration (via App)
## 🚀 App Launch & Authentication Flow
Start App Signup → Physician enters email address
Email Check
### 1. Initial App Launch
```
App Launch → Splash Screen → Authentication Check
If email already exists → Show message "Already Registered" → Redirect to Login
If new email → Continue to next step
Set Password → Create secure password
Enter Personal Info → Complete profile information
Upload ID Proof → Verify credentials
Select Hospital → Choose affiliated hospital → Wait for admin approval
Note: Upon self-registration, a physician's status will be set to 'Inactive' by default. The hospital administrator must review and approve the registration by changing the status to 'Active'.
After Review → Login → Reset Password → Dashboard
B. Admin-Created Physician
Admin creates physician account in the system
Login credentials sent to physician's registered email address
Physician logs in using received credentials
Upload ID Proof → Complete verification
Reset Password → Access Dashboard
2. Dashboard Overview
When the physician logs in, they land on the main Dashboard displaying:
Total Predictions (e.g., 24 AI analyses performed)
Total Patients (unique patients analyzed)
Feedback Rate (% of predictions validated by radiologist)
Average Confidence (average AI confidence score)
Confidence Score Distribution (High, Medium, Low confidence statistics)
Case Lists categorized as:
Radiologist Reviewed
Pending Review
3. Patients List
Accessible from the Patients Tab.
Features:
Displays all patients processed by the AI system
Each patient card includes:
Patient ID
Files uploaded (DICOM format)
Processing Status (Processed / Pending)
Report availability status
Filtering Options:
All Cases
Processed
Pending
4. Patient Details
Clicking a patient opens the Patient Details Page with:
Patient Information:
Patient ID & Basic Info
Navigation Tabs:
Overview - General patient summary
AI Analysis - Predictions and series details
Feedbacks - Radiologist feedback and notes
AI Analysis Tab Includes:
Total series uploaded
AI predictions count
Processing status
Preview of DICOM images
Prediction results with:
Severity level
Category classification
Location mapping
5. AI Predictions By Series & DICOM Image Viewer
Series Navigation:
Each Series contains multiple images
Scroll through series (e.g., Image 1 of 30)
Compare AI predictions with actual scan images
AI Analysis Display:
Findings (e.g., midline shift, hemorrhage)
Confidence Score (e.g., 98.6%)
Severity Classification (High, Medium, Low)
Additional Analysis Results:
Stroke detection
Hemorrhage detection
Other pathological findings
Detailed Findings Breakdown:
Epidural
Subdural
Intraparenchymal
Subarachnoid
6. Feedback Section
The physician has access to all feedback submitted by radiologists.
Each Feedback Entry Includes:
Radiologist's Prediction Outcome (Positive or Negative)
Clinical Notes (Optional suggestions provided by radiologist)
Decision Support Information to aid clinical decision-making
7. Profile & Account Management
Profile Card:
Display Name
Role (Physician)
Email Address
Profile Picture (Optional for personalization)
Account Management Options:
Edit Profile:
Update personal details
Modify contact information
Change Password:
Reset login password
Ensure account security
Navigation Flow Summary
App Launch
[Authenticated] → ER Dashboard
[Not Authenticated] → Login Screen
```
### 2. Authentication Flow Rules
```
Login Screen Options:
├── Hospital SSO Login (Primary)
├── Credential Login (Fallback)
├── Emergency Access (Quick)
└── Temporary Login (Limited)
```
### 3. SSO Integration Rules
- **Primary**: Hospital SSO integration
- **Fallback**: Username/password authentication
- **Emergency**: Quick access codes for urgent situations
- **Remember Device**: 30-day device authentication
- **Session Timeout**: 8 hours for security
## 📊 ER Dashboard Flow
### 4. Dashboard Entry Points
```
ER Dashboard → Load Patient List → Real-time Updates
Registration/Login
├── Critical Alerts (Priority 1)
├── Pending Scans (Priority 2)
├── Recent Reports (Priority 3)
└── All Patients (Complete View)
```
Dashboard (Main Hub)
├── Patients List
│ └── Patient Details
│ ├── Overview
│ ├── AI Analysis
│ │ └── DICOM Viewer
│ └── Feedbacks
├── Profile Management
│ ├── Edit Profile
│ └── Change Password
└── Logout
Key Features
### 5. Real-time Data Flow
```
WebSocket Connection → Live Updates
├── Patient Status Changes
├── New Scan Results
├── Critical Findings
├── Bed Assignments
└── Shift Changes
```
Streamlined Onboarding with dual registration paths
Comprehensive Dashboard with key performance metrics
Advanced DICOM Viewing with AI prediction overlay
Robust Feedback System for continuous improvement
Secure Profile Management with credential verification
### 6. Alert Priority System
- **🔴 Critical**: Immediate action required (0-2 minutes)
- **🟡 Warning**: Attention needed (2-10 minutes)
- **🟢 Info**: Routine updates (10+ minutes)
- **🔵 Status**: General information
## 🚨 Critical Finding Response Workflow
### 7. Critical Alert Reception
```
Push Notification → Alert Screen → Patient Details
AI Summary → Image Review → Treatment Protocol
Consultation → Action → Documentation
```
### 8. Alert Response Timeline
- **0-30 seconds**: Alert received & acknowledged
- **30-60 seconds**: Patient details reviewed
- **1-2 minutes**: AI findings assessed
- **2-5 minutes**: Treatment decision made
- **5+ minutes**: Action initiated & documented
### 9. Critical Alert Components
```
Alert Screen Elements:
├── Patient Identification
├── Bed Location
├── Critical Finding Type
├── AI-Generated Summary
├── Urgency Level
├── Time Stamp
└── Action Buttons
```
## 🏥 Patient Care Flow
### 10. Patient Selection Flow
```
Patient List → Patient Card → Patient Details
├── Vital Signs
├── Medical History
├── Current Medications
├── Allergy Information
├── Bed Information
└── Admission Details
```
### 11. Patient Detail Navigation
```
Patient Details Screen:
├── Demographics (Top)
├── Vital Signs (Real-time)
├── Medical History (Expandable)
├── Current Medications (List)
├── Allergy Information (Alert)
├── Bed Information (Status)
└── Action Buttons (Bottom)
```
### 12. Medical Record Integration
- **EMR Sync**: Real-time patient data
- **Vital Signs**: Live monitoring integration
- **Medication History**: Pharmacy system sync
- **Lab Results**: Laboratory system integration
- **Imaging**: PACS system connection
## 📱 Screen Navigation Patterns
### 13. Primary Navigation Structure
```
Bottom Tab Navigation:
├── Dashboard (Home)
├── Patients
├── Alerts
├── Reports
└── Settings
```
### 14. Secondary Navigation
```
Stack Navigation per Tab:
├── List View → Detail View
├── Detail View → Action View
├── Action View → Confirmation
└── Confirmation → Return to List
```
### 15. Modal Navigation Rules
- **Quick Actions**: Modal overlays
- **Critical Actions**: Full-screen modals
- **Confirmation**: Alert modals
- **Settings**: Sheet modals
## 🔄 State Management Flow
### 16. Redux State Structure
```
Root State:
├── Auth (Authentication state)
├── Dashboard (ER dashboard data)
├── PatientCare (Patient information)
├── Alerts (Notification system)
├── Settings (User preferences)
└── UI (Interface state)
```
### 17. Data Flow Patterns
```
API Call → Redux Action → State Update → UI Re-render
WebSocket → Real-time Update → Immediate UI Change
User Action → Local State → API Call → Server Sync
```
### 18. Caching Strategy
- **Patient Data**: 15-minute cache
- **Critical Alerts**: No cache (real-time)
- **User Settings**: Persistent storage
- **Medical Records**: 5-minute cache
## 📋 Workflow Rules
### 19. Critical Finding Workflow
```
Step 1: Alert Reception
├── Push notification received
├── Alert screen displayed
├── Patient context loaded
└── AI summary generated
Step 2: Assessment
├── Patient details reviewed
├── Medical history checked
├── Current status assessed
└── Urgency level determined
Step 3: Action Planning
├── Treatment protocol loaded
├── Specialist consultation initiated
├── Emergency procedures prepared
└── Documentation started
Step 4: Implementation
├── Actions executed
├── Status updated
├── Team notified
└── Record documented
```
### 20. Routine Scan Processing
```
Step 1: Report Notification
├── Scan completion notification
├── Report status update
├── AI findings summary
└── Priority assignment
Step 2: Review Process
├── Report details loaded
├── Images reviewed
├── Findings assessed
└── Action plan created
Step 3: Documentation
├── Patient record updated
├── Treatment plan documented
├── Follow-up scheduled
└── Discharge planning initiated
```
## 🔐 Security & Access Control
### 21. Authentication Rules
- **Session Management**: 8-hour timeout
- **Auto-logout**: Inactivity after 30 minutes
- **Device Remembering**: 30-day trusted devices
- **Emergency Access**: Limited functionality
- **Audit Trail**: All actions logged
### 22. Permission Levels
```
User Roles:
├── ER Physician (Full Access)
├── Resident (Limited Access)
├── Medical Student (Read-only)
├── Emergency Access (Critical Only)
└── Temporary Access (Time-limited)
```
### 23. Data Access Rules
- **Patient Data**: Role-based access
- **Critical Alerts**: All ER staff
- **Medical Records**: Authorized personnel only
- **Settings**: User-specific
- **Audit Logs**: Admin only
## 📊 Performance & Optimization
### 24. Loading States
```
Loading Hierarchy:
├── Critical Alerts (Immediate)
├── Patient List (Fast)
├── Patient Details (Medium)
├── Medical History (Medium)
└── Full Reports (Slow)
```
### 25. Offline Capabilities
- **Critical Alerts**: Always available
- **Patient List**: Cached data
- **Recent Reports**: Offline access
- **Settings**: Local storage
- **Sync**: Automatic when online
### 26. Error Handling
```
Error Recovery:
├── Network Errors → Retry with backoff
├── Authentication Errors → Re-login
├── Data Errors → Fallback to cache
├── Critical Errors → Emergency mode
└── UI Errors → Graceful degradation
```
## 🎯 User Experience Rules
### 27. Interaction Patterns
- **Critical Actions**: Confirmation required
- **Quick Actions**: One-tap execution
- **Navigation**: Intuitive flow
- **Feedback**: Immediate response
- **Accessibility**: WCAG 2.1 compliance
### 28. Visual Hierarchy
```
Priority Order:
├── Critical Alerts (Red, Large)
├── Active Patients (Blue, Medium)
├── Pending Items (Yellow, Medium)
├── Completed Items (Green, Small)
└── Background Info (Gray, Small)
```
### 29. Responsive Design
- **Mobile First**: Optimized for phones
- **Tablet Support**: Enhanced layouts
- **Landscape Mode**: Alternative views
- **Accessibility**: Voice commands support
## 🔄 Data Synchronization
### 30. Real-time Updates
```
Update Types:
├── Patient Status (Immediate)
├── Vital Signs (30-second intervals)
├── Alert Status (Real-time)
├── Bed Assignments (Real-time)
└── Report Status (5-minute intervals)
```
### 31. Conflict Resolution
- **Server Priority**: Server data overrides local
- **Timestamp Comparison**: Latest data wins
- **User Confirmation**: Manual resolution for conflicts
- **Audit Trail**: All changes tracked
## 📱 Device Integration
### 32. Hardware Integration
- **Camera**: Document scanning
- **Microphone**: Voice notes
- **Biometrics**: Secure access
- **NFC**: Patient identification
- **Bluetooth**: Medical device connection
### 33. Platform-Specific Features
```
iOS Features:
├── Face ID authentication
├── Apple Health integration
├── Siri shortcuts
└── iOS notifications
Android Features:
├── Fingerprint authentication
├── Google Fit integration
├── Android Auto
└── Android notifications
```
This comprehensive flow ensures efficient, secure, and user-friendly operation of the Physician App in emergency medical scenarios.
This flow guide ensures a smooth user experience while maintaining security and clinical accuracy standards.

3
.env
View File

@ -1,2 +1,3 @@
BASE_URL='https://neoscan-backend.tech4bizsolutions.com'
# BASE_URL='http://192.168.1.87:3000'
# BASE_URL='http://192.168.1.87:3000'
# BASE_URL='https://neoscanbackend.tech4biz.io'

696
README.md
View File

@ -1,317 +1,393 @@
/*
* File: README.md
* Description: Project documentation and setup instructions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
# NeoScan Physician App
A comprehensive React Native application designed for emergency department physicians to manage patient care, critical alerts, and medical workflows in real-time.
> **A comprehensive React Native application designed for physicians to review AI-powered medical imaging predictions, manage patient cases, and provide clinical insights to improve AI diagnostic accuracy.**
## 🚀 Features
### Authentication & Security
- **Hospital SSO Integration** - Seamless login with hospital credentials
- **Multi-factor Authentication** - Enhanced security for medical data
- **Emergency Access** - Quick access codes for urgent situations
- **Session Management** - 8-hour sessions with 30-minute inactivity timeout
- **Device Remembering** - 30-day trusted device authentication
### ER Dashboard
- **Real-time Patient Monitoring** - Live updates of patient status and vital signs
- **Critical Alert System** - Immediate notifications for life-threatening conditions
- **Department Statistics** - Overview of emergency, trauma, cardiac, neurology, pediatrics, and ICU
- **Quick Actions** - Emergency procedures, scan orders, medication, lab work, consultations
- **Patient Filtering** - Filter by status (All, Critical, Active)
### Patient Management
- **Comprehensive Patient Cards** - Vital signs, allergies, medications, diagnosis
- **Medical History** - Complete patient medical records
- **Real-time Vital Signs** - Blood pressure, heart rate, temperature, respiratory rate, oxygen saturation
- **Allergy Alerts** - Prominent display of patient allergies
- **Medication Tracking** - Current medications with dosages and schedules
### Critical Finding Response
- **AI-Powered Detection** - Automated critical finding identification
- **Immediate Alert System** - Push notifications for urgent cases
- **Response Timeline** - 0-30 seconds acknowledgment, 2-5 minutes action
- **Treatment Protocols** - Quick access to emergency procedures
## 🏗️ Architecture
### Project Structure
```
NeoScan_Physician/
├── app/ # Main application code
│ ├── modules/ # Feature-based modules
│ │ ├── Auth/ # Authentication module
│ │ ├── Dashboard/ # ER Dashboard module
│ │ ├── PatientCare/ # Patient management module
│ │ └── Settings/ # App settings module
│ ├── shared/ # Shared utilities & components
│ │ ├── components/ # Reusable UI components
│ │ ├── utils/ # Utility functions
│ │ └── types/ # TypeScript type definitions
│ ├── theme/ # Design system & theming
│ ├── navigation/ # Navigation setup
│ ├── store/ # Redux state management
│ ├── config/ # Configuration files
│ └── assets/ # Static assets
├── android/ # Android native code
├── ios/ # iOS native code
└── docs/ # Documentation
```
### Technology Stack
- **React Native 0.79.0** - Cross-platform mobile development
- **TypeScript** - Type-safe development
- **React Navigation 6** - Navigation management
- **Redux Toolkit** - State management
- **React Native Vector Icons** - Icon library
- **React Native Push Notification** - Real-time notifications
- **React Native Keychain** - Secure credential storage
## 🎨 Design System
### Color Palette - "Modern Healthcare Blue"
- **Primary**: #2196F3 (Material Blue)
- **Secondary**: #1976D2 (Darker Blue)
- **Critical**: #F44336 (Material Red)
- **Warning**: #FF9800 (Material Orange)
- **Success**: #4CAF50 (Material Green)
### Typography
- **Primary Font**: Roboto
- **Font Weights**: Light (300), Regular (400), Medium (500), Bold (700)
- **Font Sizes**: Display (32px, 24px, 20px), Body (16px, 14px, 12px), Caption (10px)
### Components
- **Patient Cards** - Comprehensive patient information display
- **Critical Alerts** - High-priority notification system
- **Quick Actions** - Emergency procedure shortcuts
- **Department Stats** - Real-time department overview
- **Dashboard Header** - ER statistics and shift information
## 📱 Screens
### Authentication Flow
1. **Splash Screen** - App initialization and authentication check
2. **Login Screen** - Hospital SSO, credential login, emergency access
3. **Main Dashboard** - ER overview with patient list and alerts
### Main Application
1. **ER Dashboard** - Real-time patient monitoring and critical alerts
2. **Patient Details** - Comprehensive patient information and medical history
3. **Alerts Center** - Critical finding notifications and response
4. **Reports** - Medical reports and scan results
5. **Settings** - User preferences and app configuration
## 🔧 Setup & Installation
### Prerequisites
- Node.js >= 18
- React Native CLI
- Android Studio (for Android development)
- Xcode (for iOS development)
### Installation Steps
1. **Clone the repository**
```bash
git clone <repository-url>
cd NeoScan_Physician
```
2. **Install dependencies**
```bash
npm install
```
3. **iOS Setup** (macOS only)
```bash
cd ios
pod install
cd ..
```
4. **Start the development server**
```bash
npm start
```
5. **Run on device/simulator**
```bash
# Android
npm run android
# iOS
npm run ios
```
## 🚨 Critical Alert Workflow
### Alert Reception (0-30 seconds)
- Push notification received
- Alert screen displayed
- Patient context loaded
- AI summary generated
### Assessment (30-60 seconds)
- Patient details reviewed
- Medical history checked
- Current status assessed
- Urgency level determined
### Action Planning (1-2 minutes)
- Treatment protocol loaded
- Specialist consultation initiated
- Emergency procedures prepared
- Documentation started
### Implementation (2-5 minutes)
- Actions executed
- Status updated
- Team notified
- Record documented
## 🔐 Security Features
### Authentication
- **Session Management**: 8-hour timeout
- **Auto-logout**: Inactivity after 30 minutes
- **Device Remembering**: 30-day trusted devices
- **Emergency Access**: Limited functionality
- **Audit Trail**: All actions logged
### Data Protection
- **Encryption**: End-to-end data encryption
- **HIPAA Compliance**: Healthcare data protection
- **Secure Storage**: Encrypted local storage
- **Network Security**: HTTPS/TLS communication
## 📊 Performance Optimization
### Loading States
- **Critical Alerts**: Immediate loading
- **Patient List**: Fast loading with caching
- **Patient Details**: Medium loading
- **Medical History**: Optimized loading
- **Full Reports**: Background loading
### Offline Capabilities
- **Critical Alerts**: Always available
- **Patient List**: Cached data access
- **Recent Reports**: Offline viewing
- **Settings**: Local storage
- **Sync**: Automatic when online
## 🧪 Testing
### Test Structure
- **Unit Tests**: Component and utility testing
- **Integration Tests**: Module interaction testing
- **E2E Tests**: Complete workflow testing
- **Performance Tests**: Load and stress testing
### Running Tests
```bash
# Unit tests
npm test
# E2E tests
npm run test:e2e
# Performance tests
npm run test:performance
```
## 📱 Platform Support
### iOS Features
- Face ID authentication
- Apple Health integration
- Siri shortcuts
- iOS notifications
### Android Features
- Fingerprint authentication
- Google Fit integration
- Android Auto
- Android notifications
## 🔄 Real-time Updates
### WebSocket Integration
- **Patient Status**: Real-time updates
- **Vital Signs**: 30-second intervals
- **Alert Status**: Immediate updates
- **Bed Assignments**: Real-time changes
- **Report Status**: 5-minute intervals
### Data Synchronization
- **Server Priority**: Server data overrides local
- **Timestamp Comparison**: Latest data wins
- **User Confirmation**: Manual resolution for conflicts
- **Audit Trail**: All changes tracked
## 📋 Development Guidelines
### Code Style
- **TypeScript**: Strict type checking
- **ESLint**: Code quality enforcement
- **Prettier**: Code formatting
- **Conventional Commits**: Git commit messages
### Component Guidelines
- **Single Responsibility**: One component, one purpose
- **Reusability**: Shared components in shared/
- **Type Safety**: Full TypeScript coverage
- **Accessibility**: WCAG 2.1 compliance
### Performance Guidelines
- **Lazy Loading**: Components loaded on demand
- **Memoization**: React.memo for expensive components
- **Image Optimization**: Compressed and cached images
- **Bundle Size**: Minimal dependencies
## 🚀 Deployment
### Build Configuration
- **Environment Variables**: Separate configs for dev/staging/prod
- **Code Signing**: Proper certificate management
- **Bundle Optimization**: Minified and optimized builds
- **Asset Management**: Optimized images and fonts
### Release Process
1. **Development**: Feature development and testing
2. **Staging**: Integration testing and QA
3. **Production**: Final testing and deployment
4. **Monitoring**: Performance and error tracking
## 📞 Support
### Documentation
- **API Documentation**: Complete API reference
- **User Guide**: End-user documentation
- **Developer Guide**: Technical documentation
- **Troubleshooting**: Common issues and solutions
### Contact
- **Technical Support**: dev-support@neoscan.com
- **Emergency Support**: emergency-support@neoscan.com
- **Feature Requests**: features@neoscan.com
## 📄 License
This project is proprietary software developed for healthcare institutions. All rights reserved.
*Design & Developed by Tech4Biz Solutions*
*Copyright (c) Spurrin Innovations. All rights reserved.*
---
**NeoScan Physician App** - Empowering emergency care with real-time intelligence and seamless workflows.
## 📋 Table of Contents
/*
* End of File: README.md
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
- [🚀 What is NeoScan Physician App?](#-what-is-neoscan-physician-app)
- [🎯 Key Features](#-key-features)
- [🏗️ How the App Works](#-how-the-app-works)
- [🛠️ What You Need to Get Started](#-what-you-need-to-get-started)
- [⚡ Quick Start Guide](#-quick-start-guide)
- [📁 Understanding the Project Structure](#-understanding-the-project-structure)
- [🔧 Development Workflow](#-development-workflow)
- [🚨 Common Issues & Solutions](#-common-issues--solutions)
- [📚 Learning Resources](#-learning-resources)
- [🤝 Getting Help](#-getting-help)
---
## 🚀 What is NeoScan Physician App?
NeoScan Physician App is a **mobile application** that helps doctors (physicians) work with **AI-powered medical imaging**. Think of it as a smart assistant that:
- **Shows AI predictions** about what might be wrong in medical scans
- **Lets doctors review** these AI suggestions and provide feedback
- **Helps improve AI accuracy** over time through expert medical input
- **Manages patient cases** and medical imaging files (DICOM images)
### 🎯 Who is this for?
- **Physicians** who review medical imaging
- **Medical students** learning about AI in healthcare
- **Developers** building healthcare applications
- **Anyone interested** in AI-powered medical technology
---
## 🎯 Key Features
### 🔐 **User Management**
- **Login/Registration**: Secure access with email and password
- **Hospital Integration**: Connect to specific medical institutions
- **Profile Management**: Update personal information
### 📊 **Dashboard Overview**
- **Statistics**: View total predictions, patients, and feedback rates
- **Performance Metrics**: Track AI confidence scores and accuracy
- **Quick Actions**: Access common tasks and recent cases
### 👥 **Patient Management**
- **Patient List**: Browse all patients in the system
- **Case Details**: View comprehensive patient information
- **Medical History**: Access patient records and imaging data
### 🖼️ **AI Analysis & Imaging**
- **DICOM Viewer**: View high-quality medical images with Cornerstone.js integration
- **AI Predictions**: Review automated diagnostic findings with confidence scores (e.g., 98.6%)
- **Finding Categories**: Stroke detection, hemorrhage analysis, anatomical findings
- **Severity Classification**: High/Medium/Low risk stratification
- **Finding Localization**: Precise visualization of pathological findings
- **Image Controls**: Zoom, frame view with slider.
### ⚙️ **Settings & Preferences**
- **App Configuration**: Customize notification and display settings
- **Account Management**: Update passwords and personal information
- **Privacy Controls**: Manage data sharing and security preferences
---
## 🏗️ How the App Works
### 🔄 **User Journey Flow**
```
1. Login → 2. Dashboard → 3. Patient List → 4. Case Review → 5. AI Analysis → 6. Feedback
```
**Step-by-Step Process:**
1. **Login**: Physician enters credentials and accesses the app
2. **Dashboard**: Overview of current cases and statistics
3. **Patient List**: Browse and search for specific patients
4. **Case Review**: Open patient details and view medical information
5. **AI Analysis**: Review AI predictions alongside medical images
6. **Feedback**: Provide clinical validation and notes
### 🔐 **User Onboarding Flow**
The app supports two ways for physicians to join:
#### **A. Self-Registration (via App)**
1. **Start App Signup** → Physician enters email address
2. **Email Check** → If email exists, show "Already Registered" and redirect to Login
3. **Set Password** → Create secure password
4. **Enter Personal Info** → Complete profile information
5. **Upload ID Proof** → Verify credentials
6. **Select Hospital** → Choose affiliated hospital → Wait for admin approval
**Note**: Upon self-registration, a physician's status is set to 'Inactive' by default. The hospital administrator must review and approve the registration by changing the status to 'Active'.
#### **B. Admin-Created Physician**
1. Admin creates physician account in the system
2. Login credentials sent to physician's registered email address
3. Physician logs in using received credentials
4. Upload ID Proof → Complete verification
5. Reset Password → Access Dashboard
### 📊 **Case Review Workflow**
1. **Select Case**: Choose from patient queue or search
2. **View DICOM**: Access medical imaging files
3. **Review AI**: Analyze AI predictions and confidence scores
4. **Provide Feedback**: Submit validation (Agree/Partially Agree/Disagree)
5. **Add Notes**: Optional clinical observations
6. **Complete Review**: Case marked as reviewed
### 📊 **Data Flow**
```
User Action → App Request → AI Analysis → Results Display → Feedback Collection → AI Learning
```
### 📝 **Advanced Feedback System**
- **Binary Validation**: Positive (✅) or Negative (❌) feedback
- **Clinical Notes**: Detailed observations and suggestions
- **Performance Improvement**: Feedback data enhances AI accuracy
- **Quality Metrics**: Continuous validation performance tracking
- **Finding Validation**: Validate specific findings like hemorrhage types, midline shift, etc.
---
## 🛠️ What You Need to Get Started
### **Essential Requirements**
- **Computer**: Windows, macOS, or Linux
- **Node.js**: Version 18.0.0 or higher (download from [nodejs.org](https://nodejs.org/))
- **Git**: For downloading the project (download from [git-scm.com](https://git-scm.com/))
- **Code Editor**: Visual Studio Code (recommended) or any text editor
### **Technology Stack**
- **React Native 0.79.0**: Cross-platform mobile development
- **TypeScript 5.x**: Type-safe development with strict typing
- **React Navigation 6**: Navigation management and routing
- **Redux Toolkit**: Modern Redux with simplified patterns
- **Redux Persist**: Local storage for offline capability
- **React Native Vector Icons**: Comprehensive icon library
- **WebView**: DICOM image viewing with Cornerstone.js integration
### **For Mobile Development**
- **Android Development**: Android Studio (for Android apps)
- **iOS Development**: Xcode (only on macOS, for iPhone apps)
### **Knowledge Prerequisites**
- **Basic JavaScript**: Understanding of variables, functions, and objects
- **React Basics**: Components, props, and state (helpful but not required)
- **Command Line**: Basic familiarity with terminal/command prompt
---
## ⚡ Quick Start Guide
### **Step 1: Get the Project (2 minutes)**
```bash
# Download the project
git clone <repository-url>
cd NeoScan_Physician
# Check if everything downloaded correctly
ls
# You should see folders like: app, android, ios, package.json
```
### **Step 2: Install Dependencies (3 minutes)**
```bash
# Install all required packages
npm install
# Wait for installation to complete (this might take a few minutes)
```
### **Step 3: Start the App (2 minutes)**
```bash
# Start the development server
npm start
# In a new terminal window, run on your phone/emulator
npm run android # For Android
npm run ios # For iOS (macOS only)
```
### **🎉 You're Done!**
The app should now be running on your device or emulator. You can start exploring the code and making changes!
---
## 📁 Understanding the Project Structure
### **📱 Main App Folder (`app/`)**
This is where all the main code lives:
```
app/
├── 🎯 modules/ # Different parts of the app
│ ├── 🔐 Auth/ # Login, registration, user management
│ ├── 📊 Dashboard/ # Main screen with statistics
│ ├── 👥 PatientCare/ # Patient management and case review
│ └── ⚙️ Settings/ # App settings and user preferences
├── 🔧 shared/ # Code used across multiple parts
│ ├── components/ # Reusable UI pieces (buttons, cards, etc.)
│ ├── utils/ # Helper functions and tools
│ └── types/ # Data structure definitions
├── 🎨 theme/ # Colors, fonts, and styling
├── 🧭 navigation/ # How users move between screens
├── 📦 store/ # Data management (Redux)
└── 📁 assets/ # Images, fonts, and other files
```
### **🤖 Platform-Specific Folders**
- **`android/`**: Android-specific code and settings
- **`ios/`**: iPhone-specific code and settings
### **📄 Configuration Files**
- **`package.json`**: Lists all the packages the app needs
- **`tsconfig.json`**: TypeScript configuration
- **`metro.config.js`**: React Native bundler settings
### **🏗️ High-Level Architecture**
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ React Native │ │ Redux Store │ │ API Services │
│ App Layer │◄──►│ (State Mgmt) │◄──►│ (Backend) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Native Modules │ │ Local Storage │ │ DICOM Viewer │
│ (iOS/Android) │ │ (Redux Persist)│ │ (WebView) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
---
## 🔧 Development Workflow
### **🔄 Making Changes**
1. **Edit Code**: Modify files in the `app/` folder
2. **Save File**: Changes automatically reload in the app
3. **Test Changes**: See results immediately on your device
4. **Repeat**: Keep iterating until it works perfectly
### **📝 Where to Start Making Changes**
#### **For UI Changes:**
- **Colors & Styling**: `app/theme/colors.ts`
- **Buttons & Cards**: `app/shared/components/`
- **Screen Layouts**: `app/modules/[ModuleName]/screens/`
#### **For New Features:**
- **New Screens**: `app/modules/[ModuleName]/screens/`
- **New Functions**: `app/modules/[ModuleName]/services/`
- **Data Management**: `app/modules/[ModuleName]/redux/`
#### **For Bug Fixes:**
- **Authentication Issues**: `app/modules/Auth/`
- **Patient Data Problems**: `app/modules/PatientCare/`
- **Dashboard Issues**: `app/modules/Dashboard/`
---
## 🚨 Common Issues & Solutions
### **❌ "Metro bundler not starting"**
```bash
# Clear the cache and restart
npm start -- --reset-cache
```
### **❌ "App not loading on device"**
```bash
# Stop the server (Ctrl+C) and restart
npm start
# Then run again: npm run android (or ios)
```
### **❌ "Dependencies not working"**
```bash
# Remove and reinstall packages
rm -rf node_modules package-lock.json
npm install
```
### **❌ "Build errors"**
```bash
# Clean the build
cd android && ./gradlew clean && cd .. # For Android
cd ios && xcodebuild clean && cd .. # For iOS
```
### **❌ "TypeScript errors"**
- Check that you're using the correct data types
- Look at existing code for examples
- Use the `any` type temporarily if stuck (not recommended for production)
---
## 📚 Learning Resources
### **🌐 Online Learning**
- **React Native**: [reactnative.dev](https://reactnative.dev/) - Official documentation
- **JavaScript**: [javascript.info](https://javascript.info/) - Modern JavaScript tutorial
- **TypeScript**: [typescriptlang.org](https://www.typescriptlang.org/) - TypeScript handbook
### **📱 App Development Concepts**
- **Mobile Apps**: How touch interfaces work
- **State Management**: Keeping track of app data
- **API Integration**: Connecting to backend services
- **User Experience**: Making apps easy to use
### **🔧 Development Tools**
- **VS Code**: Best code editor for React Native
- **React Native Debugger**: Debug your app
- **Flipper**: Advanced debugging and inspection
---
## 🤝 Getting Help
### **📖 Self-Help Resources**
- **This README**: Start here for basic questions
- **Code Comments**: Most code has helpful explanations
- **Console Logs**: Check the terminal for error messages
- **React Native Docs**: Official documentation and examples
### **👥 Team Support**
- **Code Reviews**: Ask team members to review your changes
- **Pair Programming**: Work together on difficult problems
- **Team Chat**: Use your team's communication platform
### **🐛 When You're Stuck**
1. **Read the error message** carefully
2. **Check the console** for more details
3. **Search online** for similar problems
4. **Ask the team** with specific details about your issue
5. **Take a break** - sometimes solutions come when you step away
---
## 🚀 Next Steps
### **🎯 For Beginners**
1. **Explore the code**: Look at different files to understand structure
2. **Make small changes**: Try changing colors or text
3. **Add a button**: Create a simple new feature
4. **Read more code**: Understand how different parts work together
### **🚀 For Intermediate Developers**
1. **Add new features**: Create new screens or functionality
2. **Improve existing code**: Refactor and optimize
3. **Write tests**: Ensure code quality and reliability
4. **Help others**: Share knowledge with team members
### **🏆 For Advanced Developers**
1. **Architecture improvements**: Suggest better ways to structure code
2. **Performance optimization**: Make the app faster and more efficient
3. **Security enhancements**: Improve data protection and privacy
4. **Mentorship**: Guide junior developers and share expertise
---
## 📄 Project Information
- **Project Name**: NeoScan Physician App
- **Version**: 0.0.1
- **Technology**: React Native 0.79.0 + TypeScript
- **Target Platforms**: iOS and Android
- **Development Status**: Active Development
---
## 🎉 Welcome to the Team!
You're now part of building something that could help doctors save lives through better AI-powered medical imaging. Every line of code you write contributes to improving healthcare technology.
**Remember**: There are no stupid questions. If something doesn't make sense, ask! The team is here to help you succeed.
---
*This README is designed to help developers at all levels understand and contribute to the NeoScan Physician App. For additional support, please refer to the code comments or ask your team members.*
**Happy Coding! 🚀**

View File

@ -16,131 +16,7 @@ import Toast from 'react-native-toast-message';
import { useAppSelector } from './store/hooks';
import { selectIsAuthenticated } from './modules/Auth/redux/authSelectors';
// ============================================================================
// MOCK DATA SECTION - For demonstration and development purposes
// ============================================================================
// Mock dashboard data representing the current state of the ER
const mockDashboard = {
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
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
},
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
},
lastUpdated: new Date(), // Last time dashboard was updated
};
// Mock patient data representing real patients in the ER
const mockPatients = [
{
id: '1', // Unique patient identifier
mrn: 'MRN001', // Medical Record Number
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
admissionDate: new Date('2024-01-15'),
status: 'ACTIVE' as const, // Current patient status
priority: 'CRITICAL' as const, // Priority level for treatment
department: 'Emergency',
attendingPhysician: 'Dr. Smith',
allergies: [
{
id: '1',
name: 'Penicillin',
severity: 'SEVERE' as const,
reaction: 'Anaphylaxis'
},
],
medications: [
{
id: '1',
name: 'Morphine',
dosage: '2mg',
frequency: 'Every 4 hours',
route: 'IV', // Administration route
startDate: new Date(),
status: 'ACTIVE' as const,
prescribedBy: 'Dr. Smith',
},
],
vitalSigns: {
bloodPressure: { systolic: 140, diastolic: 90, 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
lastUpdated: new Date(),
},
{
id: '2',
mrn: 'MRN002',
firstName: 'Jane',
lastName: 'Smith',
dateOfBirth: new Date('1990-07-22'),
gender: 'FEMALE' as const,
age: 33,
bedNumber: 'B2',
roomNumber: '102',
admissionDate: new Date('2024-01-15'),
status: 'ACTIVE' as const,
priority: 'HIGH' as const,
department: 'Trauma',
attendingPhysician: 'Dr. Johnson',
allergies: [],
medications: [],
vitalSigns: {
bloodPressure: { systolic: 120, diastolic: 80, 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
lastUpdated: new Date(),
},
];
// Mock alerts representing critical notifications that require immediate attention
const mockAlerts = [
{
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: '1', // Associated patient
patientName: 'John Doe',
bedNumber: 'A1',
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
},
];
/**
* AppContent Component (Inner Component)

View File

@ -1,249 +0,0 @@
/*
* File: AIPredictionCard.test.tsx
* Description: Unit tests for AI Prediction Card component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import AIPredictionCard from '../components/AIPredictionCard';
import type { AIPredictionCase } from '../types';
// ============================================================================
// MOCK DATA
// ============================================================================
const mockPredictionCase: AIPredictionCase = {
patid: 'test-patient-001',
hospital_id: 'hospital-123',
prediction: {
label: 'midline shift',
finding_type: 'pathology',
clinical_urgency: 'urgent',
confidence_score: 0.96,
finding_category: 'abnormal',
primary_severity: 'high',
anatomical_location: 'brain',
},
created_at: '2024-01-15T10:30:00Z',
updated_at: '2024-01-15T10:30:00Z',
review_status: 'pending',
priority: 'critical',
};
const mockProps = {
predictionCase: mockPredictionCase,
onPress: jest.fn(),
onReview: jest.fn(),
onToggleSelect: jest.fn(),
isSelected: false,
showReviewButton: true,
};
// ============================================================================
// UNIT TESTS
// ============================================================================
describe('AIPredictionCard', () => {
beforeEach(() => {
jest.clearAllMocks();
});
// ============================================================================
// RENDERING TESTS
// ============================================================================
describe('Rendering', () => {
it('should render correctly with required props', () => {
const { getByText } = render(
<AIPredictionCard
predictionCase={mockPredictionCase}
onPress={mockProps.onPress}
/>
);
expect(getByText('test-patient-001')).toBeTruthy();
expect(getByText('Midline Shift')).toBeTruthy();
expect(getByText('96%')).toBeTruthy();
expect(getByText('Urgent')).toBeTruthy();
});
it('should render review button when showReviewButton is true', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
expect(getByText('Review')).toBeTruthy();
});
it('should not render review button when showReviewButton is false', () => {
const { queryByText } = render(
<AIPredictionCard {...mockProps} showReviewButton={false} />
);
expect(queryByText('Review')).toBeNull();
});
it('should not render review button when status is not pending', () => {
const reviewedCase = {
...mockPredictionCase,
review_status: 'reviewed' as const,
};
const { queryByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={reviewedCase}
/>
);
expect(queryByText('Review')).toBeNull();
});
it('should render selection checkbox when onToggleSelect is provided', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
expect(getByRole('checkbox')).toBeTruthy();
});
it('should show selected state correctly', () => {
const { getByRole } = render(
<AIPredictionCard {...mockProps} isSelected={true} />
);
const checkbox = getByRole('checkbox');
expect(checkbox.props.accessibilityState.checked).toBe(true);
});
});
// ============================================================================
// INTERACTION TESTS
// ============================================================================
describe('Interactions', () => {
it('should call onPress when card is pressed', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
fireEvent.press(getByRole('button'));
expect(mockProps.onPress).toHaveBeenCalledWith(mockPredictionCase);
});
it('should call onReview when review button is pressed', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
fireEvent.press(getByText('Review'));
expect(mockProps.onReview).toHaveBeenCalledWith('test-patient-001');
});
it('should call onToggleSelect when checkbox is pressed', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
fireEvent.press(getByRole('checkbox'));
expect(mockProps.onToggleSelect).toHaveBeenCalledWith('test-patient-001');
});
});
// ============================================================================
// DATA FORMATTING TESTS
// ============================================================================
describe('Data formatting', () => {
it('should format confidence score as percentage', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
expect(getByText('96%')).toBeTruthy();
});
it('should capitalize text correctly', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
expect(getByText('Midline Shift')).toBeTruthy();
expect(getByText('Pathology')).toBeTruthy();
expect(getByText('Abnormal')).toBeTruthy();
});
it('should handle missing anatomical location', () => {
const caseWithoutLocation = {
...mockPredictionCase,
prediction: {
...mockPredictionCase.prediction,
anatomical_location: 'not_applicable',
},
};
const { queryByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={caseWithoutLocation}
/>
);
// Should not render location when it's 'not_applicable'
expect(queryByText('Not Applicable')).toBeNull();
});
});
// ============================================================================
// ACCESSIBILITY TESTS
// ============================================================================
describe('Accessibility', () => {
it('should have proper accessibility labels', () => {
const { getByLabelText } = render(<AIPredictionCard {...mockProps} />);
expect(
getByLabelText('AI Prediction case for patient test-patient-001')
).toBeTruthy();
});
it('should have proper accessibility hints', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
const cardButton = getByRole('button');
expect(cardButton.props.accessibilityHint).toBe(
'Tap to view detailed prediction information'
);
});
});
// ============================================================================
// EDGE CASES TESTS
// ============================================================================
describe('Edge cases', () => {
it('should handle missing dates gracefully', () => {
const caseWithoutDates = {
...mockPredictionCase,
created_at: undefined,
updated_at: undefined,
};
const { getByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={caseWithoutDates}
/>
);
expect(getByText('N/A')).toBeTruthy();
});
it('should handle emergency urgency with special styling', () => {
const emergencyCase = {
...mockPredictionCase,
prediction: {
...mockPredictionCase.prediction,
clinical_urgency: 'emergency' as const,
},
};
const { getByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={emergencyCase}
/>
);
expect(getByText('Emergency')).toBeTruthy();
});
});
});
/*
* End of File: AIPredictionCard.test.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,361 +0,0 @@
/*
* File: aiPredictionAPI.test.ts
* Description: Unit tests for AI Prediction API service
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { aiPredictionAPI } from '../services/aiPredictionAPI';
// Mock apisauce
jest.mock('apisauce', () => ({
create: jest.fn(() => ({
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
})),
}));
// Mock API utilities
jest.mock('../../../shared/utils', () => ({
API_CONFIG: {
BASE_URL: 'https://test-api.com',
},
buildHeaders: jest.fn((options = {}) => ({
headers: {
'Content-Type': 'application/json',
...(options.token && { Authorization: `Bearer ${options.token}` }),
},
})),
}));
// ============================================================================
// MOCK DATA
// ============================================================================
const mockToken = 'test-token-123';
const mockResponse = {
ok: true,
data: {
success: true,
data: [
{
patid: 'test-001',
hospital_id: 'hospital-001',
prediction: {
label: 'test finding',
finding_type: 'pathology',
clinical_urgency: 'urgent',
confidence_score: 0.95,
finding_category: 'abnormal',
primary_severity: 'high',
anatomical_location: 'brain',
},
},
],
},
};
// ============================================================================
// UNIT TESTS
// ============================================================================
describe('AI Prediction API', () => {
let mockApi: any;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Get the mocked API instance
const { create } = require('apisauce');
mockApi = create();
});
// ============================================================================
// GET ALL PREDICTIONS TESTS
// ============================================================================
describe('getAllPredictions', () => {
it('should call GET endpoint with correct parameters', async () => {
mockApi.get.mockResolvedValue(mockResponse);
const params = {
page: 1,
limit: 20,
urgency: 'urgent',
search: 'test',
};
await aiPredictionAPI.getAllPredictions(mockToken, params);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/all-prediction-results',
params,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
it('should call GET endpoint without parameters', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getAllPredictions(mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/all-prediction-results',
{},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// GET CASE DETAILS TESTS
// ============================================================================
describe('getCaseDetails', () => {
it('should call GET endpoint with correct case ID', async () => {
const caseId = 'test-case-001';
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getCaseDetails(caseId, mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
`/api/ai-cases/prediction-details/${caseId}`,
{},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// UPDATE CASE REVIEW TESTS
// ============================================================================
describe('updateCaseReview', () => {
it('should call PUT endpoint with correct data', async () => {
const caseId = 'test-case-001';
const reviewData = {
review_status: 'reviewed' as const,
reviewed_by: 'Dr. Test',
review_notes: 'Test notes',
priority: 'high' as const,
};
mockApi.put.mockResolvedValue(mockResponse);
await aiPredictionAPI.updateCaseReview(caseId, reviewData, mockToken);
expect(mockApi.put).toHaveBeenCalledWith(
`/api/ai-cases/review/${caseId}`,
reviewData,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// GET STATISTICS TESTS
// ============================================================================
describe('getPredictionStats', () => {
it('should call GET endpoint with time range parameter', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getPredictionStats(mockToken, 'week');
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/statistics',
{ timeRange: 'week' },
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
it('should call GET endpoint without time range parameter', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getPredictionStats(mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/statistics',
{},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// SEARCH PREDICTIONS TESTS
// ============================================================================
describe('searchPredictions', () => {
it('should call GET endpoint with search query and filters', async () => {
const query = 'test search';
const filters = {
urgency: ['urgent', 'emergency'],
severity: ['high'],
dateRange: { start: '2024-01-01', end: '2024-01-31' },
};
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.searchPredictions(query, mockToken, filters);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/search',
{
q: query,
filters: JSON.stringify(filters),
},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
it('should call GET endpoint with only search query', async () => {
const query = 'test search';
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.searchPredictions(query, mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/search',
{ q: query },
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// BULK OPERATIONS TESTS
// ============================================================================
describe('bulkUpdateReviews', () => {
it('should call PUT endpoint with case IDs and review data', async () => {
const caseIds = ['case-001', 'case-002', 'case-003'];
const reviewData = {
review_status: 'reviewed' as const,
reviewed_by: 'Dr. Test',
review_notes: 'Bulk review',
};
mockApi.put.mockResolvedValue(mockResponse);
await aiPredictionAPI.bulkUpdateReviews(caseIds, reviewData, mockToken);
expect(mockApi.put).toHaveBeenCalledWith(
'/api/ai-cases/bulk-review',
{ caseIds, reviewData },
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// SUBMIT FEEDBACK TESTS
// ============================================================================
describe('submitPredictionFeedback', () => {
it('should call POST endpoint with feedback data', async () => {
const caseId = 'test-case-001';
const feedbackData = {
accuracy_rating: 4 as const,
is_accurate: true,
physician_diagnosis: 'Confirmed midline shift',
feedback_notes: 'Accurate prediction',
improvement_suggestions: 'None',
};
mockApi.post.mockResolvedValue(mockResponse);
await aiPredictionAPI.submitPredictionFeedback(caseId, feedbackData, mockToken);
expect(mockApi.post).toHaveBeenCalledWith(
`/api/ai-cases/feedback/${caseId}`,
feedbackData,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// ERROR HANDLING TESTS
// ============================================================================
describe('Error handling', () => {
it('should handle API errors gracefully', async () => {
const errorResponse = {
ok: false,
problem: 'NETWORK_ERROR',
data: null,
};
mockApi.get.mockResolvedValue(errorResponse);
const result = await aiPredictionAPI.getAllPredictions(mockToken);
expect(result).toEqual(errorResponse);
});
it('should handle missing token', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getAllPredictions('');
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/all-prediction-results',
{},
expect.objectContaining({
headers: expect.objectContaining({
'Content-Type': 'application/json',
}),
})
);
});
});
});
/*
* End of File: aiPredictionAPI.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,231 +0,0 @@
/*
* File: aiPredictionSlice.test.ts
* Description: Unit tests for AI Prediction Redux slice
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import aiPredictionReducer, {
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
clearAllFilters,
toggleShowFilters,
clearError,
} from '../redux/aiPredictionSlice';
import type { AIPredictionState } from '../types';
// ============================================================================
// MOCK DATA
// ============================================================================
const initialState: AIPredictionState = {
predictionCases: [],
currentCase: null,
isLoading: false,
isRefreshing: false,
isLoadingCaseDetails: false,
error: null,
searchQuery: '',
selectedUrgencyFilter: 'all',
selectedSeverityFilter: 'all',
selectedCategoryFilter: 'all',
sortBy: 'date',
sortOrder: 'desc',
currentPage: 1,
itemsPerPage: 20,
totalItems: 0,
lastUpdated: null,
cacheExpiry: null,
showFilters: false,
selectedCaseIds: [],
};
// ============================================================================
// UNIT TESTS
// ============================================================================
describe('AI Prediction Slice', () => {
// ============================================================================
// INITIAL STATE TESTS
// ============================================================================
it('should return the initial state', () => {
const result = aiPredictionReducer(undefined, { type: 'unknown' });
expect(result).toEqual(initialState);
});
// ============================================================================
// SEARCH TESTS
// ============================================================================
describe('Search functionality', () => {
it('should handle setSearchQuery', () => {
const searchQuery = 'test search';
const action = setSearchQuery(searchQuery);
const result = aiPredictionReducer(initialState, action);
expect(result.searchQuery).toBe(searchQuery);
expect(result.currentPage).toBe(1); // Should reset to first page
});
it('should handle empty search query', () => {
const state = { ...initialState, searchQuery: 'existing search' };
const action = setSearchQuery('');
const result = aiPredictionReducer(state, action);
expect(result.searchQuery).toBe('');
expect(result.currentPage).toBe(1);
});
});
// ============================================================================
// FILTER TESTS
// ============================================================================
describe('Filter functionality', () => {
it('should handle setUrgencyFilter', () => {
const filter = 'urgent';
const action = setUrgencyFilter(filter);
const result = aiPredictionReducer(initialState, action);
expect(result.selectedUrgencyFilter).toBe(filter);
expect(result.currentPage).toBe(1); // Should reset to first page
});
it('should handle setSeverityFilter', () => {
const filter = 'high';
const action = setSeverityFilter(filter);
const result = aiPredictionReducer(initialState, action);
expect(result.selectedSeverityFilter).toBe(filter);
expect(result.currentPage).toBe(1);
});
it('should handle setCategoryFilter', () => {
const filter = 'critical';
const action = setCategoryFilter(filter);
const result = aiPredictionReducer(initialState, action);
expect(result.selectedCategoryFilter).toBe(filter);
expect(result.currentPage).toBe(1);
});
it('should handle clearAllFilters', () => {
const state: AIPredictionState = {
...initialState,
searchQuery: 'test',
selectedUrgencyFilter: 'urgent',
selectedSeverityFilter: 'high',
selectedCategoryFilter: 'critical',
currentPage: 3,
};
const action = clearAllFilters();
const result = aiPredictionReducer(state, action);
expect(result.searchQuery).toBe('');
expect(result.selectedUrgencyFilter).toBe('all');
expect(result.selectedSeverityFilter).toBe('all');
expect(result.selectedCategoryFilter).toBe('all');
expect(result.currentPage).toBe(1);
});
});
// ============================================================================
// UI STATE TESTS
// ============================================================================
describe('UI state functionality', () => {
it('should handle toggleShowFilters', () => {
const action = toggleShowFilters();
// Toggle from false to true
const result1 = aiPredictionReducer(initialState, action);
expect(result1.showFilters).toBe(true);
// Toggle from true to false
const result2 = aiPredictionReducer(result1, action);
expect(result2.showFilters).toBe(false);
});
it('should handle clearError', () => {
const state = { ...initialState, error: 'Test error' };
const action = clearError();
const result = aiPredictionReducer(state, action);
expect(result.error).toBe(null);
});
});
// ============================================================================
// ASYNC ACTION TESTS
// ============================================================================
describe('Async actions', () => {
it('should handle fetchAIPredictions.pending', () => {
const action = { type: 'aiPrediction/fetchAIPredictions/pending' };
const result = aiPredictionReducer(initialState, action);
expect(result.isLoading).toBe(true);
expect(result.error).toBe(null);
});
it('should handle fetchAIPredictions.fulfilled', () => {
const mockCases = [
{
patid: 'test-001',
hospital_id: 'hospital-001',
prediction: {
label: 'test finding',
finding_type: 'pathology' as const,
clinical_urgency: 'urgent' as const,
confidence_score: 0.95,
finding_category: 'abnormal' as const,
primary_severity: 'high' as const,
anatomical_location: 'brain',
},
},
];
const action = {
type: 'aiPrediction/fetchAIPredictions/fulfilled',
payload: {
cases: mockCases,
total: 1,
page: 1,
limit: 20,
},
};
const result = aiPredictionReducer(initialState, action);
expect(result.isLoading).toBe(false);
expect(result.predictionCases).toEqual(mockCases);
expect(result.totalItems).toBe(1);
expect(result.error).toBe(null);
expect(result.lastUpdated).toBeInstanceOf(Date);
expect(result.cacheExpiry).toBeInstanceOf(Date);
});
it('should handle fetchAIPredictions.rejected', () => {
const errorMessage = 'Failed to fetch predictions';
const action = {
type: 'aiPrediction/fetchAIPredictions/rejected',
payload: errorMessage,
};
const result = aiPredictionReducer(initialState, action);
expect(result.isLoading).toBe(false);
expect(result.error).toBe(errorMessage);
});
});
});
/*
* End of File: aiPredictionSlice.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,522 +0,0 @@
/*
* File: AIPredictionCard.tsx
* Description: Card component for displaying AI prediction case information
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import { AIPredictionCase, URGENCY_COLORS, SEVERITY_COLORS, CATEGORY_COLORS } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface AIPredictionCardProps {
predictionCase: AIPredictionCase;
onPress: (predictionCase: AIPredictionCase) => void;
onReview?: (caseId: string) => void;
isSelected?: boolean;
onToggleSelect?: (caseId: string) => void;
showReviewButton?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const CARD_WIDTH = width - 32; // Full width with margins
// ============================================================================
// AI PREDICTION CARD COMPONENT
// ============================================================================
/**
* AIPredictionCard Component
*
* Purpose: Display AI prediction case information in a card format
*
* Features:
* - Patient ID and hospital information
* - AI prediction results with confidence score
* - Urgency and severity indicators
* - Review status and actions
* - Selection support for bulk operations
* - Modern card design with proper spacing
* - Color-coded priority indicators
* - Accessibility support
*/
const AIPredictionCard: React.FC<AIPredictionCardProps> = ({
predictionCase,
onPress,
onReview,
isSelected = false,
onToggleSelect,
showReviewButton = true,
}) => {
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Urgency Color
*
* Purpose: Get color based on clinical urgency
*/
const getUrgencyColor = (urgency: string): string => {
return URGENCY_COLORS[urgency as keyof typeof URGENCY_COLORS] || theme.colors.textMuted;
};
/**
* Get Severity Color
*
* Purpose: Get color based on primary severity
*/
const getSeverityColor = (severity: string): string => {
return SEVERITY_COLORS[severity as keyof typeof SEVERITY_COLORS] || theme.colors.textMuted;
};
/**
* Get Category Color
*
* Purpose: Get color based on finding category
*/
const getCategoryColor = (category: string): string => {
return CATEGORY_COLORS[category as keyof typeof CATEGORY_COLORS] || theme.colors.textMuted;
};
/**
* Get Review Status Color
*
* Purpose: Get color based on review status
*/
const getReviewStatusColor = (status: string): string => {
switch (status) {
case 'confirmed':
return theme.colors.success;
case 'reviewed':
return theme.colors.info;
case 'disputed':
return theme.colors.warning;
case 'pending':
default:
return theme.colors.error;
}
};
/**
* Format Confidence Score
*
* Purpose: Format confidence score as percentage
*/
const formatConfidence = (score: number): string => {
return `${Math.round(score * 100)}%`;
};
/**
* Capitalize Text
*
* Purpose: Capitalize first letter of each word
*/
const capitalize = (text: string): string => {
return text.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
};
/**
* Format Date
*
* Purpose: Format date for display
*/
const formatDate = (dateString?: string): string => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
} catch {
return 'N/A';
}
};
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Card Press
*
* Purpose: Handle card tap to view details
*/
const handleCardPress = () => {
onPress(predictionCase);
};
/**
* Handle Review Press
*
* Purpose: Handle review button press
*/
const handleReviewPress = (event: any) => {
event.stopPropagation();
if (onReview) {
onReview(predictionCase.patid);
}
};
/**
* Handle Selection Toggle
*
* Purpose: Handle case selection toggle
*/
const handleSelectionToggle = (event: any) => {
event.stopPropagation();
if (onToggleSelect) {
onToggleSelect(predictionCase.patid);
}
};
// ============================================================================
// RENDER
// ============================================================================
return (
<TouchableOpacity
style={[
styles.container,
isSelected && styles.selectedContainer,
predictionCase.prediction.clinical_urgency === 'emergency' && styles.emergencyContainer,
]}
onPress={handleCardPress}
activeOpacity={0.7}
accessibilityRole="button"
accessibilityLabel={`AI Prediction case for patient ${predictionCase.patid}`}
accessibilityHint="Tap to view detailed prediction information"
>
{/* Header Section */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.patientId} numberOfLines={1}>
{predictionCase.patid}
</Text>
<Text style={styles.date}>
{formatDate(predictionCase.processed_at)}
</Text>
</View>
<View style={styles.headerRight}>
{onToggleSelect && (
<TouchableOpacity
style={styles.selectionButton}
onPress={handleSelectionToggle}
accessibilityRole="checkbox"
accessibilityState={{ checked: isSelected }}
>
<Icon
name={isSelected ? 'check-square' : 'square'}
size={20}
color={isSelected ? theme.colors.primary : theme.colors.textMuted}
/>
</TouchableOpacity>
)}
<View style={[
styles.priorityBadge,
{ backgroundColor: getUrgencyColor(predictionCase.prediction.clinical_urgency) }
]}>
<Text style={styles.priorityText}>
{capitalize(predictionCase.prediction.clinical_urgency)}
</Text>
</View>
</View>
</View>
{/* Prediction Information */}
<View style={styles.predictionSection}>
<View style={styles.predictionHeader}>
<Text style={styles.predictionLabel} numberOfLines={2}>
{capitalize(predictionCase.prediction.label)}
</Text>
<View style={styles.confidenceContainer}>
<Icon name="trending-up" size={16} color={theme.colors.primary} />
<Text style={styles.confidenceText}>
{formatConfidence(predictionCase.prediction.confidence_score)}
</Text>
</View>
</View>
{/* Finding Details */}
<View style={styles.findingDetails}>
<View style={styles.findingItem}>
<Text style={styles.findingLabel}>Type:</Text>
<Text style={styles.findingValue}>
{capitalize(predictionCase.prediction.finding_type)}
</Text>
</View>
<View style={styles.findingItem}>
<Text style={styles.findingLabel}>Category:</Text>
<View style={[
styles.categoryBadge,
{ backgroundColor: getCategoryColor(predictionCase.prediction.finding_category) }
]}>
<Text style={styles.categoryText}>
{capitalize(predictionCase.prediction.finding_category)}
</Text>
</View>
</View>
</View>
{/* Severity and Location */}
<View style={styles.detailsRow}>
<View style={styles.detailItem}>
<Icon name="alert-triangle" size={14} color={getSeverityColor(predictionCase.prediction.primary_severity)} />
<Text style={[styles.detailText, { color: getSeverityColor(predictionCase.prediction.primary_severity) }]}>
{capitalize(predictionCase.prediction.primary_severity)} Severity
</Text>
</View>
{predictionCase.prediction.anatomical_location !== 'not_applicable' && (
<View style={styles.detailItem}>
<Icon name="map-pin" size={14} color={theme.colors.textSecondary} />
<Text style={styles.detailText}>
{capitalize(predictionCase.prediction.anatomical_location)}
</Text>
</View>
)}
</View>
</View>
{/* Footer Section */}
<View style={styles.footer}>
<View style={styles.footerLeft}>
<View style={[
styles.reviewStatusBadge,
{ backgroundColor: getReviewStatusColor(predictionCase.review_status || 'pending') }
]}>
<Text style={styles.reviewStatusText}>
{capitalize(predictionCase.review_status || 'pending')}
</Text>
</View>
{predictionCase.reviewed_by && (
<Text style={styles.reviewedBy}>
by {predictionCase.reviewed_by}
</Text>
)}
</View>
{showReviewButton && predictionCase.review_status === 'pending' && (
<TouchableOpacity
style={styles.reviewButton}
onPress={handleReviewPress}
accessibilityRole="button"
accessibilityLabel="Review this case"
>
<Icon name="eye" size={16} color={theme.colors.primary} />
<Text style={styles.reviewButtonText}>Review</Text>
</TouchableOpacity>
)}
</View>
</TouchableOpacity>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.lg,
marginHorizontal: theme.spacing.md,
marginVertical: theme.spacing.sm,
width: CARD_WIDTH,
...theme.shadows.medium,
borderWidth: 1,
borderColor: theme.colors.border,
},
selectedContainer: {
borderColor: theme.colors.primary,
borderWidth: 2,
},
emergencyContainer: {
borderLeftWidth: 4,
borderLeftColor: URGENCY_COLORS.emergency,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.md,
},
headerLeft: {
flex: 1,
},
headerRight: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
patientId: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
date: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textSecondary,
},
selectionButton: {
padding: theme.spacing.xs,
},
priorityBadge: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
},
priorityText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
predictionSection: {
marginBottom: theme.spacing.md,
},
predictionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.sm,
},
predictionLabel: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.textPrimary,
flex: 1,
marginRight: theme.spacing.sm,
},
confidenceContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
confidenceText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.primary,
},
findingDetails: {
marginBottom: theme.spacing.sm,
},
findingItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: theme.spacing.xs,
},
findingLabel: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
findingValue: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textPrimary,
},
categoryBadge: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
},
categoryText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
detailsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
detailItem: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
flex: 1,
},
detailText: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textSecondary,
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: theme.spacing.sm,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
footerLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
flex: 1,
},
reviewStatusBadge: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
},
reviewStatusText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
reviewedBy: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.textMuted,
fontStyle: 'italic',
},
reviewButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
borderWidth: 1,
borderColor: theme.colors.primary,
},
reviewButtonText: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionCard;
/*
* End of File: AIPredictionCard.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,287 +0,0 @@
/*
* File: EmptyState.tsx
* Description: Empty state component for AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
// ============================================================================
// INTERFACES
// ============================================================================
interface EmptyStateProps {
title?: string;
message?: string;
iconName?: string;
actionText?: string;
onAction?: () => void;
style?: any;
showRefreshButton?: boolean;
onRefresh?: () => void;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width, height } = Dimensions.get('window');
// ============================================================================
// EMPTY STATE COMPONENT
// ============================================================================
/**
* EmptyState Component
*
* Purpose: Display empty state for AI predictions
*
* Features:
* - Customizable title and message
* - Icon display with customizable icon
* - Optional action button
* - Refresh functionality
* - Responsive design
* - Modern empty state design
* - Accessibility support
*/
const EmptyState: React.FC<EmptyStateProps> = ({
title = 'No AI Predictions Found',
message = 'There are no AI prediction cases available at the moment. Try adjusting your filters or refresh to see new predictions.',
iconName = 'brain',
actionText = 'Refresh',
onAction,
style,
showRefreshButton = true,
onRefresh,
}) => {
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Action Press
*
* Purpose: Handle action button press
*/
const handleActionPress = () => {
if (onAction) {
onAction();
} else if (onRefresh) {
onRefresh();
}
};
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={[styles.container, style]}>
{/* Empty State Icon */}
<View style={styles.iconContainer}>
<Icon
name={iconName}
size={64}
color={theme.colors.textMuted}
style={styles.icon}
/>
</View>
{/* Empty State Title */}
<Text style={styles.title} accessibilityRole="header">
{title}
</Text>
{/* Empty State Message */}
<Text style={styles.message}>
{message}
</Text>
{/* Action Buttons */}
<View style={styles.buttonsContainer}>
{/* Primary Action Button */}
{(onAction || onRefresh) && (
<TouchableOpacity
style={styles.actionButton}
onPress={handleActionPress}
accessibilityRole="button"
accessibilityLabel={actionText}
>
<Icon
name="refresh-cw"
size={18}
color={theme.colors.background}
style={styles.buttonIcon}
/>
<Text style={styles.actionButtonText}>
{actionText}
</Text>
</TouchableOpacity>
)}
{/* Secondary Refresh Button */}
{showRefreshButton && onRefresh && !onAction && (
<TouchableOpacity
style={styles.secondaryButton}
onPress={onRefresh}
accessibilityRole="button"
accessibilityLabel="Refresh data"
>
<Icon
name="refresh-cw"
size={16}
color={theme.colors.primary}
style={styles.buttonIcon}
/>
<Text style={styles.secondaryButtonText}>
Refresh Data
</Text>
</TouchableOpacity>
)}
</View>
{/* Suggestions */}
<View style={styles.suggestionsContainer}>
<Text style={styles.suggestionsTitle}>Try:</Text>
<View style={styles.suggestionsList}>
<View style={styles.suggestionItem}>
<Icon name="search" size={14} color={theme.colors.textMuted} />
<Text style={styles.suggestionText}>Clearing search filters</Text>
</View>
<View style={styles.suggestionItem}>
<Icon name="filter" size={14} color={theme.colors.textMuted} />
<Text style={styles.suggestionText}>Adjusting filter criteria</Text>
</View>
<View style={styles.suggestionItem}>
<Icon name="refresh-cw" size={14} color={theme.colors.textMuted} />
<Text style={styles.suggestionText}>Refreshing the data</Text>
</View>
</View>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.xxl,
minHeight: height * 0.4,
},
iconContainer: {
width: 120,
height: 120,
borderRadius: 60,
backgroundColor: theme.colors.backgroundAlt,
justifyContent: 'center',
alignItems: 'center',
marginBottom: theme.spacing.xl,
...theme.shadows.small,
},
icon: {
opacity: 0.6,
},
title: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
textAlign: 'center',
marginBottom: theme.spacing.md,
},
message: {
fontSize: theme.typography.fontSize.bodyLarge,
color: theme.colors.textSecondary,
textAlign: 'center',
lineHeight: theme.typography.lineHeight.relaxed * theme.typography.fontSize.bodyLarge,
marginBottom: theme.spacing.xl,
maxWidth: width * 0.8,
},
buttonsContainer: {
flexDirection: 'row',
gap: theme.spacing.md,
marginBottom: theme.spacing.xl,
},
actionButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
...theme.shadows.medium,
},
actionButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
secondaryButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: theme.colors.primary,
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
},
secondaryButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.primary,
},
buttonIcon: {
// No additional styles needed
},
suggestionsContainer: {
alignItems: 'center',
maxWidth: width * 0.8,
},
suggestionsTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.sm,
},
suggestionsList: {
gap: theme.spacing.sm,
},
suggestionItem: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
},
suggestionText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textMuted,
},
});
export default EmptyState;
/*
* End of File: EmptyState.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,368 +0,0 @@
/*
* File: FilterTabs.tsx
* Description: Filter tabs component for AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import type { AIPredictionState } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface FilterTabsProps {
selectedUrgencyFilter: AIPredictionState['selectedUrgencyFilter'];
selectedSeverityFilter: AIPredictionState['selectedSeverityFilter'];
selectedCategoryFilter: AIPredictionState['selectedCategoryFilter'];
onUrgencyFilterChange: (filter: AIPredictionState['selectedUrgencyFilter']) => void;
onSeverityFilterChange: (filter: AIPredictionState['selectedSeverityFilter']) => void;
onCategoryFilterChange: (filter: AIPredictionState['selectedCategoryFilter']) => void;
onClearFilters: () => void;
filterCounts?: {
urgency: Record<string, number>;
severity: Record<string, number>;
category: Record<string, number>;
};
activeFiltersCount?: number;
}
interface FilterOption {
label: string;
value: string;
count?: number;
color?: string;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const URGENCY_FILTERS: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'Emergency', value: 'emergency', color: '#F44336' },
{ label: 'Urgent', value: 'urgent', color: '#FF5722' },
{ label: 'Moderate', value: 'moderate', color: '#FF9800' },
{ label: 'Low', value: 'low', color: '#FFC107' },
{ label: 'Routine', value: 'routine', color: '#4CAF50' },
];
const SEVERITY_FILTERS: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'High', value: 'high', color: '#F44336' },
{ label: 'Medium', value: 'medium', color: '#FF9800' },
{ label: 'Low', value: 'low', color: '#FFC107' },
{ label: 'None', value: 'none', color: '#4CAF50' },
];
const CATEGORY_FILTERS: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'Critical', value: 'critical', color: '#F44336' },
{ label: 'Abnormal', value: 'abnormal', color: '#FF9800' },
{ label: 'Warning', value: 'warning', color: '#FFC107' },
{ label: 'Normal', value: 'normal', color: '#4CAF50' },
{ label: 'Unknown', value: 'unknown', color: '#9E9E9E' },
];
// ============================================================================
// FILTER TABS COMPONENT
// ============================================================================
/**
* FilterTabs Component
*
* Purpose: Provide filtering functionality for AI predictions
*
* Features:
* - Multiple filter categories (urgency, severity, category)
* - Visual filter counts
* - Active filter indicators
* - Clear all filters functionality
* - Color-coded filter options
* - Horizontal scroll support
* - Responsive design
* - Accessibility support
*/
const FilterTabs: React.FC<FilterTabsProps> = ({
selectedUrgencyFilter,
selectedSeverityFilter,
selectedCategoryFilter,
onUrgencyFilterChange,
onSeverityFilterChange,
onCategoryFilterChange,
onClearFilters,
filterCounts,
activeFiltersCount = 0,
}) => {
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Filter Count
*
* Purpose: Get count for specific filter value
*/
const getFilterCount = (category: 'urgency' | 'severity' | 'category', value: string): number => {
return filterCounts?.[category]?.[value] || 0;
};
/**
* Render Filter Tab
*
* Purpose: Render individual filter tab
*/
const renderFilterTab = (
option: FilterOption,
isSelected: boolean,
onPress: () => void,
category: 'urgency' | 'severity' | 'category'
) => {
const count = getFilterCount(category, option.value);
return (
<TouchableOpacity
key={option.value}
style={[
styles.filterTab,
isSelected && styles.selectedFilterTab,
isSelected && option.color && { borderColor: option.color },
]}
onPress={onPress}
accessibilityRole="button"
accessibilityState={{ selected: isSelected }}
accessibilityLabel={`Filter by ${option.label}${count > 0 ? `, ${count} items` : ''}`}
>
{option.color && isSelected && (
<View style={[styles.colorIndicator, { backgroundColor: option.color }]} />
)}
<Text style={[
styles.filterTabText,
isSelected && styles.selectedFilterTabText,
isSelected && option.color && { color: option.color },
]}>
{option.label}
</Text>
{count > 0 && (
<View style={[
styles.countBadge,
isSelected && styles.selectedCountBadge,
isSelected && option.color && { backgroundColor: option.color },
]}>
<Text style={[
styles.countText,
isSelected && styles.selectedCountText,
]}>
{count}
</Text>
</View>
)}
</TouchableOpacity>
);
};
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={styles.container}>
{/* Header with Clear Filters */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Filters</Text>
{activeFiltersCount > 0 && (
<TouchableOpacity
style={styles.clearButton}
onPress={onClearFilters}
accessibilityRole="button"
accessibilityLabel="Clear all filters"
>
<Icon name="x" size={16} color={theme.colors.primary} />
<Text style={styles.clearButtonText}>Clear All</Text>
</TouchableOpacity>
)}
</View>
{/* Urgency Filters */}
<View style={styles.filterSection}>
<Text style={styles.sectionTitle}>Clinical Urgency</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{URGENCY_FILTERS.map((option) =>
renderFilterTab(
{ ...option, count: getFilterCount('urgency', option.value) },
selectedUrgencyFilter === option.value,
() => onUrgencyFilterChange(option.value as AIPredictionState['selectedUrgencyFilter']),
'urgency'
)
)}
</ScrollView>
</View>
{/* Severity Filters */}
<View style={styles.filterSection}>
<Text style={styles.sectionTitle}>Primary Severity</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{SEVERITY_FILTERS.map((option) =>
renderFilterTab(
{ ...option, count: getFilterCount('severity', option.value) },
selectedSeverityFilter === option.value,
() => onSeverityFilterChange(option.value as AIPredictionState['selectedSeverityFilter']),
'severity'
)
)}
</ScrollView>
</View>
{/* Category Filters */}
<View style={styles.filterSection}>
<Text style={styles.sectionTitle}>Finding Category</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{CATEGORY_FILTERS.map((option) =>
renderFilterTab(
{ ...option, count: getFilterCount('category', option.value) },
selectedCategoryFilter === option.value,
() => onCategoryFilterChange(option.value as AIPredictionState['selectedCategoryFilter']),
'category'
)
)}
</ScrollView>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
paddingVertical: theme.spacing.md,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.md,
},
headerTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
clearButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
borderWidth: 1,
borderColor: theme.colors.primary,
},
clearButtonText: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
filterSection: {
marginBottom: theme.spacing.lg,
},
sectionTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.textSecondary,
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.sm,
},
filterRow: {
paddingHorizontal: theme.spacing.md,
gap: theme.spacing.sm,
},
filterTab: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.border,
backgroundColor: theme.colors.background,
gap: theme.spacing.xs,
},
selectedFilterTab: {
borderColor: theme.colors.primary,
backgroundColor: theme.colors.backgroundAccent,
},
colorIndicator: {
width: 8,
height: 8,
borderRadius: 4,
},
filterTabText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
selectedFilterTabText: {
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.bold,
},
countBadge: {
backgroundColor: theme.colors.textMuted,
borderRadius: theme.borderRadius.small,
paddingHorizontal: theme.spacing.xs,
paddingVertical: 2,
minWidth: 20,
alignItems: 'center',
},
selectedCountBadge: {
backgroundColor: theme.colors.primary,
},
countText: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.bold,
},
selectedCountText: {
color: theme.colors.background,
},
});
export default FilterTabs;
/*
* End of File: FilterTabs.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,139 +0,0 @@
/*
* File: LoadingState.tsx
* Description: Loading state component for AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
ActivityIndicator,
Dimensions,
} from 'react-native';
import { theme } from '../../../theme';
// ============================================================================
// INTERFACES
// ============================================================================
interface LoadingStateProps {
message?: string;
showSpinner?: boolean;
size?: 'small' | 'large';
style?: any;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width, height } = Dimensions.get('window');
// ============================================================================
// LOADING STATE COMPONENT
// ============================================================================
/**
* LoadingState Component
*
* Purpose: Display loading state for AI predictions
*
* Features:
* - Customizable loading message
* - Optional spinner display
* - Different spinner sizes
* - Custom styling support
* - Centered layout
* - Accessibility support
*/
const LoadingState: React.FC<LoadingStateProps> = ({
message = 'Loading AI predictions...',
showSpinner = true,
size = 'large',
style,
}) => {
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={[styles.container, style]} accessibilityRole="progressbar">
{/* Loading Spinner */}
{showSpinner && (
<ActivityIndicator
size={size}
color={theme.colors.primary}
style={styles.spinner}
/>
)}
{/* Loading Message */}
<Text style={styles.message} accessibilityLabel={message}>
{message}
</Text>
{/* Loading Animation Dots */}
<View style={styles.dotsContainer}>
<View style={[styles.dot, styles.dot1]} />
<View style={[styles.dot, styles.dot2]} />
<View style={[styles.dot, styles.dot3]} />
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.xxl,
minHeight: height * 0.3,
},
spinner: {
marginBottom: theme.spacing.lg,
},
message: {
fontSize: theme.typography.fontSize.bodyLarge,
color: theme.colors.textSecondary,
textAlign: 'center',
fontWeight: theme.typography.fontWeight.medium,
marginBottom: theme.spacing.xl,
},
dotsContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: theme.colors.primary,
},
dot1: {
opacity: 0.3,
},
dot2: {
opacity: 0.6,
},
dot3: {
opacity: 1,
},
});
export default LoadingState;
/*
* End of File: LoadingState.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,226 +0,0 @@
/*
* File: SearchBar.tsx
* Description: Search bar component for filtering AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState, useCallback } from 'react';
import {
View,
TextInput,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
// ============================================================================
// INTERFACES
// ============================================================================
interface SearchBarProps {
value: string;
onChangeText: (text: string) => void;
onClear?: () => void;
placeholder?: string;
autoFocus?: boolean;
disabled?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
// ============================================================================
// SEARCH BAR COMPONENT
// ============================================================================
/**
* SearchBar Component
*
* Purpose: Provide search functionality for AI predictions
*
* Features:
* - Real-time search input
* - Clear button functionality
* - Customizable placeholder text
* - Auto-focus support
* - Disabled state support
* - Modern design with icons
* - Responsive width
* - Accessibility support
*/
const SearchBar: React.FC<SearchBarProps> = ({
value,
onChangeText,
onClear,
placeholder = 'Search predictions...',
autoFocus = false,
disabled = false,
}) => {
// ============================================================================
// STATE
// ============================================================================
const [isFocused, setIsFocused] = useState(false);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Focus
*
* Purpose: Handle input focus state
*/
const handleFocus = useCallback(() => {
setIsFocused(true);
}, []);
/**
* Handle Blur
*
* Purpose: Handle input blur state
*/
const handleBlur = useCallback(() => {
setIsFocused(false);
}, []);
/**
* Handle Clear
*
* Purpose: Clear search input
*/
const handleClear = useCallback(() => {
onChangeText('');
if (onClear) {
onClear();
}
}, [onChangeText, onClear]);
/**
* Handle Text Change
*
* Purpose: Handle search text input
*/
const handleTextChange = useCallback((text: string) => {
onChangeText(text);
}, [onChangeText]);
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={[
styles.container,
isFocused && styles.focusedContainer,
disabled && styles.disabledContainer,
]}>
{/* Search Icon */}
<Icon
name="search"
size={20}
color={isFocused ? theme.colors.primary : theme.colors.textMuted}
style={styles.searchIcon}
/>
{/* Text Input */}
<TextInput
style={[
styles.input,
disabled && styles.disabledInput,
]}
value={value}
onChangeText={handleTextChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
placeholderTextColor={theme.colors.textMuted}
autoFocus={autoFocus}
editable={!disabled}
selectTextOnFocus={!disabled}
autoCorrect={false}
autoCapitalize="none"
returnKeyType="search"
clearButtonMode="never" // We handle clear button manually
accessibilityLabel="Search AI predictions"
accessibilityHint="Enter patient ID, finding type, or location to search"
/>
{/* Clear Button */}
{value.length > 0 && !disabled && (
<TouchableOpacity
style={styles.clearButton}
onPress={handleClear}
accessibilityRole="button"
accessibilityLabel="Clear search"
accessibilityHint="Clear the search input"
>
<Icon
name="x"
size={18}
color={theme.colors.textMuted}
/>
</TouchableOpacity>
)}
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.background,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
marginHorizontal: theme.spacing.md,
marginVertical: theme.spacing.sm,
...theme.shadows.small,
},
focusedContainer: {
borderColor: theme.colors.primary,
backgroundColor: theme.colors.background,
},
disabledContainer: {
backgroundColor: theme.colors.backgroundAlt,
opacity: 0.6,
},
searchIcon: {
marginRight: theme.spacing.sm,
},
input: {
flex: 1,
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textPrimary,
paddingVertical: 0, // Remove default padding to maintain consistent height
fontFamily: theme.typography.fontFamily.regular,
},
disabledInput: {
color: theme.colors.textMuted,
},
clearButton: {
marginLeft: theme.spacing.sm,
padding: theme.spacing.xs,
},
});
export default SearchBar;
/*
* End of File: SearchBar.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,454 +0,0 @@
/*
* File: StatsOverview.tsx
* Description: Statistics overview component for AI predictions dashboard
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import type { AIPredictionStats } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface StatsOverviewProps {
stats: AIPredictionStats;
onStatsPress?: (statType: string) => void;
isLoading?: boolean;
style?: any;
}
interface StatCardProps {
title: string;
value: string | number;
subtitle?: string;
iconName: string;
color: string;
onPress?: () => void;
trend?: number;
isPercentage?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const CARD_WIDTH = (width - 48) / 2; // Two cards per row with margins
// ============================================================================
// STAT CARD COMPONENT
// ============================================================================
/**
* StatCard Component
*
* Purpose: Individual statistics card
*/
const StatCard: React.FC<StatCardProps> = ({
title,
value,
subtitle,
iconName,
color,
onPress,
trend,
isPercentage = false,
}) => {
const displayValue = typeof value === 'number'
? isPercentage
? `${Math.round(value * 100)}%`
: value.toLocaleString()
: value;
return (
<TouchableOpacity
style={[styles.statCard, { borderLeftColor: color }]}
onPress={onPress}
disabled={!onPress}
accessibilityRole="button"
accessibilityLabel={`${title}: ${displayValue}${subtitle ? `, ${subtitle}` : ''}`}
>
{/* Card Header */}
<View style={styles.cardHeader}>
<View style={{flexDirection: 'row', alignItems: 'center', gap: theme.spacing.sm}}>
<View style={[styles.iconContainer, { backgroundColor: color + '20' }]}>
<Icon name={iconName} size={20} color={color} />
</View>
<Text style={styles.statValue}>{displayValue}</Text>
</View>
{trend !== undefined && (
<View style={styles.trendContainer}>
<Icon
name={trend >= 0 ? 'trending-up' : 'trending-down'}
size={14}
color={trend >= 0 ? theme.colors.success : theme.colors.error}
/>
<Text style={[
styles.trendText,
{ color: trend >= 0 ? theme.colors.success : theme.colors.error }
]}>
{Math.abs(trend).toFixed(1)}%
</Text>
</View>
)}
</View>
{/* Card Content */}
<View style={styles.cardContent}>
<Text style={styles.statTitle} numberOfLines={2}>{title}</Text>
{subtitle && (
<Text style={styles.statSubtitle} numberOfLines={1}>{subtitle}</Text>
)}
</View>
</TouchableOpacity>
);
};
// ============================================================================
// STATS OVERVIEW COMPONENT
// ============================================================================
/**
* StatsOverview Component
*
* Purpose: Display comprehensive AI predictions statistics
*
* Features:
* - Total cases overview
* - Critical and urgent case counts
* - Review progress tracking
* - Average confidence metrics
* - Trend indicators
* - Interactive stat cards
* - Responsive grid layout
* - Modern card design
* - Accessibility support
*/
const StatsOverview: React.FC<StatsOverviewProps> = ({
stats,
onStatsPress,
isLoading = false,
style,
}) => {
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Stat Press
*
* Purpose: Handle statistics card press
*/
const handleStatPress = (statType: string) => {
if (onStatsPress) {
onStatsPress(statType);
}
};
// ============================================================================
// RENDER
// ============================================================================
if (isLoading) {
return (
<View style={[styles.container, style]}>
<View style={styles.header}>
<Text style={styles.sectionTitle}>AI Predictions Overview</Text>
</View>
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading statistics...</Text>
</View>
</View>
);
}
return (
<View style={[styles.container, style]}>
{/* Section Header */}
<View style={styles.header}>
<Text style={styles.sectionTitle}>AI Predictions Overview</Text>
<TouchableOpacity
style={styles.viewAllButton}
onPress={() => handleStatPress('all')}
accessibilityRole="button"
accessibilityLabel="View all statistics"
>
<Text style={styles.viewAllText}>View All</Text>
<Icon name="arrow-right" size={16} color={theme.colors.primary} />
</TouchableOpacity>
</View>
{/* Statistics Grid */}
<View style={styles.statsGrid}>
{/* Total Cases */}
<StatCard
title="Total Cases"
value={stats.totalCases}
subtitle="All predictions"
iconName="database"
color={theme.colors.primary}
onPress={() => handleStatPress('total')}
/>
{/* Critical Cases */}
<StatCard
title="Critical Cases"
value={stats.criticalCases}
subtitle="Require attention"
iconName="alert-triangle"
color={theme.colors.error}
onPress={() => handleStatPress('critical')}
/>
{/* Urgent Cases */}
<StatCard
title="Urgent Cases"
value={stats.urgentCases}
subtitle="High priority"
iconName="clock"
color={theme.colors.warning}
onPress={() => handleStatPress('urgent')}
/>
{/* Reviewed Cases */}
<StatCard
title="Reviewed Cases"
value={stats.reviewedCases}
subtitle="Completed reviews"
iconName="check-circle"
color={theme.colors.success}
onPress={() => handleStatPress('reviewed')}
/>
{/* Pending Cases */}
<StatCard
title="Pending Reviews"
value={stats.pendingCases}
subtitle="Awaiting review"
iconName="eye"
color={theme.colors.info}
onPress={() => handleStatPress('pending')}
/>
{/* Average Confidence */}
<StatCard
title="Avg Confidence"
value={stats.averageConfidence}
subtitle="AI accuracy"
iconName="trending-up"
color={theme.colors.primary}
onPress={() => handleStatPress('confidence')}
isPercentage={true}
/>
{/* Today's Cases */}
<StatCard
title="Today's Cases"
value={stats.todaysCases}
subtitle="New predictions"
iconName="calendar"
color={theme.colors.info}
onPress={() => handleStatPress('today')}
/>
{/* Weekly Trend */}
<StatCard
title="Weekly Trend"
value={`${stats.weeklyTrend >= 0 ? '+' : ''}${stats.weeklyTrend.toFixed(1)}%`}
subtitle="vs last week"
iconName={stats.weeklyTrend >= 0 ? 'trending-up' : 'trending-down'}
color={stats.weeklyTrend >= 0 ? theme.colors.success : theme.colors.error}
onPress={() => handleStatPress('trend')}
trend={stats.weeklyTrend}
/>
</View>
{/* Summary Section */}
<View style={styles.summarySection}>
<View style={styles.summaryCard}>
<View style={styles.summaryHeader}>
<Icon name="activity" size={20} color={theme.colors.primary} />
<Text style={styles.summaryTitle}>Quick Insights</Text>
</View>
<View style={styles.summaryContent}>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Review Progress:</Text>
<Text style={styles.summaryValue}>
{Math.round((stats.reviewedCases / stats.totalCases) * 100)}%
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Critical Rate:</Text>
<Text style={styles.summaryValue}>
{Math.round((stats.criticalCases / stats.totalCases) * 100)}%
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Daily Average:</Text>
<Text style={styles.summaryValue}>
{Math.round(stats.totalCases / 7)} cases
</Text>
</View>
</View>
</View>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
paddingVertical: theme.spacing.lg,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.lg,
},
sectionTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
viewAllButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
viewAllText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
loadingContainer: {
paddingVertical: theme.spacing.xxl,
alignItems: 'center',
},
loadingText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textMuted,
},
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: theme.spacing.md,
gap: theme.spacing.md,
},
statCard: {
width: CARD_WIDTH,
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
borderLeftWidth: 4,
padding: theme.spacing.md,
...theme.shadows.medium,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing.sm,
},
iconContainer: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
},
trendContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
trendText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
},
cardContent: {
gap: theme.spacing.xs,
},
statValue: {
fontSize: theme.typography.fontSize.displayMedium,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
statTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
statSubtitle: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textMuted,
},
summarySection: {
paddingHorizontal: theme.spacing.md,
marginTop: theme.spacing.lg,
},
summaryCard: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.lg,
...theme.shadows.small,
},
summaryHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
marginBottom: theme.spacing.md,
},
summaryTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
summaryContent: {
gap: theme.spacing.sm,
},
summaryItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
summaryLabel: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
},
summaryValue: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
});
export default StatsOverview;
/*
* End of File: StatsOverview.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,19 +0,0 @@
/*
* File: index.ts
* Description: Components exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AIPredictionCard } from './AIPredictionCard';
export { default as SearchBar } from './SearchBar';
export { default as FilterTabs } from './FilterTabs';
export { default as LoadingState } from './LoadingState';
export { default as EmptyState } from './EmptyState';
export { default as StatsOverview } from './StatsOverview';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,14 +0,0 @@
/*
* File: index.ts
* Description: Hooks exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './useAIPredictions';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,383 +0,0 @@
/*
* File: useAIPredictions.ts
* Description: Custom hook for AI Predictions functionality
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useCallback, useEffect, useMemo } from 'react';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
// Import Redux actions and selectors
import {
fetchAIPredictions,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
clearAllFilters,
updateCaseReview,
} from '../redux';
import {
selectPaginatedCases,
selectIsLoading,
selectError,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectCasesStatistics,
selectActiveFiltersCount,
selectCurrentPage,
selectTotalPages,
} from '../redux';
// Import auth selector
import { selectUser } from '../../Auth/redux/authSelectors';
// Import types
import type { AIPredictionState } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface UseAIPredictionsOptions {
autoLoad?: boolean;
refreshInterval?: number;
}
interface UseAIPredictionsReturn {
// Data
cases: ReturnType<typeof selectPaginatedCases>;
statistics: ReturnType<typeof selectCasesStatistics>;
// Loading states
isLoading: boolean;
error: string | null;
// Filters
searchQuery: string;
urgencyFilter: AIPredictionState['selectedUrgencyFilter'];
severityFilter: AIPredictionState['selectedSeverityFilter'];
categoryFilter: AIPredictionState['selectedCategoryFilter'];
activeFiltersCount: number;
// Pagination
currentPage: number;
totalPages: number;
// Actions
loadPredictions: () => Promise<void>;
refreshPredictions: () => Promise<void>;
setSearch: (query: string) => void;
setUrgency: (filter: AIPredictionState['selectedUrgencyFilter']) => void;
setSeverity: (filter: AIPredictionState['selectedSeverityFilter']) => void;
setCategory: (filter: AIPredictionState['selectedCategoryFilter']) => void;
clearFilters: () => void;
reviewCase: (caseId: string, reviewData?: Partial<{
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by: string;
review_notes: string;
priority: 'critical' | 'high' | 'medium' | 'low';
}>) => Promise<void>;
// Computed properties
hasFilters: boolean;
isEmpty: boolean;
hasError: boolean;
}
// ============================================================================
// USE AI PREDICTIONS HOOK
// ============================================================================
/**
* useAIPredictions Hook
*
* Purpose: Custom hook for managing AI predictions state and actions
*
* Features:
* - Automatic data loading on mount
* - Search and filtering functionality
* - Case review management
* - Error handling
* - Loading states
* - Computed properties for UI state
* - Auto-refresh capability
* - Type-safe actions and selectors
*/
export const useAIPredictions = (options: UseAIPredictionsOptions = {}): UseAIPredictionsReturn => {
const {
autoLoad = true,
refreshInterval,
} = options;
// ============================================================================
// REDUX STATE
// ============================================================================
const dispatch = useAppDispatch();
// Auth state
const user = useAppSelector(selectUser);
// AI Predictions state
const cases = useAppSelector(selectPaginatedCases);
const statistics = useAppSelector(selectCasesStatistics);
const isLoading = useAppSelector(selectIsLoading);
const error = useAppSelector(selectError);
const searchQuery = useAppSelector(selectSearchQuery);
const urgencyFilter = useAppSelector(selectUrgencyFilter);
const severityFilter = useAppSelector(selectSeverityFilter);
const categoryFilter = useAppSelector(selectCategoryFilter);
const activeFiltersCount = useAppSelector(selectActiveFiltersCount);
const currentPage = useAppSelector(selectCurrentPage);
const totalPages = useAppSelector(selectTotalPages);
// ============================================================================
// MEMOIZED VALUES
// ============================================================================
/**
* Has Filters
*
* Purpose: Check if any filters are active
*/
const hasFilters = useMemo(() => activeFiltersCount > 0, [activeFiltersCount]);
/**
* Is Empty
*
* Purpose: Check if the cases list is empty
*/
const isEmpty = useMemo(() => cases.length === 0, [cases.length]);
/**
* Has Error
*
* Purpose: Check if there's an error
*/
const hasError = useMemo(() => error !== null, [error]);
// ============================================================================
// ACTIONS
// ============================================================================
/**
* Load Predictions
*
* Purpose: Load AI predictions from API
*/
const loadPredictions = useCallback(async () => {
if (!user?.access_token) {
throw new Error('User not authenticated');
}
try {
const params = {
page: currentPage,
limit: 20,
...(urgencyFilter !== 'all' && { urgency: urgencyFilter }),
...(severityFilter !== 'all' && { severity: severityFilter }),
...(categoryFilter !== 'all' && { category: categoryFilter }),
...(searchQuery.trim() && { search: searchQuery.trim() }),
};
await dispatch(fetchAIPredictions({
token: user.access_token,
params,
})).unwrap();
} catch (error) {
console.error('Failed to load AI predictions:', error);
throw error;
}
}, [
dispatch,
user?.access_token,
currentPage,
urgencyFilter,
severityFilter,
categoryFilter,
searchQuery,
]);
/**
* Refresh Predictions
*
* Purpose: Refresh AI predictions data
*/
const refreshPredictions = useCallback(async () => {
await loadPredictions();
}, [loadPredictions]);
/**
* Set Search
*
* Purpose: Set search query
*/
const setSearch = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Set Urgency Filter
*
* Purpose: Set urgency filter
*/
const setUrgency = useCallback((filter: AIPredictionState['selectedUrgencyFilter']) => {
dispatch(setUrgencyFilter(filter));
}, [dispatch]);
/**
* Set Severity Filter
*
* Purpose: Set severity filter
*/
const setSeverity = useCallback((filter: AIPredictionState['selectedSeverityFilter']) => {
dispatch(setSeverityFilter(filter));
}, [dispatch]);
/**
* Set Category Filter
*
* Purpose: Set category filter
*/
const setCategory = useCallback((filter: AIPredictionState['selectedCategoryFilter']) => {
dispatch(setCategoryFilter(filter));
}, [dispatch]);
/**
* Clear Filters
*
* Purpose: Clear all active filters
*/
const clearFilters = useCallback(() => {
dispatch(clearAllFilters());
}, [dispatch]);
/**
* Review Case
*
* Purpose: Update case review status
*/
const reviewCase = useCallback(async (
caseId: string,
reviewData: Partial<{
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by: string;
review_notes: string;
priority: 'critical' | 'high' | 'medium' | 'low';
}> = {}
) => {
if (!user?.access_token) {
throw new Error('User not authenticated');
}
try {
const defaultReviewData = {
review_status: 'reviewed' as const,
reviewed_by: user.display_name || user.email || 'Current User',
...reviewData,
};
await dispatch(updateCaseReview({
caseId,
reviewData: defaultReviewData,
token: user.access_token,
})).unwrap();
} catch (error) {
console.error('Failed to review case:', error);
throw error;
}
}, [dispatch, user]);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Auto-load Effect
*
* Purpose: Automatically load predictions on mount if enabled
*/
useEffect(() => {
if (autoLoad && user?.access_token) {
loadPredictions().catch(console.error);
}
}, [autoLoad, user?.access_token, loadPredictions]);
/**
* Auto-refresh Effect
*
* Purpose: Set up auto-refresh interval if specified
*/
useEffect(() => {
if (!refreshInterval || !user?.access_token) return;
const interval = setInterval(() => {
loadPredictions().catch(console.error);
}, refreshInterval);
return () => clearInterval(interval);
}, [refreshInterval, user?.access_token, loadPredictions]);
/**
* Filter Change Effect
*
* Purpose: Reload data when filters change
*/
useEffect(() => {
if (user?.access_token) {
loadPredictions().catch(console.error);
}
}, [urgencyFilter, severityFilter, categoryFilter, searchQuery, currentPage]);
// ============================================================================
// RETURN
// ============================================================================
return {
// Data
cases,
statistics,
// Loading states
isLoading,
error,
// Filters
searchQuery,
urgencyFilter,
severityFilter,
categoryFilter,
activeFiltersCount,
// Pagination
currentPage,
totalPages,
// Actions
loadPredictions,
refreshPredictions,
setSearch,
setUrgency,
setSeverity,
setCategory,
clearFilters,
reviewCase,
// Computed properties
hasFilters,
isEmpty,
hasError,
};
};
/*
* End of File: useAIPredictions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,54 +0,0 @@
/*
* File: index.ts
* Description: Main exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// ============================================================================
// COMPONENT EXPORTS
// ============================================================================
export * from './components';
// ============================================================================
// SCREEN EXPORTS
// ============================================================================
export * from './screens';
// ============================================================================
// NAVIGATION EXPORTS
// ============================================================================
export * from './navigation';
// ============================================================================
// REDUX EXPORTS
// ============================================================================
export * from './redux';
// ============================================================================
// SERVICE EXPORTS
// ============================================================================
export * from './services';
// ============================================================================
// TYPE EXPORTS
// ============================================================================
export * from './types';
// ============================================================================
// HOOK EXPORTS
// ============================================================================
export * from './hooks';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,249 +0,0 @@
/*
* File: AIPredictionStackNavigator.tsx
* Description: Stack navigator for AI Prediction module screens
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
// Import screens
import { AIPredictionsScreen, AIPredictionDetailScreen } from '../screens';
import { ComingSoonScreen, DicomViewer } from '../../../shared/components';
// Import types
import type { AIPredictionStackParamList } from './navigationTypes';
// ============================================================================
// STACK NAVIGATOR SETUP
// ============================================================================
const Stack = createStackNavigator<AIPredictionStackParamList>();
// ============================================================================
// HEADER COMPONENTS
// ============================================================================
/**
* Header Back Button
*
* Purpose: Custom back button for navigation header
*/
const HeaderBackButton: React.FC<{ onPress: () => void }> = ({ onPress }) => (
<TouchableOpacity style={styles.headerButton} onPress={onPress}>
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
</TouchableOpacity>
);
/**
* Header Action Button
*
* Purpose: Custom action button for navigation header
*/
const HeaderActionButton: React.FC<{
iconName: string;
onPress: () => void;
accessibilityLabel?: string;
}> = ({ iconName, onPress, accessibilityLabel }) => (
<TouchableOpacity
style={styles.headerButton}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
>
<Icon name={iconName} size={24} color={theme.colors.textPrimary} />
</TouchableOpacity>
);
// ============================================================================
// SCREEN OPTIONS
// ============================================================================
/**
* Default Screen Options
*
* Purpose: Common screen options for all AI prediction screens
*/
const defaultScreenOptions = {
headerStyle: {
backgroundColor: theme.colors.background,
elevation: 2,
shadowOpacity: 0.1,
shadowRadius: 4,
shadowOffset: { width: 0, height: 2 },
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
headerTitleStyle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
headerTintColor: theme.colors.textPrimary,
headerBackTitleVisible: false,
gestureEnabled: true,
cardStyleInterpolator: ({ current, layouts }: any) => {
return {
cardStyle: {
transform: [
{
translateX: current.progress.interpolate({
inputRange: [0, 1],
outputRange: [layouts.screen.width, 0],
}),
},
],
},
};
},
};
// ============================================================================
// AI PREDICTION STACK NAVIGATOR COMPONENT
// ============================================================================
/**
* AIPredictionStackNavigator Component
*
* Purpose: Stack navigator for AI prediction module
*
* Features:
* - AI Prediction List screen (main screen)
* - AI Prediction Details screen (case details)
* - AI Prediction Filters screen (advanced filtering)
* - AI Prediction Stats screen (detailed statistics)
* - Custom header styling and buttons
* - Smooth navigation transitions
* - Accessibility support
* - Coming soon screens for unimplemented features
*/
const AIPredictionStackNavigator: React.FC = () => {
return (
<Stack.Navigator
initialRouteName="AIPredictionList"
screenOptions={defaultScreenOptions}
>
{/* AI Prediction List Screen */}
<Stack.Screen
name="AIPredictionList"
component={AIPredictionsScreen}
options={({ navigation }) => ({
title: 'AI Predictions',
headerLeft: () => null, // No back button on main screen
headerRight: () => (
<HeaderActionButton
iconName="more-vertical"
onPress={() => {
// Open options menu
// For now, just navigate to stats
// @ts-ignore
navigation.navigate('AIPredictionStats');
}}
accessibilityLabel="More options"
/>
),
})}
/>
{/* AI Prediction Details Screen */}
<Stack.Screen
name="AIPredictionDetails"
component={() => <DicomViewer
dicomUrl={'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm'}
debugMode={true}
onError={(error) => console.log('DICOM Error:', error)}
onLoad={() => console.log('DICOM Viewer loaded successfully')}
/>}
options={({ navigation, route }) => ({
title: 'Create Suggestion',
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
headerRight: () => (
<HeaderActionButton
iconName="help-circle"
onPress={() => {
// Show help for suggestion form
console.log('Show help for case:', route.params?.caseId);
}}
accessibilityLabel="Help"
/>
),
})}
/>
{/* AI Prediction Filters Screen */}
<Stack.Screen
name="AIPredictionFilters"
component={ComingSoonScreen}
options={({ navigation }) => ({
title: 'Advanced Filters',
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
headerRight: () => (
<HeaderActionButton
iconName="refresh-cw"
onPress={() => {
// Reset filters
console.log('Reset filters');
}}
accessibilityLabel="Reset filters"
/>
),
})}
/>
{/* AI Prediction Stats Screen */}
<Stack.Screen
name="AIPredictionStats"
component={ComingSoonScreen}
options={({ navigation, route }) => ({
title: 'Statistics',
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
headerRight: () => (
<HeaderActionButton
iconName="download"
onPress={() => {
// Export statistics
console.log('Export stats:', route.params?.timeRange);
}}
accessibilityLabel="Export statistics"
/>
),
})}
/>
</Stack.Navigator>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
headerButton: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
marginHorizontal: theme.spacing.xs,
},
headerButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionStackNavigator;
/*
* End of File: AIPredictionStackNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,16 +0,0 @@
/*
* File: index.ts
* Description: Navigation exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AIPredictionStackNavigator } from './AIPredictionStackNavigator';
export * from './navigationTypes';
export * from './navigationUtils';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,169 +0,0 @@
/*
* File: navigationTypes.ts
* Description: Navigation type definitions for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import type { StackNavigationProp } from '@react-navigation/stack';
import type { RouteProp } from '@react-navigation/native';
// ============================================================================
// AI PREDICTION STACK PARAM LIST
// ============================================================================
/**
* AI Prediction Stack Param List
*
* Purpose: Define navigation parameters for AI prediction screens
*
* Screens:
* - AIPredictionList: Main list of AI predictions
* - AIPredictionDetails: Detailed view of a specific prediction with suggestion form
* - AIPredictionFilters: Advanced filtering options
* - AIPredictionStats: Detailed statistics view
*/
export type AIPredictionStackParamList = {
AIPredictionList: undefined;
AIPredictionDetails: { caseId: string };
AIPredictionFilters: undefined;
AIPredictionStats: { timeRange?: 'today' | 'week' | 'month' };
};
// ============================================================================
// NAVIGATION PROP TYPES
// ============================================================================
/**
* AI Prediction List Navigation Prop
*
* Purpose: Navigation prop type for AI prediction list screen
*/
export type AIPredictionListNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionList'
>;
/**
* AI Prediction Details Navigation Prop
*
* Purpose: Navigation prop type for AI prediction details screen
*/
export type AIPredictionDetailsNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionDetails'
>;
/**
* AI Prediction Filters Navigation Prop
*
* Purpose: Navigation prop type for AI prediction filters screen
*/
export type AIPredictionFiltersNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionFilters'
>;
/**
* AI Prediction Stats Navigation Prop
*
* Purpose: Navigation prop type for AI prediction statistics screen
*/
export type AIPredictionStatsNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionStats'
>;
// ============================================================================
// ROUTE PROP TYPES
// ============================================================================
/**
* AI Prediction List Route Prop
*
* Purpose: Route prop type for AI prediction list screen
*/
export type AIPredictionListRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionList'
>;
/**
* AI Prediction Details Route Prop
*
* Purpose: Route prop type for AI prediction details screen
*/
export type AIPredictionDetailsRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionDetails'
>;
/**
* AI Prediction Filters Route Prop
*
* Purpose: Route prop type for AI prediction filters screen
*/
export type AIPredictionFiltersRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionFilters'
>;
/**
* AI Prediction Stats Route Prop
*
* Purpose: Route prop type for AI prediction statistics screen
*/
export type AIPredictionStatsRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionStats'
>;
// ============================================================================
// COMBINED PROP TYPES
// ============================================================================
/**
* AI Prediction List Screen Props
*
* Purpose: Combined props for AI prediction list screen
*/
export interface AIPredictionListScreenProps {
navigation: AIPredictionListNavigationProp;
route: AIPredictionListRouteProp;
}
/**
* AI Prediction Details Screen Props
*
* Purpose: Combined props for AI prediction details screen
*/
export interface AIPredictionDetailsScreenProps {
navigation: AIPredictionDetailsNavigationProp;
route: AIPredictionDetailsRouteProp;
}
/**
* AI Prediction Filters Screen Props
*
* Purpose: Combined props for AI prediction filters screen
*/
export interface AIPredictionFiltersScreenProps {
navigation: AIPredictionFiltersNavigationProp;
route: AIPredictionFiltersRouteProp;
}
/**
* AI Prediction Stats Screen Props
*
* Purpose: Combined props for AI prediction statistics screen
*/
export interface AIPredictionStatsScreenProps {
navigation: AIPredictionStatsNavigationProp;
route: AIPredictionStatsRouteProp;
}
/*
* End of File: navigationTypes.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,251 +0,0 @@
/*
* File: navigationUtils.ts
* Description: Navigation utility functions for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { CommonActions } from '@react-navigation/native';
import type { AIPredictionStackParamList } from './navigationTypes';
// ============================================================================
// NAVIGATION UTILITY FUNCTIONS
// ============================================================================
/**
* Navigate to AI Prediction Details
*
* Purpose: Navigate to AI prediction case details screen
*
* @param navigation - Navigation object
* @param caseId - AI prediction case ID
*/
export const navigateToAIPredictionDetails = (
navigation: any,
caseId: string
) => {
navigation.navigate('AIPredictionDetails', { caseId });
};
/**
* Navigate to AI Prediction Filters
*
* Purpose: Navigate to advanced filters screen
*
* @param navigation - Navigation object
*/
export const navigateToAIPredictionFilters = (navigation: any) => {
navigation.navigate('AIPredictionFilters');
};
/**
* Navigate to AI Prediction Statistics
*
* Purpose: Navigate to detailed statistics screen
*
* @param navigation - Navigation object
* @param timeRange - Optional time range filter
*/
export const navigateToAIPredictionStats = (
navigation: any,
timeRange?: 'today' | 'week' | 'month'
) => {
navigation.navigate('AIPredictionStats', { timeRange });
};
/**
* Go Back to AI Prediction List
*
* Purpose: Navigate back to AI prediction list screen
*
* @param navigation - Navigation object
*/
export const goBackToAIPredictionList = (navigation: any) => {
navigation.navigate('AIPredictionList');
};
/**
* Reset to AI Prediction List
*
* Purpose: Reset navigation stack to AI prediction list
*
* @param navigation - Navigation object
*/
export const resetToAIPredictionList = (navigation: any) => {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'AIPredictionList' }],
})
);
};
/**
* Can Go Back
*
* Purpose: Check if navigation can go back
*
* @param navigation - Navigation object
* @returns Boolean indicating if can go back
*/
export const canGoBack = (navigation: any): boolean => {
return navigation.canGoBack();
};
/**
* Get Current Route Name
*
* Purpose: Get the current route name
*
* @param navigation - Navigation object
* @returns Current route name or undefined
*/
export const getCurrentRouteName = (navigation: any): string | undefined => {
return navigation.getCurrentRoute()?.name;
};
/**
* Get Current Route Params
*
* Purpose: Get the current route parameters
*
* @param navigation - Navigation object
* @returns Current route params or undefined
*/
export const getCurrentRouteParams = (navigation: any): any => {
return navigation.getCurrentRoute()?.params;
};
/**
* Navigate with Replace
*
* Purpose: Navigate to a screen by replacing the current one
*
* @param navigation - Navigation object
* @param routeName - Route name to navigate to
* @param params - Optional route parameters
*/
export const navigateWithReplace = (
navigation: any,
routeName: keyof AIPredictionStackParamList,
params?: any
) => {
navigation.replace(routeName, params);
};
/**
* Navigate with Push
*
* Purpose: Navigate to a screen by pushing it onto the stack
*
* @param navigation - Navigation object
* @param routeName - Route name to navigate to
* @param params - Optional route parameters
*/
export const navigateWithPush = (
navigation: any,
routeName: keyof AIPredictionStackParamList,
params?: any
) => {
navigation.push(routeName, params);
};
/**
* Pop Navigation Stack
*
* Purpose: Pop the specified number of screens from the stack
*
* @param navigation - Navigation object
* @param count - Number of screens to pop (default: 1)
*/
export const popNavigationStack = (navigation: any, count: number = 1) => {
navigation.pop(count);
};
/**
* Pop to Top
*
* Purpose: Pop to the top of the navigation stack
*
* @param navigation - Navigation object
*/
export const popToTop = (navigation: any) => {
navigation.popToTop();
};
/**
* Set Navigation Params
*
* Purpose: Set parameters for the current screen
*
* @param navigation - Navigation object
* @param params - Parameters to set
*/
export const setNavigationParams = (navigation: any, params: any) => {
navigation.setParams(params);
};
/**
* Add Navigation Listener
*
* Purpose: Add a navigation event listener
*
* @param navigation - Navigation object
* @param eventName - Event name to listen for
* @param callback - Callback function
* @returns Unsubscribe function
*/
export const addNavigationListener = (
navigation: any,
eventName: string,
callback: (e: any) => void
) => {
return navigation.addListener(eventName, callback);
};
/**
* Remove Navigation Listener
*
* Purpose: Remove a navigation event listener
*
* @param navigation - Navigation object
* @param eventName - Event name
* @param callback - Callback function
*/
export const removeNavigationListener = (
navigation: any,
eventName: string,
callback: (e: any) => void
) => {
navigation.removeListener(eventName, callback);
};
/**
* Check if Screen is Focused
*
* Purpose: Check if the current screen is focused
*
* @param navigation - Navigation object
* @returns Boolean indicating if screen is focused
*/
export const isScreenFocused = (navigation: any): boolean => {
return navigation.isFocused();
};
/**
* Get Navigation State
*
* Purpose: Get the current navigation state
*
* @param navigation - Navigation object
* @returns Navigation state
*/
export const getNavigationState = (navigation: any) => {
return navigation.getState();
};
/*
* End of File: navigationUtils.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,410 +0,0 @@
/*
* File: aiPredictionSelectors.ts
* Description: Redux selectors for AI Prediction state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from '../../../store';
import { AIPredictionCase } from '../types';
// ============================================================================
// BASE SELECTORS
// ============================================================================
/**
* Select AI Prediction State
*
* Purpose: Get the entire AI prediction state
*/
export const selectAIPredictionState = (state: RootState) => state.aiPrediction;
/**
* Select Prediction Cases
*
* Purpose: Get all AI prediction cases
*/
export const selectPredictionCases = (state: RootState) => state.aiPrediction.predictionCases;
/**
* Select Current Case
*
* Purpose: Get the currently selected AI prediction case
*/
export const selectCurrentCase = (state: RootState) => state.aiPrediction.currentCase;
/**
* Select Loading State
*
* Purpose: Get the loading state for AI predictions
*/
export const selectIsLoading = (state: RootState) => state.aiPrediction.isLoading;
/**
* Select Loading Case Details State
*
* Purpose: Get the loading state for case details
*/
export const selectIsLoadingCaseDetails = (state: RootState) => state.aiPrediction.isLoadingCaseDetails;
/**
* Select Error
*
* Purpose: Get the current error message
*/
export const selectError = (state: RootState) => state.aiPrediction.error;
/**
* Select Search Query
*
* Purpose: Get the current search query
*/
export const selectSearchQuery = (state: RootState) => state.aiPrediction.searchQuery;
/**
* Select Filter States
*
* Purpose: Get all filter states
*/
export const selectUrgencyFilter = (state: RootState) => state.aiPrediction.selectedUrgencyFilter;
export const selectSeverityFilter = (state: RootState) => state.aiPrediction.selectedSeverityFilter;
export const selectCategoryFilter = (state: RootState) => state.aiPrediction.selectedCategoryFilter;
/**
* Select Sort Options
*
* Purpose: Get current sort configuration
*/
export const selectSortBy = (state: RootState) => state.aiPrediction.sortBy;
export const selectSortOrder = (state: RootState) => state.aiPrediction.sortOrder;
/**
* Select Pagination
*
* Purpose: Get pagination configuration
*/
export const selectCurrentPage = (state: RootState) => state.aiPrediction.currentPage;
export const selectItemsPerPage = (state: RootState) => state.aiPrediction.itemsPerPage;
export const selectTotalItems = (state: RootState) => state.aiPrediction.totalItems;
/**
* Select UI State
*
* Purpose: Get UI state flags
*/
export const selectShowFilters = (state: RootState) => state.aiPrediction.showFilters;
export const selectSelectedCaseIds = (state: RootState) => state.aiPrediction.selectedCaseIds;
// ============================================================================
// COMPUTED SELECTORS
// ============================================================================
/**
* Select Filtered and Sorted Cases
*
* Purpose: Get AI prediction cases filtered and sorted based on current settings
*/
export const selectFilteredAndSortedCases = createSelector(
[
selectPredictionCases,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectSortBy,
selectSortOrder,
],
(cases, searchQuery, urgencyFilter, severityFilter, categoryFilter, sortBy, sortOrder) => {
let filteredCases = [...cases];
// Apply search filter
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
filteredCases = filteredCases.filter(case_ =>
case_.patid.toLowerCase().includes(query) ||
case_.prediction.label.toLowerCase().includes(query) ||
case_.prediction.anatomical_location.toLowerCase().includes(query)
);
}
// Apply urgency filter
if (urgencyFilter !== 'all') {
filteredCases = filteredCases.filter(case_ =>
case_.prediction.clinical_urgency === urgencyFilter
);
}
// Apply severity filter
if (severityFilter !== 'all') {
filteredCases = filteredCases.filter(case_ =>
case_.prediction.primary_severity === severityFilter
);
}
// Apply category filter
if (categoryFilter !== 'all') {
filteredCases = filteredCases.filter(case_ =>
case_.prediction.finding_category === categoryFilter
);
}
// Apply sorting
filteredCases.sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case 'date':
comparison = new Date(a.created_at || '').getTime() - new Date(b.created_at || '').getTime();
break;
case 'urgency':
const urgencyOrder = { emergency: 5, urgent: 4, moderate: 3, low: 2, routine: 1 };
comparison = (urgencyOrder[a.prediction.clinical_urgency as keyof typeof urgencyOrder] || 0) -
(urgencyOrder[b.prediction.clinical_urgency as keyof typeof urgencyOrder] || 0);
break;
case 'confidence':
comparison = a.prediction.confidence_score - b.prediction.confidence_score;
break;
case 'severity':
const severityOrder = { high: 4, medium: 3, low: 2, none: 1 };
comparison = (severityOrder[a.prediction.primary_severity as keyof typeof severityOrder] || 0) -
(severityOrder[b.prediction.primary_severity as keyof typeof severityOrder] || 0);
break;
default:
break;
}
return sortOrder === 'desc' ? -comparison : comparison;
});
return filteredCases;
}
);
/**
* Select Paginated Cases
*
* Purpose: Get the current page of filtered and sorted cases
*/
export const selectPaginatedCases = createSelector(
[selectFilteredAndSortedCases, selectCurrentPage, selectItemsPerPage],
(filteredCases, currentPage, itemsPerPage) => {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return filteredCases.slice(startIndex, endIndex);
}
);
/**
* Select Critical Cases
*
* Purpose: Get cases marked as critical or emergency
*/
export const selectCriticalCases = createSelector(
[selectPredictionCases],
(cases) => cases.filter(case_ =>
case_.prediction.clinical_urgency === 'emergency' ||
case_.prediction.clinical_urgency === 'urgent' ||
case_.prediction.primary_severity === 'high' ||
case_.priority === 'critical'
)
);
/**
* Select Pending Cases
*
* Purpose: Get cases pending review
*/
export const selectPendingCases = createSelector(
[selectPredictionCases],
(cases) => cases.filter(case_ => case_.review_status === 'pending')
);
/**
* Select Reviewed Cases
*
* Purpose: Get cases that have been reviewed
*/
export const selectReviewedCases = createSelector(
[selectPredictionCases],
(cases) => cases.filter(case_ =>
case_.review_status === 'reviewed' ||
case_.review_status === 'confirmed' ||
case_.review_status === 'disputed'
)
);
/**
* Select Cases by Urgency
*
* Purpose: Group cases by urgency level
*/
export const selectCasesByUrgency = createSelector(
[selectPredictionCases],
(cases) => {
const grouped = {
emergency: [] as AIPredictionCase[],
urgent: [] as AIPredictionCase[],
moderate: [] as AIPredictionCase[],
low: [] as AIPredictionCase[],
routine: [] as AIPredictionCase[],
};
cases.forEach(case_ => {
const urgency = case_.prediction.clinical_urgency as keyof typeof grouped;
if (grouped[urgency]) {
grouped[urgency].push(case_);
}
});
return grouped;
}
);
/**
* Select Cases Statistics
*
* Purpose: Get statistical overview of cases
*/
export const selectCasesStatistics = createSelector(
[selectPredictionCases],
(cases) => {
const total = cases.length;
const critical = cases.filter(c =>
c.prediction.clinical_urgency === 'emergency' ||
c.prediction.clinical_urgency === 'urgent'
).length;
const pending = cases.filter(c => c.review_status === 'pending').length;
const reviewed = cases.filter(c =>
c.review_status === 'reviewed' ||
c.review_status === 'confirmed'
).length;
const averageConfidence = total > 0
? cases.reduce((sum, c) => sum + c.prediction.confidence_score, 0) / total
: 0;
return {
total,
critical,
pending,
reviewed,
averageConfidence: Math.round(averageConfidence * 1000) / 1000, // Round to 3 decimal places
reviewProgress: total > 0 ? Math.round((reviewed / total) * 100) : 0,
};
}
);
/**
* Select Filter Counts
*
* Purpose: Get counts for each filter option
*/
export const selectFilterCounts = createSelector(
[selectPredictionCases],
(cases) => {
const urgencyCounts = {
all: cases.length,
emergency: 0,
urgent: 0,
moderate: 0,
low: 0,
routine: 0,
};
const severityCounts = {
all: cases.length,
high: 0,
medium: 0,
low: 0,
none: 0,
};
const categoryCounts = {
all: cases.length,
normal: 0,
abnormal: 0,
critical: 0,
warning: 0,
unknown: 0,
};
cases.forEach(case_ => {
// Count urgency
const urgency = case_.prediction.clinical_urgency as keyof typeof urgencyCounts;
if (urgencyCounts[urgency] !== undefined) {
urgencyCounts[urgency]++;
}
// Count severity
const severity = case_.prediction.primary_severity as keyof typeof severityCounts;
if (severityCounts[severity] !== undefined) {
severityCounts[severity]++;
}
// Count category
const category = case_.prediction.finding_category as keyof typeof categoryCounts;
if (categoryCounts[category] !== undefined) {
categoryCounts[category]++;
}
});
return {
urgency: urgencyCounts,
severity: severityCounts,
category: categoryCounts,
};
}
);
/**
* Select Total Pages
*
* Purpose: Calculate total number of pages based on filtered results
*/
export const selectTotalPages = createSelector(
[selectFilteredAndSortedCases, selectItemsPerPage],
(filteredCases, itemsPerPage) => Math.ceil(filteredCases.length / itemsPerPage)
);
/**
* Select Has Previous Page
*
* Purpose: Check if there's a previous page available
*/
export const selectHasPreviousPage = createSelector(
[selectCurrentPage],
(currentPage) => currentPage > 1
);
/**
* Select Has Next Page
*
* Purpose: Check if there's a next page available
*/
export const selectHasNextPage = createSelector(
[selectCurrentPage, selectTotalPages],
(currentPage, totalPages) => currentPage < totalPages
);
/**
* Select Active Filters Count
*
* Purpose: Count how many filters are currently active
*/
export const selectActiveFiltersCount = createSelector(
[selectSearchQuery, selectUrgencyFilter, selectSeverityFilter, selectCategoryFilter],
(searchQuery, urgencyFilter, severityFilter, categoryFilter) => {
let count = 0;
if (searchQuery.trim()) count++;
if (urgencyFilter !== 'all') count++;
if (severityFilter !== 'all') count++;
if (categoryFilter !== 'all') count++;
return count;
}
);
/*
* End of File: aiPredictionSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,621 +0,0 @@
/*
* File: aiPredictionSlice.ts
* Description: Redux slice for AI Prediction state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import {
AIPredictionCase,
AIPredictionState,
AIPredictionStats,
AIPredictionAPIResponse
} from '../types';
import { aiPredictionAPI } from '../services';
// ============================================================================
// ASYNC THUNKS
// ============================================================================
/**
* Fetch AI Predictions Async Thunk
*
* Purpose: Fetch AI prediction results from API
*
* @param token - Authentication token
* @param params - Optional query parameters for filtering
* @returns Promise with AI prediction data or error
*/
export const fetchAIPredictions = createAsyncThunk(
'aiPrediction/fetchAIPredictions',
async (payload: {
token: string;
params?: {
page?: number;
limit?: number;
urgency?: string;
severity?: string;
category?: string;
search?: string;
}
}, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getAllPredictions(payload.token, payload.params);
console.log('AI predictions response:', response);
if (response.ok && response.data && response.data.success) {
// Add additional metadata to each case for UI purposes
const enhancedCases = response.data.data.map((aiCase: AIPredictionCase) => ({
...aiCase,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
review_status: 'pending' as const,
priority: getPriorityFromPrediction(aiCase.prediction)
}));
console.log('Enhanced AI prediction cases:', enhancedCases);
return {
cases: enhancedCases as AIPredictionCase[],
total: response.data.total || enhancedCases.length,
page: response.data.page || 1,
limit: response.data.limit || 20
};
} else {
// Fallback to mock data for development
const mockData = generateMockAIPredictions();
return {
cases: mockData,
total: mockData.length,
page: 1,
limit: 20
};
}
} catch (error: any) {
console.error('Fetch AI predictions error:', error);
return rejectWithValue(error.message || 'Failed to fetch AI predictions.');
}
}
);
/**
* Fetch AI Prediction Case Details Async Thunk
*
* Purpose: Fetch detailed information for a specific AI prediction case
*
* @param caseId - AI prediction case ID
* @param token - Authentication token
* @returns Promise with case details or error
*/
export const fetchAIPredictionDetails = createAsyncThunk(
'aiPrediction/fetchAIPredictionDetails',
async (payload: { caseId: string; token: string }, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getCaseDetails(payload.caseId, payload.token);
if (response.ok && response.data) {
return response.data as AIPredictionCase;
} else {
// Fallback to mock data
const mockCase = generateMockAIPredictions().find(c => c.patid === payload.caseId);
if (mockCase) {
return mockCase;
}
throw new Error('Case not found');
}
} catch (error: any) {
console.error('Fetch AI prediction details error:', error);
return rejectWithValue(error.message || 'Failed to fetch case details.');
}
}
);
/**
* Update Case Review Async Thunk
*
* Purpose: Update review status of an AI prediction case
*
* @param caseId - Case ID to update
* @param reviewData - Review data
* @param token - Authentication token
* @returns Promise with updated case or error
*/
export const updateCaseReview = createAsyncThunk(
'aiPrediction/updateCaseReview',
async (payload: {
caseId: string;
reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
priority?: 'critical' | 'high' | 'medium' | 'low';
};
token: string;
}, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.updateCaseReview(
payload.caseId,
payload.reviewData,
payload.token
);
if (response.ok && response.data) {
return {
caseId: payload.caseId,
...payload.reviewData,
updated_at: new Date().toISOString()
};
} else {
throw new Error('Failed to update case review');
}
} catch (error: any) {
console.error('Update case review error:', error);
return rejectWithValue(error.message || 'Failed to update case review.');
}
}
);
/**
* Fetch AI Prediction Statistics Async Thunk
*
* Purpose: Fetch statistics for AI predictions dashboard
*
* @param token - Authentication token
* @param timeRange - Time range filter
* @returns Promise with statistics data or error
*/
export const fetchAIPredictionStats = createAsyncThunk(
'aiPrediction/fetchAIPredictionStats',
async (payload: { token: string; timeRange?: 'today' | 'week' | 'month' }, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getPredictionStats(payload.token, payload.timeRange);
if (response.ok && response.data) {
return response.data as AIPredictionStats;
} else {
// Fallback to mock stats
return generateMockStats();
}
} catch (error: any) {
console.error('Fetch AI prediction stats error:', error);
return rejectWithValue(error.message || 'Failed to fetch statistics.');
}
}
);
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Priority from AI Prediction
*
* Purpose: Determine case priority based on AI prediction results
*/
function getPriorityFromPrediction(prediction: any): 'critical' | 'high' | 'medium' | 'low' {
if (prediction.clinical_urgency === 'emergency' || prediction.primary_severity === 'high') {
return 'critical';
}
if (prediction.clinical_urgency === 'urgent' || prediction.primary_severity === 'medium') {
return 'high';
}
if (prediction.clinical_urgency === 'moderate' || prediction.primary_severity === 'low') {
return 'medium';
}
return 'low';
}
/**
* Generate Mock AI Predictions
*
* Purpose: Generate mock data for development and testing
*/
function generateMockAIPredictions(): AIPredictionCase[] {
return [
{
patid: "demogw05-08-2017",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "midline shift",
finding_type: "pathology",
clinical_urgency: "urgent",
confidence_score: 0.996,
finding_category: "abnormal",
primary_severity: "high",
anatomical_location: "brain"
},
created_at: "2024-01-15T10:30:00Z",
updated_at: "2024-01-15T10:30:00Z",
review_status: "pending",
priority: "critical"
},
{
patid: "demo-patient-002",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "normal brain",
finding_type: "no_pathology",
clinical_urgency: "routine",
confidence_score: 0.892,
finding_category: "normal",
primary_severity: "none",
anatomical_location: "not_applicable"
},
created_at: "2024-01-15T09:15:00Z",
updated_at: "2024-01-15T09:15:00Z",
review_status: "reviewed",
priority: "low"
},
{
patid: "demo-patient-003",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "hemorrhage",
finding_type: "pathology",
clinical_urgency: "emergency",
confidence_score: 0.945,
finding_category: "critical",
primary_severity: "high",
anatomical_location: "temporal lobe"
},
created_at: "2024-01-15T11:45:00Z",
updated_at: "2024-01-15T11:45:00Z",
review_status: "confirmed",
priority: "critical"
}
];
}
/**
* Generate Mock Statistics
*
* Purpose: Generate mock statistics for development
*/
function generateMockStats(): AIPredictionStats {
return {
totalCases: 156,
criticalCases: 23,
urgentCases: 45,
reviewedCases: 89,
pendingCases: 67,
averageConfidence: 0.887,
todaysCases: 12,
weeklyTrend: 15.4
};
}
// ============================================================================
// INITIAL STATE
// ============================================================================
/**
* Initial AI Prediction State
*
* Purpose: Define the initial state for AI predictions
*
* Features:
* - Prediction cases list and management
* - Current case details
* - Loading states for async operations
* - Error handling and messages
* - Search and filtering
* - Pagination support
* - Cache management
*/
const initialState: AIPredictionState = {
// Prediction data
predictionCases: [],
currentCase: null,
// Loading states
isLoading: false,
isRefreshing: false,
isLoadingCaseDetails: false,
// Error handling
error: null,
// Search and filtering
searchQuery: '',
selectedUrgencyFilter: 'all',
selectedSeverityFilter: 'all',
selectedCategoryFilter: 'all',
sortBy: 'date',
sortOrder: 'desc',
// Pagination
currentPage: 1,
itemsPerPage: 20,
totalItems: 0,
// Cache management
lastUpdated: null,
cacheExpiry: null,
// UI state
showFilters: false,
selectedCaseIds: [],
};
// ============================================================================
// AI PREDICTION SLICE
// ============================================================================
/**
* AI Prediction Slice
*
* Purpose: Redux slice for AI prediction state management
*
* Features:
* - AI prediction data management
* - Search and filtering
* - Case review management
* - Pagination
* - Caching
* - Error handling
* - Loading states
*/
const aiPredictionSlice = createSlice({
name: 'aiPrediction',
initialState,
reducers: {
/**
* Clear Error Action
*
* Purpose: Clear AI prediction errors
*/
clearError: (state) => {
state.error = null;
},
/**
* Set Search Query Action
*
* Purpose: Set search query for AI predictions
*/
setSearchQuery: (state, action: PayloadAction<string>) => {
state.searchQuery = action.payload;
state.currentPage = 1; // Reset to first page when searching
},
/**
* Set Urgency Filter Action
*
* Purpose: Set urgency filter for AI predictions
*/
setUrgencyFilter: (state, action: PayloadAction<AIPredictionState['selectedUrgencyFilter']>) => {
state.selectedUrgencyFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Severity Filter Action
*
* Purpose: Set severity filter for AI predictions
*/
setSeverityFilter: (state, action: PayloadAction<AIPredictionState['selectedSeverityFilter']>) => {
state.selectedSeverityFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Category Filter Action
*
* Purpose: Set category filter for AI predictions
*/
setCategoryFilter: (state, action: PayloadAction<AIPredictionState['selectedCategoryFilter']>) => {
state.selectedCategoryFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Sort Action
*
* Purpose: Set sort options for AI predictions
*/
setSort: (state, action: PayloadAction<{ by: 'date' | 'urgency' | 'confidence' | 'severity'; order: 'asc' | 'desc' }>) => {
state.sortBy = action.payload.by;
state.sortOrder = action.payload.order;
},
/**
* Set Current Page Action
*
* Purpose: Set current page for pagination
*/
setCurrentPage: (state, action: PayloadAction<number>) => {
state.currentPage = action.payload;
},
/**
* Set Items Per Page Action
*
* Purpose: Set items per page for pagination
*/
setItemsPerPage: (state, action: PayloadAction<number>) => {
state.itemsPerPage = action.payload;
state.currentPage = 1; // Reset to first page when changing items per page
},
/**
* Set Current Case Action
*
* Purpose: Set the currently selected AI prediction case
*/
setCurrentCase: (state, action: PayloadAction<AIPredictionCase | null>) => {
state.currentCase = action.payload;
},
/**
* Update Case in List Action
*
* Purpose: Update an AI prediction case in the list
*/
updateCaseInList: (state, action: PayloadAction<AIPredictionCase>) => {
const index = state.predictionCases.findIndex(case_ => case_.patid === action.payload.patid);
if (index !== -1) {
state.predictionCases[index] = action.payload;
}
// Update current case if it's the same case
if (state.currentCase && state.currentCase.patid === action.payload.patid) {
state.currentCase = action.payload;
}
},
/**
* Toggle Show Filters Action
*
* Purpose: Toggle the display of filter options
*/
toggleShowFilters: (state) => {
state.showFilters = !state.showFilters;
},
/**
* Clear All Filters Action
*
* Purpose: Reset all filters to default values
*/
clearAllFilters: (state) => {
state.searchQuery = '';
state.selectedUrgencyFilter = 'all';
state.selectedSeverityFilter = 'all';
state.selectedCategoryFilter = 'all';
state.currentPage = 1;
},
/**
* Select Case Action
*
* Purpose: Add/remove case from selected cases
*/
toggleCaseSelection: (state, action: PayloadAction<string>) => {
const caseId = action.payload;
const index = state.selectedCaseIds.indexOf(caseId);
if (index === -1) {
state.selectedCaseIds.push(caseId);
} else {
state.selectedCaseIds.splice(index, 1);
}
},
/**
* Clear Selected Cases Action
*
* Purpose: Clear all selected cases
*/
clearSelectedCases: (state) => {
state.selectedCaseIds = [];
},
/**
* Clear Cache Action
*
* Purpose: Clear AI prediction data cache
*/
clearCache: (state) => {
state.predictionCases = [];
state.currentCase = null;
state.lastUpdated = null;
state.cacheExpiry = null;
},
},
extraReducers: (builder) => {
// Fetch AI Predictions
builder
.addCase(fetchAIPredictions.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchAIPredictions.fulfilled, (state, action) => {
state.isLoading = false;
state.predictionCases = action.payload.cases;
state.totalItems = action.payload.total;
state.lastUpdated = new Date().toLocaleString();
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000).toLocaleString(); // 5 minutes
state.error = null;
})
.addCase(fetchAIPredictions.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
// Fetch AI Prediction Details
builder
.addCase(fetchAIPredictionDetails.pending, (state) => {
state.isLoadingCaseDetails = true;
state.error = null;
})
.addCase(fetchAIPredictionDetails.fulfilled, (state, action) => {
state.isLoadingCaseDetails = false;
state.currentCase = action.payload;
state.error = null;
})
.addCase(fetchAIPredictionDetails.rejected, (state, action) => {
state.isLoadingCaseDetails = false;
state.error = action.payload as string;
});
// Update Case Review
builder
.addCase(updateCaseReview.fulfilled, (state, action) => {
// Update case in list
const index = state.predictionCases.findIndex(case_ => case_.patid === action.payload.caseId);
if (index !== -1) {
state.predictionCases[index] = {
...state.predictionCases[index],
review_status: action.payload.review_status,
reviewed_by: action.payload.reviewed_by,
priority: action.payload.priority,
updated_at: action.payload.updated_at
};
}
// Update current case if it's the same case
if (state.currentCase && state.currentCase.patid === action.payload.caseId) {
state.currentCase = {
...state.currentCase,
review_status: action.payload.review_status,
reviewed_by: action.payload.reviewed_by,
priority: action.payload.priority,
updated_at: action.payload.updated_at
};
}
})
.addCase(updateCaseReview.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
// ============================================================================
// EXPORTS
// ============================================================================
export const {
clearError,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
setSort,
setCurrentPage,
setItemsPerPage,
setCurrentCase,
updateCaseInList,
toggleShowFilters,
clearAllFilters,
toggleCaseSelection,
clearSelectedCases,
clearCache,
} = aiPredictionSlice.actions;
export default aiPredictionSlice.reducer;
/*
* End of File: aiPredictionSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,15 +0,0 @@
/*
* File: index.ts
* Description: Redux exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './aiPredictionSlice';
export * from './aiPredictionSelectors';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,748 +0,0 @@
/*
* File: AIPredictionsScreen.tsx
* Description: Main AI Predictions screen with data rendering and management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useEffect, useCallback, useState } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
RefreshControl,
TouchableOpacity,
Alert,
StatusBar,
SafeAreaView,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
// Import Redux actions and selectors
import {
fetchAIPredictions,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
setCurrentPage,
clearAllFilters,
toggleShowFilters,
toggleCaseSelection,
clearSelectedCases,
updateCaseReview,
} from '../redux';
import {
selectPaginatedCases,
selectIsLoading,
selectError,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectShowFilters,
selectSelectedCaseIds,
selectCasesStatistics,
selectFilterCounts,
selectActiveFiltersCount,
selectCurrentPage,
selectTotalPages,
selectHasNextPage,
selectHasPreviousPage,
} from '../redux';
// Import components
import {
AIPredictionCard,
SearchBar,
FilterTabs,
LoadingState,
EmptyState,
StatsOverview,
} from '../components';
// Import types
import type { AIPredictionCase } from '../types';
// Import auth selector
import { selectUser } from '../../Auth/redux/authSelectors';
// ============================================================================
// INTERFACES
// ============================================================================
interface AIPredictionsScreenProps {
navigation: any;
route?: any;
}
// ============================================================================
// AI PREDICTIONS SCREEN COMPONENT
// ============================================================================
/**
* AIPredictionsScreen Component
*
* Purpose: Main screen for displaying and managing AI prediction cases
*
* Features:
* - Comprehensive AI predictions list
* - Real-time search and filtering
* - Statistics overview dashboard
* - Bulk case selection and actions
* - Pull-to-refresh functionality
* - Pagination support
* - Review status management
* - Modern card-based design
* - Error handling and retry
* - Loading states and empty states
* - Accessibility support
*/
const AIPredictionsScreen: React.FC<AIPredictionsScreenProps> = ({ navigation }) => {
// ============================================================================
// REDUX STATE
// ============================================================================
const dispatch = useAppDispatch();
// Auth state
const user :any = useAppSelector(selectUser);
// AI Prediction state
const cases = useAppSelector(selectPaginatedCases);
const isLoading = useAppSelector(selectIsLoading);
const error = useAppSelector(selectError);
const searchQuery = useAppSelector(selectSearchQuery);
const urgencyFilter = useAppSelector(selectUrgencyFilter);
const severityFilter = useAppSelector(selectSeverityFilter);
const categoryFilter = useAppSelector(selectCategoryFilter);
const showFilters = useAppSelector(selectShowFilters);
const selectedCaseIds = useAppSelector(selectSelectedCaseIds);
const statistics = useAppSelector(selectCasesStatistics);
const filterCounts = useAppSelector(selectFilterCounts);
const activeFiltersCount = useAppSelector(selectActiveFiltersCount);
const currentPage = useAppSelector(selectCurrentPage);
const totalPages = useAppSelector(selectTotalPages);
const hasNextPage = useAppSelector(selectHasNextPage);
const hasPreviousPage = useAppSelector(selectHasPreviousPage);
// ============================================================================
// LOCAL STATE
// ============================================================================
const [refreshing, setRefreshing] = useState(false);
const [showStats, setShowStats] = useState(true);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Load AI Predictions on Mount
*
* Purpose: Fetch AI predictions when component mounts
*/
console.log('user ===>', user);
useEffect(() => {
if (user?.access_token) {
loadAIPredictions();
}
}, [user?.access_token]);
/**
* Load AI Predictions on Filter Change
*
* Purpose: Reload data when filters change
*/
useEffect(() => {
if (user?.access_token) {
loadAIPredictions();
}
}, [urgencyFilter, severityFilter, categoryFilter, searchQuery, currentPage]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Load AI Predictions
*
* Purpose: Fetch AI predictions from API
*/
const loadAIPredictions = useCallback(async () => {
if (!user?.access_token) return;
try {
const params = {
page: currentPage,
limit: 20,
...(urgencyFilter !== 'all' && { urgency: urgencyFilter }),
...(severityFilter !== 'all' && { severity: severityFilter }),
...(categoryFilter !== 'all' && { category: categoryFilter }),
...(searchQuery.trim() && { search: searchQuery.trim() }),
};
await dispatch(fetchAIPredictions({
token: user.access_token,
params,
})).unwrap();
} catch (error) {
console.error('Failed to load AI predictions:', error);
// Error is handled by Redux state
}
}, [dispatch, user?.access_token, currentPage, urgencyFilter, severityFilter, categoryFilter, searchQuery]);
/**
* Handle Refresh
*
* Purpose: Handle pull-to-refresh
*/
const handleRefresh = useCallback(async () => {
setRefreshing(true);
await loadAIPredictions();
setRefreshing(false);
}, [loadAIPredictions]);
/**
* Handle Search
*
* Purpose: Handle search query change
*/
const handleSearch = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Handle Filter Changes
*
* Purpose: Handle filter option changes
*/
const handleUrgencyFilterChange = useCallback((filter: typeof urgencyFilter) => {
dispatch(setUrgencyFilter(filter));
}, [dispatch]);
const handleSeverityFilterChange = useCallback((filter: typeof severityFilter) => {
dispatch(setSeverityFilter(filter));
}, [dispatch]);
const handleCategoryFilterChange = useCallback((filter: typeof categoryFilter) => {
dispatch(setCategoryFilter(filter));
}, [dispatch]);
/**
* Handle Clear Filters
*
* Purpose: Clear all active filters
*/
const handleClearFilters = useCallback(() => {
dispatch(clearAllFilters());
}, [dispatch]);
/**
* Handle Toggle Filters
*
* Purpose: Toggle filter visibility
*/
const handleToggleFilters = useCallback(() => {
dispatch(toggleShowFilters());
}, [dispatch]);
/**
* Handle Case Press
*
* Purpose: Navigate to case details
*/
const handleCasePress = useCallback((predictionCase: AIPredictionCase) => {
navigation.navigate('AIPredictionDetails', { caseId: predictionCase.patid });
}, [navigation]);
/**
* Handle Case Review
*
* Purpose: Handle case review action
*/
const handleCaseReview = useCallback(async (caseId: string) => {
if (!user?.access_token) return;
try {
await dispatch(updateCaseReview({
caseId,
reviewData: {
review_status: 'reviewed',
reviewed_by: user.name || user.email || 'Current User',
},
token: user.access_token,
})).unwrap();
Alert.alert(
'Review Updated',
'Case has been marked as reviewed.',
[{ text: 'OK' }]
);
} catch (error) {
Alert.alert(
'Error',
'Failed to update case review. Please try again.',
[{ text: 'OK' }]
);
}
}, [dispatch, user]);
/**
* Handle Case Selection
*
* Purpose: Handle case selection for bulk operations
*/
const handleCaseSelection = useCallback((caseId: string) => {
dispatch(toggleCaseSelection(caseId));
}, [dispatch]);
/**
* Handle Bulk Actions
*
* Purpose: Handle bulk actions on selected cases
*/
const handleBulkReview = useCallback(() => {
if (selectedCaseIds.length === 0) return;
Alert.alert(
'Bulk Review',
`Mark ${selectedCaseIds.length} cases as reviewed?`,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Confirm',
onPress: async () => {
// Implement bulk review logic here
// For now, just clear selections
dispatch(clearSelectedCases());
},
},
]
);
}, [selectedCaseIds, dispatch]);
/**
* Handle Page Change
*
* Purpose: Handle pagination
*/
const handlePreviousPage = useCallback(() => {
if (hasPreviousPage) {
dispatch(setCurrentPage(currentPage - 1));
}
}, [dispatch, currentPage, hasPreviousPage]);
const handleNextPage = useCallback(() => {
if (hasNextPage) {
dispatch(setCurrentPage(currentPage + 1));
}
}, [dispatch, currentPage, hasNextPage]);
/**
* Handle Stats Press
*
* Purpose: Handle statistics card press
*/
const handleStatsPress = useCallback((statType: string) => {
// Navigate to detailed statistics or apply relevant filters
switch (statType) {
case 'critical':
dispatch(setUrgencyFilter('emergency'));
break;
case 'urgent':
dispatch(setUrgencyFilter('urgent'));
break;
case 'pending':
// Filter for pending reviews
break;
default:
break;
}
}, [dispatch]);
/**
* Handle Retry
*
* Purpose: Handle retry after error
*/
const handleRetry = useCallback(() => {
loadAIPredictions();
}, [loadAIPredictions]);
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* Render AI Prediction Case
*
* Purpose: Render individual AI prediction case card
*/
const renderPredictionCase = useCallback(({ item }: { item: AIPredictionCase }) => (
<AIPredictionCard
predictionCase={item}
onPress={handleCasePress}
onReview={handleCaseReview}
isSelected={selectedCaseIds.includes(item.patid)}
onToggleSelect={handleCaseSelection}
showReviewButton={true}
/>
), [handleCasePress, handleCaseReview, selectedCaseIds, handleCaseSelection]);
/**
* Render List Header
*
* Purpose: Render search, filters, and statistics
*/
const renderListHeader = useCallback(() => (
<View>
{/* Statistics Overview */}
{showStats && (
<StatsOverview
stats={{
totalCases: statistics.total,
criticalCases: statistics.critical,
urgentCases: 0, // Would need to be calculated from urgency filter
reviewedCases: statistics.reviewed,
pendingCases: statistics.pending,
averageConfidence: statistics.averageConfidence,
todaysCases: 0, // Would need to be calculated from today's data
weeklyTrend: 12.5, // Mock data
}}
onStatsPress={handleStatsPress}
/>
)}
{/* Search Bar */}
<SearchBar
value={searchQuery}
onChangeText={handleSearch}
placeholder="Search by patient ID, finding, location..."
/>
{/* Filter Controls */}
<View style={styles.filterControls}>
<TouchableOpacity
style={[styles.filterToggle, showFilters && styles.filterToggleActive]}
onPress={handleToggleFilters}
accessibilityRole="button"
accessibilityLabel="Toggle filters"
>
<Icon name="filter" size={18} color={showFilters ? theme.colors.background : theme.colors.primary} />
<Text style={[styles.filterToggleText, showFilters && styles.filterToggleActiveText]}>
Filters
</Text>
{activeFiltersCount > 0 && (
<View style={styles.filterBadge}>
<Text style={styles.filterBadgeText}>{activeFiltersCount}</Text>
</View>
)}
</TouchableOpacity>
{selectedCaseIds.length > 0 && (
<TouchableOpacity
style={styles.bulkActionButton}
onPress={handleBulkReview}
accessibilityRole="button"
accessibilityLabel={`Bulk actions for ${selectedCaseIds.length} selected cases`}
>
<Icon name="check-circle" size={18} color={theme.colors.background} />
<Text style={styles.bulkActionText}>
Review {selectedCaseIds.length}
</Text>
</TouchableOpacity>
)}
</View>
{/* Filter Tabs */}
{showFilters && (
<FilterTabs
selectedUrgencyFilter={urgencyFilter}
selectedSeverityFilter={severityFilter}
selectedCategoryFilter={categoryFilter}
onUrgencyFilterChange={handleUrgencyFilterChange}
onSeverityFilterChange={handleSeverityFilterChange}
onCategoryFilterChange={handleCategoryFilterChange}
onClearFilters={handleClearFilters}
filterCounts={filterCounts}
activeFiltersCount={activeFiltersCount}
/>
)}
{/* Results Summary */}
<View style={styles.resultsSummary}>
<Text style={styles.resultsText}>
{statistics.total} predictions found
{activeFiltersCount > 0 && ` (${activeFiltersCount} filters applied)`}
</Text>
</View>
</View>
), [
showStats,
statistics,
handleStatsPress,
searchQuery,
handleSearch,
showFilters,
handleToggleFilters,
activeFiltersCount,
selectedCaseIds,
handleBulkReview,
urgencyFilter,
severityFilter,
categoryFilter,
handleUrgencyFilterChange,
handleSeverityFilterChange,
handleCategoryFilterChange,
handleClearFilters,
filterCounts,
]);
/**
* Render List Footer
*
* Purpose: Render pagination controls
*/
const renderListFooter = useCallback(() => {
if (totalPages <= 1) return null;
return (
<View style={styles.paginationContainer}>
<TouchableOpacity
style={[styles.paginationButton, !hasPreviousPage && styles.paginationButtonDisabled]}
onPress={handlePreviousPage}
disabled={!hasPreviousPage}
accessibilityRole="button"
accessibilityLabel="Previous page"
>
<Icon name="chevron-left" size={20} color={hasPreviousPage ? theme.colors.primary : theme.colors.textMuted} />
<Text style={[styles.paginationButtonText, !hasPreviousPage && styles.paginationButtonTextDisabled]}>
Previous
</Text>
</TouchableOpacity>
<Text style={styles.paginationInfo}>
Page {currentPage} of {totalPages}
</Text>
<TouchableOpacity
style={[styles.paginationButton, !hasNextPage && styles.paginationButtonDisabled]}
onPress={handleNextPage}
disabled={!hasNextPage}
accessibilityRole="button"
accessibilityLabel="Next page"
>
<Text style={[styles.paginationButtonText, !hasNextPage && styles.paginationButtonTextDisabled]}>
Next
</Text>
<Icon name="chevron-right" size={20} color={hasNextPage ? theme.colors.primary : theme.colors.textMuted} />
</TouchableOpacity>
</View>
);
}, [totalPages, currentPage, hasPreviousPage, hasNextPage, handlePreviousPage, handleNextPage]);
// ============================================================================
// RENDER
// ============================================================================
return (
<SafeAreaView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>AI Predictions</Text>
<TouchableOpacity
style={styles.headerButton}
onPress={() => setShowStats(!showStats)}
accessibilityRole="button"
accessibilityLabel="Toggle statistics"
>
<Icon name={showStats ? 'eye-off' : 'eye'} size={20} color={theme.colors.primary} />
</TouchableOpacity>
</View>
{/* Content */}
{error ? (
<EmptyState
title="Error Loading Predictions"
message={error}
iconName="alert-circle"
actionText="Retry"
onAction={handleRetry}
/>
) : isLoading && cases.length === 0 ? (
<LoadingState message="Loading AI predictions..." />
) : cases.length === 0 ? (
<EmptyState
title="No AI Predictions Found"
message="There are no AI prediction cases matching your current filters."
iconName="brain"
actionText="Clear Filters"
onAction={activeFiltersCount > 0 ? handleClearFilters : handleRefresh}
/>
) : (
<FlatList
data={cases}
renderItem={renderPredictionCase}
keyExtractor={(item) => item.patid}
ListHeaderComponent={renderListHeader}
ListFooterComponent={renderListFooter}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.listContent}
accessibilityRole="list"
/>
)}
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.background,
},
headerTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
headerButton: {
padding: theme.spacing.sm,
},
filterControls: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
filterToggle: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.primary,
gap: theme.spacing.sm,
},
filterToggleActive: {
backgroundColor: theme.colors.primary,
},
filterToggleText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
filterToggleActiveText: {
color: theme.colors.background,
},
filterBadge: {
backgroundColor: theme.colors.error,
borderRadius: 10,
minWidth: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
},
filterBadgeText: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.bold,
},
bulkActionButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
},
bulkActionText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.medium,
},
resultsSummary: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
resultsText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
},
listContent: {
paddingBottom: theme.spacing.xl,
},
paginationContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.lg,
marginTop: theme.spacing.lg,
},
paginationButton: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.primary,
gap: theme.spacing.xs,
},
paginationButtonDisabled: {
borderColor: theme.colors.textMuted,
opacity: 0.5,
},
paginationButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
paginationButtonTextDisabled: {
color: theme.colors.textMuted,
},
paginationInfo: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionsScreen;
/*
* End of File: AIPredictionsScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,15 +0,0 @@
/*
* File: index.ts
* Description: Screens exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AIPredictionsScreen } from './AIPredictionsScreen';
export { default as AIPredictionDetailScreen } from './AIPredictionDetailScreen';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,337 +0,0 @@
/*
* File: aiPredictionAPI.ts
* Description: API service for AI prediction operations using apisauce
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
import { API_CONFIG, buildHeaders } from '../../../shared/utils';
const api = create({
baseURL: API_CONFIG.BASE_URL
});
/**
* AI Prediction API Service
*
* Purpose: Handle all AI prediction-related API operations
*
* Features:
* - Get AI prediction results for all patients
* - Get individual case prediction details
* - Update case review status
* - Search and filter predictions
* - Get prediction statistics
*/
export const aiPredictionAPI = {
/**
* Get All AI Prediction Results
*
* Purpose: Fetch all AI prediction results from server
*
* @param token - Authentication token
* @param params - Optional query parameters for filtering
* @returns Promise with AI prediction cases data
*/
getAllPredictions: (token: string, params?: {
page?: number;
limit?: number;
urgency?: string;
severity?: string;
category?: string;
search?: string;
}) => {
const queryParams = params ? { ...params } : {};
return api.get('/api/ai-cases/all-prediction-results', queryParams, buildHeaders({ token }));
},
/**
* Get AI Prediction Case Details
*
* Purpose: Fetch detailed information for a specific AI prediction case
*
* @param caseId - AI prediction case ID (patid)
* @param token - Authentication token
* @returns Promise with detailed case prediction data
*/
getCaseDetails: (caseId: string, token: string) => {
return api.get(`/api/ai-cases/prediction-details/${caseId}`, {}, buildHeaders({ token }));
},
/**
* Update Case Review Status
*
* Purpose: Update the review status of an AI prediction case
*
* @param caseId - AI prediction case ID
* @param reviewData - Review status and notes
* @param token - Authentication token
* @returns Promise with updated case data
*/
updateCaseReview: (caseId: string, reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
priority?: 'critical' | 'high' | 'medium' | 'low';
}, token: string) => {
return api.put(`/api/ai-cases/review/${caseId}`, reviewData, buildHeaders({ token }));
},
/**
* Get Prediction Statistics
*
* Purpose: Fetch AI prediction statistics for dashboard
*
* @param token - Authentication token
* @param timeRange - Optional time range filter (today, week, month)
* @returns Promise with prediction statistics
*/
getPredictionStats: (token: string, timeRange?: 'today' | 'week' | 'month') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/statistics', params, buildHeaders({ token }));
},
/**
* Search AI Prediction Cases
*
* Purpose: Search AI prediction cases by various criteria
*
* @param query - Search query (patient ID, hospital, findings)
* @param token - Authentication token
* @param filters - Additional search filters
* @returns Promise with search results
*/
searchPredictions: (query: string, token: string, filters?: {
urgency?: string[];
severity?: string[];
category?: string[];
dateRange?: { start: string; end: string };
}) => {
const params = {
q: query,
...(filters && { filters: JSON.stringify(filters) })
};
return api.get('/api/ai-cases/search', params, buildHeaders({ token }));
},
/**
* Get Predictions by Hospital
*
* Purpose: Fetch AI predictions filtered by hospital
*
* @param hospitalId - Hospital UUID
* @param token - Authentication token
* @param params - Optional query parameters
* @returns Promise with hospital-specific predictions
*/
getPredictionsByHospital: (hospitalId: string, token: string, params?: {
page?: number;
limit?: number;
urgency?: string;
startDate?: string;
endDate?: string;
}) => {
const queryParams = params ? { ...params } : {};
return api.get(`/api/ai-cases/hospital/${hospitalId}/predictions`, queryParams, buildHeaders({ token }));
},
/**
* Get Critical Predictions
*
* Purpose: Fetch only critical and urgent AI predictions
*
* @param token - Authentication token
* @returns Promise with critical predictions data
*/
getCriticalPredictions: (token: string) => {
return api.get('/api/ai-cases/critical-predictions', {}, buildHeaders({ token }));
},
/**
* Bulk Update Case Reviews
*
* Purpose: Update multiple case reviews at once
*
* @param caseIds - Array of case IDs to update
* @param reviewData - Review data to apply to all cases
* @param token - Authentication token
* @returns Promise with bulk update results
*/
bulkUpdateReviews: (caseIds: string[], reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
}, token: string) => {
return api.put('/api/ai-cases/bulk-review', {
caseIds,
reviewData
}, buildHeaders({ token }));
},
/**
* Export Predictions Data
*
* Purpose: Export AI predictions data for reporting
*
* @param token - Authentication token
* @param filters - Export filters
* @param format - Export format (csv, xlsx, pdf)
* @returns Promise with export file data
*/
exportPredictions: (token: string, filters?: {
urgency?: string[];
severity?: string[];
dateRange?: { start: string; end: string };
hospitalId?: string;
}, format: 'csv' | 'xlsx' | 'pdf' = 'csv') => {
const params = {
format,
...(filters && { filters: JSON.stringify(filters) })
};
return api.get('/api/ai-cases/export', params, buildHeaders({ token }));
},
/**
* Get Prediction Trends
*
* Purpose: Fetch prediction trends data for analytics
*
* @param token - Authentication token
* @param period - Time period for trends (daily, weekly, monthly)
* @returns Promise with trends data
*/
getPredictionTrends: (token: string, period: 'daily' | 'weekly' | 'monthly' = 'weekly') => {
return api.get('/api/ai-cases/trends', { period }, buildHeaders({ token }));
},
/**
* Submit Feedback on Prediction
*
* Purpose: Submit physician feedback on AI prediction accuracy
*
* @param caseId - AI prediction case ID
* @param feedbackData - Feedback data
* @param token - Authentication token
* @returns Promise with feedback submission result
*/
submitPredictionFeedback: (caseId: string, feedbackData: {
accuracy_rating: 1 | 2 | 3 | 4 | 5;
is_accurate: boolean;
physician_diagnosis?: string;
feedback_notes?: string;
improvement_suggestions?: string;
}, token: string) => {
return api.post(`/api/ai-cases/feedback/${caseId}`, feedbackData, buildHeaders({ token }));
},
/**
* Submit AI Suggestion
*
* Purpose: Submit physician suggestions for AI findings
*
* @param suggestionData - Suggestion data including patient ID, type, title, text, etc.
* @param token - Authentication token
* @returns Promise with suggestion submission result
*/
submitAISuggestion: (suggestionData: {
patid: string;
suggestion_type: string;
suggestion_title: string;
suggestion_text: string;
confidence_score: number;
priority_level: string;
category: string;
related_findings: Record<string, string>;
evidence_sources: string[];
contraindications: string;
cost_estimate: number;
time_estimate: string;
expires_at: string | null;
tags: string[];
ai_model_version: string;
}, token: string) => {
return api.post('/api/ai-cases/suggestions', suggestionData, buildHeaders({ token }));
},
/**
* Get AI Suggestions
*
* Purpose: Fetch AI suggestions for a specific case or all suggestions
*
* @param token - Authentication token
* @param params - Optional query parameters
* @returns Promise with suggestions data
*/
getAISuggestions: (token: string, params?: {
caseId?: string;
patientId?: string;
suggestionType?: string;
priority?: string;
status?: string;
page?: number;
limit?: number;
}) => {
const queryParams = params ? { ...params } : {};
return api.get('/api/ai-cases/suggestions', queryParams, buildHeaders({ token }));
},
/**
* Update AI Suggestion
*
* Purpose: Update an existing AI suggestion
*
* @param suggestionId - Suggestion ID to update
* @param updateData - Updated suggestion data
* @param token - Authentication token
* @returns Promise with updated suggestion data
*/
updateAISuggestion: (suggestionId: string, updateData: {
suggestion_title?: string;
suggestion_text?: string;
priority_level?: string;
category?: string;
related_findings?: Record<string, string>;
evidence_sources?: string[];
contraindications?: string;
cost_estimate?: number;
time_estimate?: string;
expires_at?: string | null;
tags?: string[];
}, token: string) => {
return api.put(`/api/ai-cases/suggestions/${suggestionId}`, updateData, buildHeaders({ token }));
},
/**
* Delete AI Suggestion
*
* Purpose: Delete an AI suggestion
*
* @param suggestionId - Suggestion ID to delete
* @param token - Authentication token
* @returns Promise with deletion result
*/
deleteAISuggestion: (suggestionId: string, token: string) => {
return api.delete(`/api/ai-cases/suggestions/${suggestionId}`, {}, buildHeaders({ token }));
},
/**
* Get Suggestion Statistics
*
* Purpose: Fetch statistics about AI suggestions
*
* @param token - Authentication token
* @param timeRange - Optional time range filter
* @returns Promise with suggestion statistics
*/
getSuggestionStats: (token: string, timeRange?: 'today' | 'week' | 'month') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/suggestions/statistics', params, buildHeaders({ token }));
}
};
/*
* End of File: aiPredictionAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,14 +0,0 @@
/*
* File: index.ts
* Description: Services exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './aiPredictionAPI';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,221 +0,0 @@
/*
* File: aiPrediction.ts
* Description: Type definitions for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// ============================================================================
// AI PREDICTION INTERFACES
// ============================================================================
/**
* AI Prediction Interface
*
* Purpose: Define the structure of AI prediction data from the API
*
* Based on API response structure:
* - label: Type of medical finding
* - finding_type: Category of the finding
* - clinical_urgency: Urgency level for medical response
* - confidence_score: AI confidence in the prediction (0-1)
* - finding_category: General category of the finding
* - primary_severity: Severity level of the condition
* - anatomical_location: Where the finding is located
*/
export interface AIPrediction {
label: string;
finding_type: 'no_pathology' | 'pathology' | 'abnormal' | 'normal' | 'unknown';
clinical_urgency: 'urgent' | 'moderate' | 'low' | 'routine' | 'emergency';
confidence_score: number; // 0.0 to 1.0
finding_category: 'normal' | 'abnormal' | 'critical' | 'warning' | 'unknown';
primary_severity: 'high' | 'medium' | 'low' | 'none';
anatomical_location: string; // 'not_applicable' | specific location
}
/**
* AI Prediction Case Interface
*
* Purpose: Complete AI prediction case data structure
*
* Features:
* - Patient identification
* - Hospital association
* - AI prediction results
* - Metadata for tracking and display
*/
export interface AIPredictionCase {
patid: string; // Patient ID
hospital_id: string; // Hospital UUID
prediction: AIPrediction;
// Additional metadata (will be added for UI purposes)
created_at?: string;
updated_at?: string;
reviewed_by?: string;
review_status?: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
priority?: 'critical' | 'high' | 'medium' | 'low';
processed_at?: string;
}
/**
* AI Prediction API Response Interface
*
* Purpose: Define the structure of API response
*/
export interface AIPredictionAPIResponse {
success: boolean;
data: AIPredictionCase[];
message?: string;
total?: number;
page?: number;
limit?: number;
}
/**
* AI Prediction State Interface
*
* Purpose: Define Redux state structure for AI predictions
*
* Features:
* - Prediction cases management
* - Current selected case
* - Loading states for async operations
* - Error handling and messages
* - Search and filtering
* - Pagination support
* - Cache management
*/
export interface AIPredictionState {
// Prediction data
predictionCases: AIPredictionCase[];
currentCase: AIPredictionCase | null;
// Loading states
isLoading: boolean;
isRefreshing: boolean;
isLoadingCaseDetails: boolean;
// Error handling
error: string | null;
// Search and filtering
searchQuery: string;
selectedUrgencyFilter: 'all' | 'urgent' | 'moderate' | 'low' | 'routine' | 'emergency';
selectedSeverityFilter: 'all' | 'high' | 'medium' | 'low' | 'none';
selectedCategoryFilter: 'all' | 'normal' | 'abnormal' | 'critical' | 'warning' | 'unknown';
sortBy: 'date' | 'urgency' | 'confidence' | 'severity';
sortOrder: 'asc' | 'desc';
// Pagination
currentPage: number;
itemsPerPage: number;
totalItems: number;
// Cache management
lastUpdated: String | null;
cacheExpiry: String | null;
// UI state
showFilters: boolean;
selectedCaseIds: string[];
}
/**
* AI Prediction Filter Options
*
* Purpose: Define available filter options for the UI
*/
export interface AIPredictionFilters {
urgency: Array<{
label: string;
value: AIPredictionState['selectedUrgencyFilter'];
count?: number;
}>;
severity: Array<{
label: string;
value: AIPredictionState['selectedSeverityFilter'];
count?: number;
}>;
category: Array<{
label: string;
value: AIPredictionState['selectedCategoryFilter'];
count?: number;
}>;
}
/**
* AI Prediction Statistics Interface
*
* Purpose: Define statistics data for dashboard display
*/
export interface AIPredictionStats {
totalCases: number;
criticalCases: number;
urgentCases: number;
reviewedCases: number;
pendingCases: number;
averageConfidence: number;
todaysCases: number;
weeklyTrend: number; // percentage change from last week
}
/**
* AI Prediction Navigation Props
*
* Purpose: Type safety for navigation between AI prediction screens
*/
export type AIPredictionNavigationProps = {
AIPredictionList: undefined;
AIPredictionDetails: { caseId: string };
AIPredictionFilters: undefined;
AIPredictionStats: undefined;
};
// ============================================================================
// UTILITY TYPES
// ============================================================================
/**
* Prediction Urgency Colors
*
* Purpose: Map urgency levels to UI colors
*/
export const URGENCY_COLORS = {
emergency: '#F44336', // Red
urgent: '#FF5722', // Deep Orange
moderate: '#FF9800', // Orange
low: '#FFC107', // Amber
routine: '#4CAF50', // Green
} as const;
/**
* Prediction Severity Colors
*
* Purpose: Map severity levels to UI colors
*/
export const SEVERITY_COLORS = {
high: '#F44336', // Red
medium: '#FF9800', // Orange
low: '#FFC107', // Amber
none: '#4CAF50', // Green
} as const;
/**
* Finding Category Colors
*
* Purpose: Map finding categories to UI colors
*/
export const CATEGORY_COLORS = {
critical: '#F44336', // Red
abnormal: '#FF9800', // Orange
warning: '#FFC107', // Amber
normal: '#4CAF50', // Green
unknown: '#9E9E9E', // Gray
} as const;
/*
* End of File: aiPrediction.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,14 +0,0 @@
/*
* File: index.ts
* Description: Type definitions exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './aiPrediction';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -12,11 +12,6 @@ export { default as SignUpScreen } from './screens/SignUpScreen';
// Export navigation
export {
AuthStackNavigator,
AuthStackParamList,
AuthNavigationProp,
AuthScreenProps,
LoginScreenProps,
SignUpScreenProps,
navigateToLogin,
navigateToSignUp,
goBack,
@ -39,7 +34,6 @@ export {
} from './components/signup';
// Export services
export { authAPI } from './services/signupAPI';
// Export types
export type {
@ -59,17 +53,8 @@ export type {
// Export Redux
export {
loginUser,
ssoLogin,
emergencyAccess,
logoutUser,
clearError,
setBiometricEnabled,
setRememberDevice,
updateUserProfile,
setSessionToken,
clearSession,
setEmergencyAccess,
} from './redux/authSlice';
/*

View File

@ -18,7 +18,6 @@ export const login = createAsyncThunk(
async (credentials: { email: string; password: string }, { rejectWithValue }) => {
try {
const response:any = await authAPI.login(credentials.email, credentials.password,'web');
console.log('response',response)
if(response.data.message && !response.data.success){
showError(response.data.message)
return rejectWithValue(response.data.message);

View File

@ -8,6 +8,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { authAPI } from '../services/authAPI';
import { showError } from '../../../shared/utils/toast';
import { logoutUser } from './authActions';
// ============================================================================
// INTERFACES
@ -51,10 +52,16 @@ interface HospitalState {
*/
export const fetchHospitals = createAsyncThunk(
'hospital/fetchHospitals',
async (_, { rejectWithValue }) => {
async (_, { rejectWithValue, dispatch }) => {
try {
const response: any = await authAPI.gethospitals();
console.log('hospital response', response);
// Check for 401 Unauthorized status and logout user
if (response.status === 401) {
console.log('Unauthorized access detected while fetching hospitals, logging out user');
dispatch(logoutUser());
return rejectWithValue('Session expired. Please login again.');
}
if (response.ok && response.data && response.data.data) {
return response.data.data;

View File

@ -463,7 +463,6 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
formData.append('id_photo', preparedFile 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) {
@ -551,7 +550,6 @@ export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
token: user?.access_token,
});
console.log('reset response', response);
if (response.data && response.data.message) {
if (response.data.success) {

View File

@ -129,7 +129,6 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
try {
const response :any = await authAPI.validatemail({email});
console.log('response', response);
if(response.status==409&&response.data.message){
// Show modal instead of toast for already registered email
@ -167,7 +166,6 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
try {
const response:any = await authAPI.validateusername(username);
console.log('response', response);
if(response.status==409&&response.data.message){
showError(response.data.message);
@ -221,7 +219,6 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
* Purpose: Submit final signup data to API
*/
const onSignUpComplete = async (payload: SignUpData) => {
console.log('final payload', payload);
setIsLoading(true);
try {
@ -266,9 +263,7 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
formData = createFormDataWithFile(formFields);
}
console.log('payload prepared', formData);
const response :any = await authAPI.signup(formData);
console.log('signup response', response);
if(response.ok && response.data && response.data.success ) {
//@ts-ignore
@ -338,7 +333,6 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
* Purpose: Render the appropriate step component based on current step
*/
const renderCurrentStep = () => {
console.log('signupdate', signUpData);
const commonProps = {
onBack: handleBack,

View File

@ -1,23 +0,0 @@
/*
* File: biometricService.ts
* Description: Service for biometric authentication (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export const biometricService = {
authenticate: async () => {
// TODO: Implement biometric authentication
return true;
},
isAvailable: async () => {
// TODO: Check if biometric is available
return true;
},
};
/*
* End of File: biometricService.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -6,7 +6,6 @@
*/
export * from './';
export * from './biometricService';
/*
* End of File: index.ts

View File

@ -1,392 +0,0 @@
/*
* File: signupAPI.ts
* Description: Signup API service with validation and hospital endpoints
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
import { SignUpData, EmailValidationApiResponse, UsernameValidationApiResponse, HospitalListApiResponse, SignUpApiResponse } from '../types/signup';
// ============================================================================
// API CONFIGURATION
// ============================================================================
/**
* API Base Configuration
*
* Purpose: Configure the base API client for signup operations
*
* Features:
* - Base URL configuration
* - Request/response interceptors
* - Error handling
* - Timeout settings
*/
const API_BASE_URL = 'https://api.neoscan-physician.com/v1'; // TODO: Replace with actual API URL
const api = create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
timeout: 30000, // 30 seconds
});
// ============================================================================
// REQUEST INTERCEPTORS
// ============================================================================
/**
* Request Interceptor
*
* Purpose: Add authentication headers and logging
*/
api.addRequestTransform((request) => {
// Add any common headers here
console.log('API Request:', {
method: request.method,
url: request.url,
data: request.data,
});
});
// ============================================================================
// RESPONSE INTERCEPTORS
// ============================================================================
/**
* Response Interceptor
*
* Purpose: Handle common response patterns and errors
*/
api.addResponseTransform((response) => {
console.log('API Response:', {
status: response.status,
url: response.config?.url,
data: response.data,
});
// Handle common error patterns
if (response.status === 401) {
// Handle unauthorized access
console.error('Unauthorized access');
}
if (response.status === 500) {
// Handle server errors
console.error('Server error occurred');
}
});
// ============================================================================
// EMAIL VALIDATION API
// ============================================================================
/**
* Validate Email
*
* Purpose: Check if email is available for registration
*
* @param email - Email address to validate
* @returns Promise with validation result
*/
export const validatemail = async (email: string): Promise<EmailValidationApiResponse> => {
try {
const response = await api.post<EmailValidationApiResponse>('/auth/validate-email', {
email,
});
if (response.ok && response.data) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to validate email');
}
} catch (error) {
console.error('Email validation error:', error);
throw error;
}
};
// ============================================================================
// USERNAME VALIDATION API
// ============================================================================
/**
* Validate Username
*
* Purpose: Check if username is available for registration
*
* @param username - Username to validate
* @returns Promise with validation result
*/
export const validateusername = async (username: string): Promise<UsernameValidationApiResponse> => {
try {
const response = await api.post<UsernameValidationApiResponse>('/auth/validate-username', {
username,
});
if (response.ok && response.data) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to validate username');
}
} catch (error) {
console.error('Username validation error:', error);
throw error;
}
};
// ============================================================================
// HOSPITAL API
// ============================================================================
/**
* Get Hospitals List
*
* Purpose: Fetch list of available hospitals
*
* @param params - Query parameters for filtering
* @returns Promise with hospital list
*/
export const gethospitals = async (params?: {
page?: number;
limit?: number;
search?: string;
type?: string;
city?: string;
state?: string;
}): Promise<HospitalListApiResponse> => {
try {
const response = await api.get<HospitalListApiResponse>('/hospitals', params);
if (response.ok && response.data) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to fetch hospitals');
}
} catch (error) {
console.error('Get hospitals error:', error);
throw error;
}
};
/**
* Get Hospital by ID
*
* Purpose: Fetch specific hospital details
*
* @param hospitalId - Hospital ID
* @returns Promise with hospital details
*/
export const getHospitalById = async (hospitalId: string) => {
try {
const response = await api.get(`/hospitals/${hospitalId}`);
if (response.ok && response.data) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to fetch hospital details');
}
} catch (error) {
console.error('Get hospital by ID error:', error);
throw error;
}
};
// ============================================================================
// SIGNUP API
// ============================================================================
/**
* Complete Signup
*
* Purpose: Submit complete signup data
*
* @param formData - FormData with signup information
* @returns Promise with signup result
*/
export const signup = async (formData: FormData): Promise<SignUpApiResponse> => {
try {
const response = await api.post<SignUpApiResponse>('/auth/signup', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
console.log('actual response ',response)
if (response.ok && response.data) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to complete signup');
}
} catch (error) {
console.error('Complete signup error:', error);
throw error;
}
};
// ============================================================================
// MOCK API FUNCTIONS (FOR DEVELOPMENT)
// ============================================================================
/**
* Mock Validate Email
*
* Purpose: Mock email validation for development
*
* @param email - Email address to validate
* @returns Promise with mock validation result
*/
export const mockValidatemail = async (email: string): Promise<EmailValidationApiResponse> => {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 1000));
// Mock validation logic
const isAvailable = email !== 'existing@hospital.com';
return {
success: true,
isAvailable,
message: isAvailable ? 'Email is available' : 'Email is already registered',
suggestions: isAvailable ? undefined : ['Try a different email address'],
};
};
/**
* Mock Validate Username
*
* Purpose: Mock username validation for development
*
* @param username - Username to validate
* @returns Promise with mock validation result
*/
export const mockValidateusername = async (username: string): Promise<UsernameValidationApiResponse> => {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 800));
// Mock validation logic
const isAvailable = username !== 'existinguser';
return {
success: true,
isAvailable,
message: isAvailable ? 'Username is available' : 'Username is already taken',
suggestions: isAvailable ? undefined : ['Try adding numbers or special characters'],
};
};
/**
* Mock Get Hospitals
*
* Purpose: Mock hospital list for development
*
* @returns Promise with mock hospital list
*/
export const mockGethospitals = async (): Promise<HospitalListApiResponse> => {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 1500));
// Mock hospital data
const mockHospitals = [
{
id: '1',
name: 'General Hospital',
address: '123 Main Street',
city: 'New York',
state: 'NY',
country: 'USA',
phoneNumber: '+1-555-0123',
email: 'info@generalhospital.com',
website: 'https://generalhospital.com',
type: 'GENERAL' as const,
specialties: ['Emergency Medicine', 'Cardiology', 'Neurology'],
isActive: true,
},
{
id: '2',
name: 'University Medical Center',
address: '456 University Ave',
city: 'Boston',
state: 'MA',
country: 'USA',
phoneNumber: '+1-555-0456',
email: 'info@umc.edu',
website: 'https://umc.edu',
type: 'UNIVERSITY' as const,
specialties: ['Emergency Medicine', 'Trauma', 'Research'],
isActive: true,
},
{
id: '3',
name: 'Specialty Medical Center',
address: '789 Specialty Blvd',
city: 'Los Angeles',
state: 'CA',
country: 'USA',
phoneNumber: '+1-555-0789',
email: 'info@specialtycenter.com',
website: 'https://specialtycenter.com',
type: 'SPECIALTY' as const,
specialties: ['Cardiology', 'Neurology', 'Oncology'],
isActive: true,
},
];
return {
success: true,
data: mockHospitals,
total: mockHospitals.length,
page: 1,
limit: 10,
};
};
/**
* Mock Complete Signup
*
* Purpose: Mock signup completion for development
*
* @param signUpData - Complete signup data
* @returns Promise with mock signup result
*/
export const mockSignup = async (signUpData: SignUpData): Promise<SignUpApiResponse> => {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 2000));
// Mock successful signup
return {
success: true,
message: 'Account created successfully',
data: {
userId: 'user_' + Date.now(),
email: signUpData.email,
token: 'mock_token_' + Date.now(),
},
};
};
// ============================================================================
// EXPORTS
// ============================================================================
export const authAPI = {
// Real API functions
validatemail,
validateusername,
gethospitals,
getHospitalById,
signup,
// Mock API functions (for development)
mockValidatemail,
mockValidateusername,
mockGethospitals,
mockSignup,
};
/*
* End of File: signupAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -110,38 +110,9 @@ export {
selectCurrentError,
} from './redux/predictionsSlice';
export {
fetchAlerts,
acknowledgeAlert,
markAlertAsRead,
clearError as clearAlertsError,
setFilter as setAlertsFilter,
setSort as setAlertsSort,
addAlert,
removeAlert,
updateAlert,
clearAllAlerts,
markAllAsRead,
} from './redux/alertsSlice';
export {
setLoading,
showModal,
hideModal,
showOverlay,
hideOverlay,
setCurrentScreen,
clearNavigationStack,
toggleDarkMode,
setFontSize,
toggleHighContrast,
setRefreshing,
setScrolling,
updateLastInteraction,
showError,
clearError as clearUIError,
resetUIState,
} from './redux/uiSlice';
/*
* End of File: index.ts

View File

@ -7,6 +7,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { dashboardAPI } from '../services/dashboardAPI';
import { logoutUser } from '../../Auth/redux/authActions';
// ============================================================================
// TYPES
@ -123,10 +124,15 @@ export interface AIDashboardState {
*/
export const fetchAIDashboardStatistics = createAsyncThunk(
'aiDashboard/fetchStatistics',
async (token: string, { rejectWithValue }) => {
async (token: string, { rejectWithValue, dispatch }) => {
try {
const response :any = await dashboardAPI.getDashboardStatistics(token);
console.log('statistics response',response);
// Check for 401 Unauthorized status and logout user
if (response.status === 401) {
dispatch(logoutUser());
return rejectWithValue('Session expired. Please login again.');
}
if (response.ok && response.data ) {
return response.data as AIDashboardData;
@ -149,10 +155,16 @@ export const fetchAIDashboardStatistics = createAsyncThunk(
*/
export const refreshAIDashboardStatistics = createAsyncThunk(
'aiDashboard/refreshStatistics',
async (token: string, { rejectWithValue }) => {
async (token: string, { rejectWithValue, dispatch }) => {
try {
const response = await dashboardAPI.getDashboardStatistics(token);
// Check for 401 Unauthorized status and logout user
if (response.status === 401) {
dispatch(logoutUser());
return rejectWithValue('Session expired. Please login again.');
}
if (response.ok && response.data) {
return response.data as AIDashboardData;
} else {
@ -164,30 +176,7 @@ export const refreshAIDashboardStatistics = createAsyncThunk(
}
);
/**
* Fetch Time-based Analysis Async Thunk
*
* Purpose: Fetch time-based analysis data for specific time range
*
* @param params - Parameters including token and time range
* @returns Promise with time-based analysis data or error
*/
export const fetchTimeBasedAnalysis = createAsyncThunk(
'aiDashboard/fetchTimeAnalysis',
async (params: { token: string; timeRange: 'today' | 'week' | 'month' | 'year' }, { rejectWithValue }) => {
try {
const response = await dashboardAPI.getTimeBasedAnalysis(params.token, params.timeRange);
if (response.ok && response.data) {
return response.data as AIDashboardData;
} else {
return rejectWithValue(response.problem || 'Failed to fetch time-based analysis');
}
} catch (error) {
return rejectWithValue('Network error occurred while fetching time-based analysis');
}
}
);
// ============================================================================
// INITIAL STATE
@ -328,22 +317,7 @@ const aiDashboardSlice = createSlice({
state.error = action.payload as string;
});
// Fetch Time-based Analysis
builder
.addCase(fetchTimeBasedAnalysis.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchTimeBasedAnalysis.fulfilled, (state, action) => {
state.isLoading = false;
state.dashboardData = action.payload;
state.lastUpdated = new Date().toLocaleDateString();
state.error = null;
})
.addCase(fetchTimeBasedAnalysis.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
},
});

View File

@ -1,341 +0,0 @@
/*
* File: alertsSlice.ts
* Description: Alerts state management slice
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { AlertType, AlertsState } from '../../../shared/types';
// ============================================================================
// ASYNC THUNKS
// ============================================================================
/**
* Fetch Alerts Async Thunk
*
* Purpose: Fetch alerts from API
*
* @returns Promise with alerts data or error
*/
export const fetchAlerts = createAsyncThunk(
'alerts/fetchAlerts',
async (_, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise(resolve => setTimeout(resolve, 1000));
// Mock alerts data
const mockAlerts: AlertType[] = [
{
id: '1',
type: 'CRITICAL_FINDING',
priority: 'CRITICAL',
title: 'Critical Finding Detected',
message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.',
patientId: '1',
patientName: 'John Doe',
bedNumber: 'A1',
timestamp: new Date(),
isRead: false,
isAcknowledged: false,
actionRequired: true,
},
{
id: '2',
type: 'VITAL_SIGNS_ALERT',
priority: 'HIGH',
title: 'Vital Signs Alert',
message: 'Patient vitals showing concerning trends. Blood pressure elevated.',
patientId: '2',
patientName: 'Jane Smith',
bedNumber: 'B2',
timestamp: new Date(Date.now() - 300000), // 5 minutes ago
isRead: true,
isAcknowledged: true,
actionRequired: false,
},
];
return mockAlerts;
} catch (error) {
return rejectWithValue('Failed to fetch alerts.');
}
}
);
/**
* Acknowledge Alert Async Thunk
*
* Purpose: Acknowledge an alert
*
* @param alertId - ID of the alert to acknowledge
* @returns Promise with success or error
*/
export const acknowledgeAlert = createAsyncThunk(
'alerts/acknowledgeAlert',
async (alertId: string, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise(resolve => setTimeout(resolve, 500));
return alertId;
} catch (error) {
return rejectWithValue('Failed to acknowledge alert.');
}
}
);
/**
* Mark Alert as Read Async Thunk
*
* Purpose: Mark an alert as read
*
* @param alertId - ID of the alert to mark as read
* @returns Promise with success or error
*/
export const markAlertAsRead = createAsyncThunk(
'alerts/markAlertAsRead',
async (alertId: string, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise(resolve => setTimeout(resolve, 300));
return alertId;
} catch (error) {
return rejectWithValue('Failed to mark alert as read.');
}
}
);
// ============================================================================
// INITIAL STATE
// ============================================================================
/**
* Initial Alerts State
*
* Purpose: Define the initial state for alerts
*
* Features:
* - Alerts list and management
* - Loading states for async operations
* - Error handling and messages
* - Real-time updates tracking
*/
const initialState: AlertsState = {
// Alerts data
alerts: [],
// Loading states
isLoading: false,
isRefreshing: false,
// Error handling
error: null,
// Real-time updates
lastUpdated: null,
unreadCount: 0,
criticalCount: 0,
// Filters and preferences
selectedFilter: 'all',
sortBy: 'timestamp',
sortOrder: 'desc',
};
// ============================================================================
// ALERTS SLICE
// ============================================================================
/**
* Alerts Slice
*
* Purpose: Redux slice for alerts state management
*
* Features:
* - Alerts data management
* - Real-time updates
* - Filtering and sorting
* - Error handling
* - Loading states
*/
const alertsSlice = createSlice({
name: 'alerts',
initialState,
reducers: {
/**
* Clear Error Action
*
* Purpose: Clear alerts errors
*/
clearError: (state) => {
state.error = null;
},
/**
* Set Filter Action
*
* Purpose: Set alerts filter
*/
setFilter: (state, action: PayloadAction<'all' | 'critical' | 'unread' | 'acknowledged'>) => {
state.selectedFilter = action.payload;
},
/**
* Set Sort Action
*
* Purpose: Set alerts sort options
*/
setSort: (state, action: PayloadAction<{ by: string; order: 'asc' | 'desc' }>) => {
state.sortBy = action.payload.by;
state.sortOrder = action.payload.order;
},
/**
* Add Alert Action
*
* Purpose: Add a new alert
*/
addAlert: (state, action: PayloadAction<AlertType>) => {
state.alerts.unshift(action.payload);
state.unreadCount += 1;
if (action.payload.priority === 'CRITICAL') {
state.criticalCount += 1;
}
state.lastUpdated = new Date();
},
/**
* Remove Alert Action
*
* Purpose: Remove an alert
*/
removeAlert: (state, action: PayloadAction<string>) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload);
if (alertIndex !== -1) {
const alert = state.alerts[alertIndex];
if (!alert.isRead) {
state.unreadCount -= 1;
}
if (alert.priority === 'CRITICAL') {
state.criticalCount -= 1;
}
state.alerts.splice(alertIndex, 1);
}
},
/**
* Update Alert Action
*
* Purpose: Update an existing alert
*/
updateAlert: (state, action: PayloadAction<{ id: string; updates: Partial<AlertType> }>) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload.id);
if (alertIndex !== -1) {
const oldAlert = state.alerts[alertIndex];
state.alerts[alertIndex] = { ...oldAlert, ...action.payload.updates };
// Update counters
if (action.payload.updates.isRead !== undefined) {
if (action.payload.updates.isRead && !oldAlert.isRead) {
state.unreadCount -= 1;
} else if (!action.payload.updates.isRead && oldAlert.isRead) {
state.unreadCount += 1;
}
}
}
},
/**
* Clear All Alerts Action
*
* Purpose: Clear all alerts
*/
clearAllAlerts: (state) => {
state.alerts = [];
state.unreadCount = 0;
state.criticalCount = 0;
},
/**
* Mark All as Read Action
*
* Purpose: Mark all alerts as read
*/
markAllAsRead: (state) => {
state.alerts.forEach(alert => {
alert.isRead = true;
});
state.unreadCount = 0;
},
},
extraReducers: (builder) => {
// Fetch Alerts
builder
.addCase(fetchAlerts.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchAlerts.fulfilled, (state, action) => {
state.isLoading = false;
state.alerts = action.payload;
state.unreadCount = action.payload.filter(alert => !alert.isRead).length;
state.criticalCount = action.payload.filter(alert => alert.priority === 'CRITICAL').length;
state.lastUpdated = new Date();
state.error = null;
})
.addCase(fetchAlerts.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
// Acknowledge Alert
builder
.addCase(acknowledgeAlert.fulfilled, (state, action) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload);
if (alertIndex !== -1) {
state.alerts[alertIndex].isAcknowledged = true;
}
})
.addCase(acknowledgeAlert.rejected, (state, action) => {
state.error = action.payload as string;
});
// Mark Alert as Read
builder
.addCase(markAlertAsRead.fulfilled, (state, action) => {
const alertIndex = state.alerts.findIndex(alert => alert.id === action.payload);
if (alertIndex !== -1 && !state.alerts[alertIndex].isRead) {
state.alerts[alertIndex].isRead = true;
state.unreadCount -= 1;
}
})
.addCase(markAlertAsRead.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
// ============================================================================
// EXPORTS
// ============================================================================
export const {
clearError,
setFilter,
setSort,
addAlert,
removeAlert,
updateAlert,
clearAllAlerts,
markAllAsRead,
} = alertsSlice.actions;
export default alertsSlice.reducer;
/*
* End of File: alertsSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -13,13 +13,9 @@ export * from './dashboardSlice';
// export { default as aiDashboardReducer } from './aiDashboardSlice';
// export * from './aiDashboardSlice';
// // UI Slice
// export { default as uiReducer } from './uiSlice';
// export * from './uiSlice';
// // Alerts Slice
// export { default as alertsReducer } from './alertsSlice';
// export * from './alertsSlice';
// // AI Dashboard Selectors
// export * from './aiDashboardSelectors';

View File

@ -8,6 +8,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { predictionsAPI } from '../services/predictionsAPI';
import type { PredictionsResponse, PredictionData, PredictionTabType } from '../types/predictions';
import { logoutUser } from '../../Auth/redux/authActions';
// ============================================================================
// ASYNC THUNKS
@ -20,10 +21,16 @@ import type { PredictionsResponse, PredictionData, PredictionTabType } from '../
*/
export const fetchAllPredictions = createAsyncThunk(
'predictions/fetchAll',
async (token: string, { rejectWithValue }) => {
async (token: string, { rejectWithValue, dispatch }) => {
try {
const response :any = await predictionsAPI.fetchAllPredictions(token);
console.log('dashboard predction data response', response);
// Check for 401 Unauthorized status and logout user
if (response.status === 401) {
dispatch(logoutUser());
return rejectWithValue('Session expired. Please login again.');
}
if (response.ok && response.data && response.data.data) {
return response.data.data as PredictionData[];
} else {
@ -155,17 +162,7 @@ const predictionsSlice = createSlice({
state.allPredictions = action.payload;
state.error = null;
// Debug logging to see what's happening with feedback filtering
console.log('🔍 Predictions filtering debug:');
console.log('Total predictions:', action.payload.length);
console.log('Predictions with feedback field:', action.payload.filter(p => p.has_provided_feedback).length);
console.log('Predictions with feedbacks array:', action.payload.filter(p => p.feedbacks && p.feedbacks.length > 0).length);
console.log('Sample prediction feedback data:', action.payload.slice(0, 2).map(p => ({
id: p.id,
has_provided_feedback: p.has_provided_feedback,
feedbacks_count: p.feedbacks?.length || 0,
user_feedback_count: p.user_feedback_count
})));
// Automatically filter predictions after fetching
// Primary filter: use has_provided_feedback field
@ -177,9 +174,6 @@ const predictionsSlice = createSlice({
prediction => !prediction.has_provided_feedback && (!prediction.feedbacks || prediction.feedbacks.length === 0)
);
console.log('Filtered results:');
console.log('With feedback tab:', state.predictionsWithFeedback.length);
console.log('Without feedback tab:', state.predictionsWithoutFeedback.length);
});
// Rejected

View File

@ -1,330 +0,0 @@
/*
* File: uiSlice.ts
* Description: UI state management slice
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// ============================================================================
// UI STATE INTERFACE
// ============================================================================
/**
* UI State Interface
*
* Purpose: Define the structure of UI state
*
* Features:
* - Loading states for different UI components
* - Modal and overlay management
* - Navigation state
* - Theme and appearance settings
* - User interaction states
*/
interface UIState {
// Loading states
isLoading: boolean;
loadingMessage: string | null;
// Modal states
isModalOpen: boolean;
modalType: string | null;
modalData: any;
// Overlay states
isOverlayVisible: boolean;
overlayType: string | null;
// Navigation states
currentScreen: string | null;
navigationStack: string[];
// Theme and appearance
isDarkMode: boolean;
fontSize: 'small' | 'medium' | 'large';
highContrast: boolean;
// User interaction states
isRefreshing: boolean;
isScrolling: boolean;
lastInteraction: Date | null;
// Error states
hasError: boolean;
errorMessage: string | null;
errorType: 'warning' | 'error' | 'info' | null;
}
// ============================================================================
// INITIAL STATE
// ============================================================================
/**
* Initial UI State
*
* Purpose: Define the initial state for UI
*
* Features:
* - Default loading states
* - Default modal and overlay states
* - Default theme settings
* - Default interaction states
*/
const initialState: UIState = {
// Loading states
isLoading: false,
loadingMessage: null,
// Modal states
isModalOpen: false,
modalType: null,
modalData: null,
// Overlay states
isOverlayVisible: false,
overlayType: null,
// Navigation states
currentScreen: null,
navigationStack: [],
// Theme and appearance
isDarkMode: false,
fontSize: 'medium',
highContrast: false,
// User interaction states
isRefreshing: false,
isScrolling: false,
lastInteraction: null,
// Error states
hasError: false,
errorMessage: null,
errorType: null,
};
// ============================================================================
// UI SLICE
// ============================================================================
/**
* UI Slice
*
* Purpose: Redux slice for UI state management
*
* Features:
* - Loading state management
* - Modal and overlay control
* - Navigation state tracking
* - Theme and appearance settings
* - User interaction tracking
* - Error state management
*/
const uiSlice = createSlice({
name: 'ui',
initialState,
reducers: {
/**
* Set Loading Action
*
* Purpose: Set loading state with optional message
*/
setLoading: (state, action: PayloadAction<{ isLoading: boolean; message?: string }>) => {
state.isLoading = action.payload.isLoading;
state.loadingMessage = action.payload.message || null;
},
/**
* Show Modal Action
*
* Purpose: Show a modal with specific type and data
*/
showModal: (state, action: PayloadAction<{ type: string; data?: any }>) => {
state.isModalOpen = true;
state.modalType = action.payload.type;
state.modalData = action.payload.data || null;
},
/**
* Hide Modal Action
*
* Purpose: Hide the current modal
*/
hideModal: (state) => {
state.isModalOpen = false;
state.modalType = null;
state.modalData = null;
},
/**
* Show Overlay Action
*
* Purpose: Show an overlay with specific type
*/
showOverlay: (state, action: PayloadAction<{ type: string }>) => {
state.isOverlayVisible = true;
state.overlayType = action.payload.type;
},
/**
* Hide Overlay Action
*
* Purpose: Hide the current overlay
*/
hideOverlay: (state) => {
state.isOverlayVisible = false;
state.overlayType = null;
},
/**
* Set Current Screen Action
*
* Purpose: Set the current screen name
*/
setCurrentScreen: (state, action: PayloadAction<string>) => {
state.currentScreen = action.payload;
if (!state.navigationStack.includes(action.payload)) {
state.navigationStack.push(action.payload);
}
},
/**
* Clear Navigation Stack Action
*
* Purpose: Clear the navigation stack
*/
clearNavigationStack: (state) => {
state.navigationStack = [];
},
/**
* Toggle Dark Mode Action
*
* Purpose: Toggle dark mode on/off
*/
toggleDarkMode: (state) => {
state.isDarkMode = !state.isDarkMode;
},
/**
* Set Font Size Action
*
* Purpose: Set the font size preference
*/
setFontSize: (state, action: PayloadAction<'small' | 'medium' | 'large'>) => {
state.fontSize = action.payload;
},
/**
* Toggle High Contrast Action
*
* Purpose: Toggle high contrast mode
*/
toggleHighContrast: (state) => {
state.highContrast = !state.highContrast;
},
/**
* Set Refreshing Action
*
* Purpose: Set refreshing state
*/
setRefreshing: (state, action: PayloadAction<boolean>) => {
state.isRefreshing = action.payload;
},
/**
* Set Scrolling Action
*
* Purpose: Set scrolling state
*/
setScrolling: (state, action: PayloadAction<boolean>) => {
state.isScrolling = action.payload;
},
/**
* Update Last Interaction Action
*
* Purpose: Update the last interaction timestamp
*/
updateLastInteraction: (state) => {
state.lastInteraction = new Date();
},
/**
* Show Error Action
*
* Purpose: Show an error message
*/
showError: (state, action: PayloadAction<{ message: string; type?: 'warning' | 'error' | 'info' }>) => {
state.hasError = true;
state.errorMessage = action.payload.message;
state.errorType = action.payload.type || 'error';
},
/**
* Clear Error Action
*
* Purpose: Clear the current error
*/
clearError: (state) => {
state.hasError = false;
state.errorMessage = null;
state.errorType = null;
},
/**
* Reset UI State Action
*
* Purpose: Reset UI state to initial values
*/
resetUIState: (state) => {
state.isLoading = false;
state.loadingMessage = null;
state.isModalOpen = false;
state.modalType = null;
state.modalData = null;
state.isOverlayVisible = false;
state.overlayType = null;
state.isRefreshing = false;
state.isScrolling = false;
state.hasError = false;
state.errorMessage = null;
state.errorType = null;
},
},
});
// ============================================================================
// EXPORTS
// ============================================================================
export const {
setLoading,
showModal,
hideModal,
showOverlay,
hideOverlay,
setCurrentScreen,
clearNavigationStack,
toggleDarkMode,
setFontSize,
toggleHighContrast,
setRefreshing,
setScrolling,
updateLastInteraction,
showError,
clearError,
resetUIState,
} = uiSlice.actions;
export default uiSlice.reducer;
/*
* End of File: uiSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -686,7 +686,6 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
}
);
console.log('Navigation successful to FeedbackDetailScreen');
} catch (error) {
console.error('Navigation error:', error);
// Fallback: show alert or handle error gracefully

View File

@ -1,791 +0,0 @@
/*
* File: ERDashboardScreen.tsx
* 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.
*/
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
RefreshControl,
FlatList,
Alert,
} from 'react-native';
import { theme } from '../../../theme/theme';
import { ERDashboard, Patient, Alert as AlertType } from '../../../shared/types';
import { PatientCard } from '../components/PatientCard';
import { CriticalAlerts } from '../components/CriticalAlerts';
import { DashboardHeader } from '../components/DashboardHeader';
import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview';
/**
* ERDashboardScreenProps Interface
*
* Purpose: Defines the props required by the ERDashboardScreen component
*
* Props:
* - navigation: React Navigation object for screen navigation
*/
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: Brain AI Analysis Dashboard for Emergency Department physicians
*
* Dashboard Features:
* 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. AI prediction confidence scores and analysis
*
* Brain Condition Filtering:
* - All: Shows all brain scan cases
* - Critical: Shows only cases with critical brain conditions
* - Pending: Shows cases awaiting AI analysis
*
* 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<ERDashboardScreenProps> = ({
navigation,
}) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
// Refresh state for pull-to-refresh functionality
const [refreshing, setRefreshing] = useState(false);
// 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<ERDashboard | null>(null);
const [patients, setPatients] = useState<Patient[]>([]);
const [alerts, setAlerts] = useState<AlertType[]>([]);
const [isLoading, setIsLoading] = useState(true);
// ============================================================================
// MOCK DATA GENERATION
// ============================================================================
/**
* generateMockDashboard Function
*
* Purpose: Generate mock dashboard data focused on brain AI analysis
*
* Returns: ERDashboard object with brain imaging statistics
*/
const generateMockDashboard = (): ERDashboard => ({
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, // 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 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
});
/**
* generateMockPatients Function
*
* Purpose: Generate mock patient data focused on brain conditions and AI predictions
*
* Returns: Array of Patient objects with brain-related medical data
*/
const generateMockPatients = (): Patient[] => [
{
id: '1',
mrn: 'MRN001',
firstName: 'John',
lastName: 'Doe',
dateOfBirth: new Date('1985-03-15'),
gender: 'MALE' as const,
age: 38,
bedNumber: 'A1',
roomNumber: '101',
admissionDate: new Date('2024-01-15'),
status: 'ACTIVE' as const,
priority: 'CRITICAL' as const,
department: 'Emergency',
attendingPhysician: 'Dr. Smith',
allergies: [
{
id: '1',
name: 'Contrast Dye',
severity: 'SEVERE' as const,
reaction: 'Anaphylaxis'
},
],
medications: [
{
id: '1',
name: 'Mannitol',
dosage: '100ml',
frequency: 'Every 6 hours',
route: 'IV',
startDate: new Date(),
status: 'ACTIVE' as const,
prescribedBy: 'Dr. Smith',
},
],
vitalSigns: {
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: 'AI Predicted: Intraparenchymal hemorrhage (IPH) - 94% confidence',
lastUpdated: new Date(),
},
{
id: '2',
mrn: 'MRN002',
firstName: 'Jane',
lastName: 'Smith',
dateOfBirth: new Date('1990-07-22'),
gender: 'FEMALE' as const,
age: 33,
bedNumber: 'B2',
roomNumber: '102',
admissionDate: new Date('2024-01-15'),
status: 'ACTIVE' as const,
priority: 'CRITICAL' as const,
department: 'Trauma',
attendingPhysician: 'Dr. Johnson',
allergies: [],
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: 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: 'AI Predicted: Subdural hematoma (SDH) with mass effect - 89% confidence',
lastUpdated: new Date(),
},
{
id: '3',
mrn: 'MRN003',
firstName: 'Michael',
lastName: 'Brown',
dateOfBirth: new Date('1978-11-08'),
gender: 'MALE' as const,
age: 45,
bedNumber: 'C3',
roomNumber: '103',
admissionDate: new Date('2024-01-15'),
status: 'PENDING' as const,
priority: 'HIGH' as const,
department: 'Stroke',
attendingPhysician: 'Dr. Williams',
allergies: [
{
id: '2',
name: 'Aspirin',
severity: 'MODERATE' as const,
reaction: 'Stomach upset'
},
],
medications: [
{
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: 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: 'AI Predicted: Stroke (ischemic) - 92% confidence',
lastUpdated: new Date(),
},
{
id: '4',
mrn: 'MRN004',
firstName: 'Sarah',
lastName: 'Wilson',
dateOfBirth: new Date('1995-04-12'),
gender: 'FEMALE' as const,
age: 28,
bedNumber: 'D4',
roomNumber: '104',
admissionDate: new Date('2024-01-15'),
status: 'ACTIVE' as const,
priority: 'CRITICAL' as const,
department: 'Neurosurgery',
attendingPhysician: 'Dr. Davis',
allergies: [],
medications: [
{
id: '4',
name: 'Phenytoin',
dosage: '100mg',
frequency: 'Every 8 hours',
route: 'IV',
startDate: new Date(),
status: 'ACTIVE' as const,
prescribedBy: 'Dr. Davis',
},
],
vitalSigns: {
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: 'AI Predicted: Epidural hematoma (EDH) with midline shift - 96% confidence',
lastUpdated: new Date(),
},
{
id: '5',
mrn: 'MRN005',
firstName: 'David',
lastName: 'Miller',
dateOfBirth: new Date('1982-09-30'),
gender: 'MALE' as const,
age: 41,
bedNumber: 'E5',
roomNumber: '105',
admissionDate: new Date('2024-01-15'),
status: 'ACTIVE' as const,
priority: 'HIGH' as const,
department: 'Neurology',
attendingPhysician: 'Dr. Brown',
allergies: [
{
id: '3',
name: 'Latex',
severity: 'SEVERE' as const,
reaction: 'Contact dermatitis'
},
],
medications: [],
vitalSigns: {
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: '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(),
},
];
/**
* generateMockAlerts Function
*
* Purpose: Generate mock alert data focused on brain AI predictions
*
* Returns: Array of Alert objects with brain-related alerts
*/
const generateMockAlerts = (): AlertType[] => [
{
id: '1',
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: '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',
timestamp: new Date(Date.now() - 300000), // 5 minutes ago
isRead: false,
isAcknowledged: false,
actionRequired: true,
},
{
id: '3',
type: 'MEDICATION_ALERT' as const,
priority: 'MEDIUM' as const,
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,
},
];
// ============================================================================
// EFFECTS
// ============================================================================
/**
* useEffect for initial data loading
*
* Purpose: Load initial mock data when component mounts
*/
useEffect(() => {
const loadInitialData = async () => {
setIsLoading(true);
// Simulate API call delay
setTimeout(() => {}, 1000);
// Generate and set mock data
setDashboard(generateMockDashboard());
setPatients(generateMockPatients());
setAlerts(generateMockAlerts());
setIsLoading(false);
};
loadInitialData();
}, []);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* handleRefresh Function
*
* Purpose: Handle pull-to-refresh functionality to update dashboard data
*/
const handleRefresh = async () => {
setRefreshing(true);
// Simulate API call with 1-second delay
setTimeout(() => {}, 1000);
// Update data with fresh mock data
setDashboard(generateMockDashboard());
setPatients(generateMockPatients());
setAlerts(generateMockAlerts());
setRefreshing(false);
};
/**
* handlePatientPress Function
*
* Purpose: Handle patient card press navigation
*
* @param patient - Patient object that was pressed
*/
const handlePatientPress = (patient: Patient) => {
console.log('Patient pressed:', patient.firstName, patient.lastName);
Alert.alert('Brain Analysis Details', `Navigate to ${patient.firstName} ${patient.lastName}'s brain scan analysis`);
};
/**
* handleAlertPress Function
*
* Purpose: Handle alert press interaction
*
* @param alert - Alert object that was pressed
*/
const handleAlertPress = (alert: AlertType) => {
console.log('Alert pressed:', alert.title);
Alert.alert('Brain Alert Details', alert.message);
};
// ============================================================================
// DATA PROCESSING
// ============================================================================
/**
* filteredPatients - Computed property
*
* Purpose: Filter patients based on selected filter criteria
*
* Filter Options:
* - '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';
case 'pending':
return patient.status === 'PENDING';
default:
return true;
}
});
/**
* criticalAlerts - Computed property
*
* Purpose: Extract critical alerts from all alerts for immediate display
*/
const criticalAlerts = alerts.filter(alert => alert.priority === 'CRITICAL');
/**
* pendingScans - Computed property
*
* Purpose: Identify patients with pending AI analysis
*/
const pendingScans = patients.filter(patient => patient.status === 'PENDING');
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* renderPatientCard Function
*
* Purpose: Render individual patient card component
*
* @param item - Patient data object
* @returns PatientCard component with patient data and press handler
*/
const renderPatientCard = ({ item }: { item: Patient }) => (
<PatientCard
patient={item}
onPress={() => handlePatientPress(item)}
/>
);
/**
* renderHeader Function
*
* Purpose: Render the dashboard header section with all dashboard components
*/
const renderHeader = () => (
<View style={styles.header}>
{/* Dashboard header with shift information and key metrics */}
{dashboard && <DashboardHeader dashboard={dashboard} />}
{/* Critical alerts section - only show if there are critical alerts */}
{criticalAlerts.length > 0 && (
<CriticalAlerts
alerts={criticalAlerts}
onAlertPress={handleAlertPress}
/>
)}
{/* Department statistics showing brain case distribution */}
{dashboard && <BrainPredictionsOverview dashboard={dashboard} />}
{/* Brain case filter section with filter buttons */}
<View style={styles.filterContainer}>
<Text style={styles.sectionTitle}>Brain Scan Cases</Text>
<View style={styles.filterButtons}>
{/* All cases filter button */}
<TouchableOpacity
style={[
styles.filterButton,
selectedFilter === 'all' && styles.filterButtonActive
]}
onPress={() => setSelectedFilter('all')}
>
<Text style={[
styles.filterButtonText,
selectedFilter === 'all' && styles.filterButtonTextActive
]}>
All ({patients.length})
</Text>
</TouchableOpacity>
{/* Critical cases filter button */}
<TouchableOpacity
style={[
styles.filterButton,
selectedFilter === 'critical' && styles.filterButtonActive
]}
onPress={() => setSelectedFilter('critical')}
>
<Text style={[
styles.filterButtonText,
selectedFilter === 'critical' && styles.filterButtonTextActive
]}>
Critical ({patients.filter(p => p.priority === 'CRITICAL').length})
</Text>
</TouchableOpacity>
{/* Pending AI analysis filter button */}
<TouchableOpacity
style={[
styles.filterButton,
selectedFilter === 'pending' && styles.filterButtonActive
]}
onPress={() => setSelectedFilter('pending')}
>
<Text style={[
styles.filterButtonText,
selectedFilter === 'pending' && styles.filterButtonTextActive
]}>
Pending AI ({patients.filter(p => p.status === 'PENDING').length})
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
// ============================================================================
// LOADING STATE
// ============================================================================
/**
* Loading state render
*
* Purpose: Show loading indicator while data is being generated
*/
if (isLoading) {
return (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading Brain AI Dashboard...</Text>
</View>
);
}
// ============================================================================
// MAIN RENDER
// ============================================================================
return (
<View style={styles.container}>
{/* FlatList for efficient rendering of patient cards */}
<FlatList
data={filteredPatients}
renderItem={renderPatientCard}
keyExtractor={(item) => item.id}
ListHeaderComponent={renderHeader}
contentContainerStyle={styles.listContainer}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
showsVerticalScrollIndicator={false}
/>
</View>
);
};
// ============================================================================
// STYLES SECTION
// ============================================================================
const styles = StyleSheet.create({
// Main container for the dashboard screen
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
// Loading container for initial data loading
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: theme.colors.background,
},
// Loading text styling
loadingText: {
fontSize: theme.typography.fontSize.bodyLarge,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium,
},
// Container for the FlatList content
listContainer: {
paddingBottom: theme.spacing.lg,
},
// Header section containing dashboard components
header: {
paddingHorizontal: theme.spacing.md,
},
// Container for patient filter section
filterContainer: {
marginTop: theme.spacing.lg,
marginBottom: theme.spacing.md,
},
// Section title styling
sectionTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.md,
},
// Container for filter buttons
filterButtons: {
flexDirection: 'row',
gap: theme.spacing.sm,
},
// Individual filter button styling
filterButton: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.border,
backgroundColor: theme.colors.background,
},
// Active filter button styling
filterButtonActive: {
backgroundColor: theme.colors.primary,
borderColor: theme.colors.primary,
},
// Filter button text styling
filterButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium,
},
// Active filter button text styling
filterButtonTextActive: {
color: theme.colors.background,
},
});
/*
* End of File: ERDashboardScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -36,126 +36,6 @@ export const dashboardAPI = {
return api.get('/api/ai-cases/feedbacks/statistics', {}, buildHeaders({ token }));
},
/**
* Get Real-time Dashboard Metrics
*
* Purpose: Fetch real-time dashboard metrics for live updates
*
* @param token - Authentication token
* @returns Promise with real-time dashboard metrics
*/
getRealTimeMetrics: (token: string) => {
return api.get('/api/ai-cases/feedbacks/statistics/realtime', {}, buildHeaders({ token }));
},
/**
* Get Time-based Analysis Data
*
* Purpose: Fetch time-based analysis data for trend visualization
*
* @param token - Authentication token
* @param timeRange - Time range for analysis (today, week, month, year)
* @returns Promise with time-based analysis data
*/
getTimeBasedAnalysis: (token: string, timeRange: 'today' | 'week' | 'month' | 'year') => {
return api.get(`/api/ai-cases/feedbacks/statistics/time-analysis/${timeRange}`, {}, buildHeaders({ token }));
},
/**
* Get Hospital-specific Statistics
*
* Purpose: Fetch statistics for a specific hospital
*
* @param token - Authentication token
* @param hospitalId - Hospital identifier
* @returns Promise with hospital-specific statistics
*/
getHospitalStatistics: (token: string, hospitalId: string) => {
return api.get(`/api/ai-cases/feedbacks/statistics/hospital/${hospitalId}`, {}, buildHeaders({ token }));
},
/**
* Get Department Performance Metrics
*
* Purpose: Fetch performance metrics for specific departments
*
* @param token - Authentication token
* @param department - Department name
* @returns Promise with department performance data
*/
getDepartmentMetrics: (token: string, department: string) => {
return api.get(`/api/ai-cases/feedbacks/statistics/department/${department}`, {}, buildHeaders({ token }));
},
/**
* Get Confidence Score Distribution
*
* Purpose: Fetch confidence score distribution for AI predictions
*
* @param token - Authentication token
* @param timeRange - Optional time range filter
* @returns Promise with confidence score distribution data
*/
getConfidenceDistribution: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/feedbacks/statistics/confidence-distribution', params, buildHeaders({ token }));
},
/**
* Get Urgency Level Distribution
*
* Purpose: Fetch urgency level distribution for AI cases
*
* @param token - Authentication token
* @param timeRange - Optional time range filter
* @returns Promise with urgency level distribution data
*/
getUrgencyDistribution: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/feedbacks/statistics/urgency-distribution', params, buildHeaders({ token }));
},
/**
* Get Feedback Analysis Data
*
* Purpose: Fetch feedback analysis and coverage metrics
*
* @param token - Authentication token
* @param timeRange - Optional time range filter
* @returns Promise with feedback analysis data
*/
getFeedbackAnalysis: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/feedbacks/statistics/feedback-analysis', params, buildHeaders({ token }));
},
/**
* Get Critical Findings Statistics
*
* Purpose: Fetch statistics for critical findings and cases
*
* @param token - Authentication token
* @param timeRange - Optional time range filter
* @returns Promise with critical findings statistics
*/
getCriticalFindingsStats: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/feedbacks/statistics/critical-findings', params, buildHeaders({ token }));
},
/**
* Get Prediction Breakdown Statistics
*
* Purpose: Fetch breakdown of AI predictions by category
*
* @param token - Authentication token
* @param timeRange - Optional time range filter
* @returns Promise with prediction breakdown data
*/
getPredictionBreakdown: (token: string, timeRange?: 'today' | 'week' | 'month' | 'year') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/feedbacks/statistics/prediction-breakdown', params, buildHeaders({ token }));
}
};
/*

View File

@ -7,6 +7,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { patientAPI } from '../services/patientAPI';
import { logoutUser } from '../../Auth/redux/authActions';
// ============================================================================
// TYPES
@ -90,10 +91,16 @@ export interface PatientCareState {
*/
export const fetchPatients = createAsyncThunk(
'patientCare/fetchPatients',
async (token: string, { rejectWithValue }) => {
async (token: string, { rejectWithValue, dispatch }) => {
try {
const response: any = await patientAPI.getPatients(token);
// Check for 401 Unauthorized status and logout user
if (response.status === 401) {
dispatch(logoutUser());
return rejectWithValue('Session expired. Please login again.');
}
if (response.ok && response.data&& response.data.data) {
// Return the patients data directly from the new API structure
return response.data.data as PatientData[];

View File

@ -32,6 +32,7 @@ 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 { logoutUser } from '../../Auth/redux/authActions';
// Import types and API
import { patientAPI } from '../services/patientAPI';
@ -182,6 +183,13 @@ const PatientDetailsScreen: React.FC<PatientDetailsScreenProps> = ({ navigation,
const response: any = await patientAPI.getPatientDetailsById(patientId, user.access_token);
// Check for 401 Unauthorized status and logout user
if (response.status === 401) {
dispatch(logoutUser());
setError('Session expired. Please login again.');
return;
}
if (response.ok && response.data && response.data.data ) {
setPatientData(response.data.data as PatientData);
} else {
@ -192,7 +200,7 @@ const PatientDetailsScreen: React.FC<PatientDetailsScreenProps> = ({ navigation,
} finally {
setIsLoading(false);
}
}, [patientId, user?.access_token]);
}, [patientId, user?.access_token, dispatch]);
// ============================================================================
// EFFECTS
@ -269,7 +277,6 @@ const PatientDetailsScreen: React.FC<PatientDetailsScreenProps> = ({ navigation,
if (firstPrediction?.preview) {
const dicomUrl = API_CONFIG.BASE_URL +'/api/dicom'+ firstPrediction.file_path;
console.log('dicomUrl', dicomUrl);
setSelectedDicomData({
dicomUrl,
seriesData: series,
@ -1147,7 +1154,6 @@ const PatientDetailsScreen: React.FC<PatientDetailsScreenProps> = ({ navigation,
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 (
<TouchableOpacity
key={series.series_num}

View File

@ -1,3 +0,0 @@

View File

@ -188,7 +188,7 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
id: 'help-support',
title: 'Help & Support',
subtitle: 'Contact support and view documentation',
icon: 'help',
icon: 'phone',
type: 'NAVIGATION',
onPress: () => handleNavigation('HELP'),
},
@ -523,7 +523,6 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
// Update Redux state with new profile photo URL
if (responseData.data?.profile_photo_url) {
console.log('Updating user profile with new photo URL:', responseData.data.profile_photo_url);
dispatch(updateUserProfile({
self_url: responseData.data.profile_photo_url
}));
@ -675,7 +674,6 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
await dispatch(logoutUser());
// Log the logout action
console.log('User logged out successfully');
} catch (error) {
console.error('Logout error:', error);
setModalConfig({

View File

@ -1,207 +0,0 @@
# DICOM Viewer Component
## Overview
The DICOM Viewer component is a React Native component that uses WebView to display DICOM medical imaging files. It integrates with Cornerstone.js and Cornerstone WADO Image Loader for robust DICOM file handling.
## Features
- ✅ WebView-based DICOM rendering
- ✅ Cornerstone.js integration for medical imaging
- ✅ Support for remote DICOM URLs
- ✅ Loading states and error handling
- ✅ Real-time communication with React Native
- ✅ Responsive design for mobile devices
## Components
### 1. DicomViewer
The main DICOM viewer component.
**Props:**
```typescript
interface DicomViewerProps {
dicomUrl: string; // URL to the DICOM file
onError?: (error: string) => void; // Error callback
onLoad?: () => void; // Load success callback
}
```
**Usage:**
```typescript
import { DicomViewer } from '../shared/components';
<DicomViewer
dicomUrl="https://example.com/sample.dcm"
onError={(error) => console.error('DICOM Error:', error)}
onLoad={() => console.log('DICOM loaded successfully')}
/>
```
### 2. DicomViewerTest
A test component for testing different DICOM URLs and debugging issues.
**Usage:**
```typescript
import { DicomViewerTest } from '../shared/components';
<DicomViewerTest />
```
## How It Works
### 1. WebView Setup
- Loads a local HTML file (`dicom-viewer.html`)
- Enables JavaScript and DOM storage
- Allows file access and universal access from file URLs
### 2. Library Loading
- Dynamically loads Cornerstone.js from CDN
- Loads Cornerstone WADO Image Loader
- Initializes the viewer when libraries are ready
### 3. DICOM Processing
- Receives DICOM URL from React Native via postMessage
- Uses Cornerstone to load and display the DICOM image
- Handles errors and success states
### 4. Communication
- Sends status messages back to React Native
- Reports loading, success, and error states
- Enables debugging and user feedback
## Troubleshooting
### Black Screen Issues
#### 1. Check Console Logs
Open the React Native debugger and check for:
- WebView loading errors
- JavaScript execution errors
- Network request failures
#### 2. Verify DICOM URL
- Ensure the URL is accessible from the device
- Check if the URL returns a valid DICOM file
- Verify CORS settings if loading from a different domain
#### 3. Library Loading Issues
- Check internet connectivity (libraries load from CDN)
- Verify the HTML file path is correct
- Check WebView permissions and settings
#### 4. Platform-Specific Issues
**Android:**
- Ensure `allowFileAccess` is enabled
- Check if the HTML file is in the correct assets folder
- Verify WebView permissions in AndroidManifest.xml
**iOS:**
- Check WebView configuration in Info.plist
- Ensure JavaScript is enabled
- Verify file access permissions
### Common Error Messages
#### "Failed to load DICOM viewer libraries"
- Check internet connectivity
- Verify CDN URLs are accessible
- Check WebView JavaScript settings
#### "Failed to load DICOM image"
- Verify DICOM URL is accessible
- Check if the file is a valid DICOM format
- Ensure the server supports CORS
#### "Invalid message received from app"
- Check the message format being sent
- Verify the postMessage implementation
- Check WebView message handling
## Testing
### 1. Use Sample URLs
The test component includes sample DICOM URLs that are known to work:
- Sample DICOM 1-3 from OHIF examples
### 2. Test Custom URLs
- Enter your own DICOM URLs
- Test with different file formats
- Verify error handling
### 3. Debug Mode
- Check console logs in React Native debugger
- Monitor WebView messages
- Use the test component for isolated testing
## Performance Tips
### 1. Image Optimization
- Use compressed DICOM files when possible
- Consider implementing progressive loading
- Cache frequently accessed images
### 2. Memory Management
- Dispose of WebView when not in use
- Monitor memory usage with large DICOM files
- Implement proper cleanup in useEffect
### 3. Network Optimization
- Use CDN for DICOM files when possible
- Implement retry logic for failed requests
- Consider offline caching for critical images
## Security Considerations
### 1. URL Validation
- Validate DICOM URLs before loading
- Implement URL whitelisting if needed
- Sanitize user input for custom URLs
### 2. WebView Security
- Limit WebView permissions to minimum required
- Implement proper origin whitelisting
- Monitor for malicious content
### 3. Data Privacy
- Ensure DICOM files don't contain PHI
- Implement proper data handling protocols
- Follow HIPAA compliance guidelines
## Future Enhancements
### 1. Offline Support
- Bundle Cornerstone libraries locally
- Implement offline DICOM caching
- Support for local DICOM files
### 2. Advanced Features
- Multi-planar reconstruction (MPR)
- Measurement tools
- Annotation capabilities
- 3D rendering support
### 3. Performance Improvements
- WebAssembly integration
- GPU acceleration
- Progressive image loading
- Background processing
## Support
For issues and questions:
1. Check this README for common solutions
2. Review console logs and error messages
3. Test with sample URLs first
4. Verify WebView configuration
5. Check platform-specific requirements
## Dependencies
- `react-native-webview`: WebView component
- `cornerstone-core`: Medical imaging library
- `cornerstone-wado-image-loader`: DICOM file loader
## License
Design & Developed by Tech4Biz Solutions
Copyright (c) Spurrin Innovations. All rights reserved.

View File

@ -1,241 +0,0 @@
/*
* File: DicomViewerModal.example.tsx
* Description: Example usage of DicomViewerModal component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { DicomViewerModal } from './index';
import { theme } from '../../theme/theme';
// ============================================================================
// EXAMPLE COMPONENT
// ============================================================================
/**
* DicomViewerModalExample Component
*
* Purpose: Demonstrates how to use the DicomViewerModal component
*
* Features:
* - Shows how to pass dicomUrl to modal
* - Demonstrates modal state management
* - Example with patient information
* - Error handling examples
*/
export const DicomViewerModalExample: React.FC = () => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const [isModalVisible, setIsModalVisible] = useState(false);
// Example DICOM URLs (replace with your actual URLs)
const exampleDicomUrl = 'https://example-dicom-server.com/studies/123/series/456/instances/789';
// Example patient data
const patientData = {
name: 'John Doe',
studyDescription: 'CT Brain with Contrast',
};
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Open DICOM viewer modal
*/
const openDicomViewer = () => {
setIsModalVisible(true);
};
/**
* Close DICOM viewer modal
*/
const closeDicomViewer = () => {
setIsModalVisible(false);
};
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={styles.container}>
{/* Trigger Button */}
<TouchableOpacity
style={styles.button}
onPress={openDicomViewer}
activeOpacity={0.7}
>
<Text style={styles.buttonText}>View DICOM Image</Text>
</TouchableOpacity>
{/* DICOM Viewer Modal */}
<DicomViewerModal
visible={isModalVisible}
dicomUrl={exampleDicomUrl}
onClose={closeDicomViewer}
title="CT Brain Scan"
patientName={patientData.name}
studyDescription={patientData.studyDescription}
/>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: theme.colors.background,
padding: theme.spacing.lg,
},
button: {
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
...theme.shadows.primary,
},
buttonText: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.background,
},
});
// ============================================================================
// USAGE EXAMPLES IN OTHER COMPONENTS
// ============================================================================
/*
// Example 1: Basic Usage in Patient Details Screen
import { DicomViewerModal } from '../../../shared/components';
const PatientDetailsExample = () => {
const [showDicom, setShowDicom] = useState(false);
const dicomUrl = patient.scanResults?.dicomUrl;
return (
<>
<TouchableOpacity onPress={() => setShowDicom(true)}>
<Text>View Scan Results</Text>
</TouchableOpacity>
<DicomViewerModal
visible={showDicom}
dicomUrl={dicomUrl}
onClose={() => setShowDicom(false)}
patientName={patient.name}
studyDescription={patient.scanResults?.description}
/>
</>
);
};
// Example 2: Usage with Series Selection
import { DicomViewerModal } from '../../../shared/components';
const SeriesListExample = () => {
const [selectedDicom, setSelectedDicom] = useState<string | null>(null);
const [modalVisible, setModalVisible] = useState(false);
const openDicom = (dicomUrl: string) => {
setSelectedDicom(dicomUrl);
setModalVisible(true);
};
const closeDicom = () => {
setModalVisible(false);
setSelectedDicom(null);
};
return (
<>
{seriesList.map((series) => (
<TouchableOpacity
key={series.id}
onPress={() => openDicom(series.dicomUrl)}
>
<Text>{series.description}</Text>
</TouchableOpacity>
))}
<DicomViewerModal
visible={modalVisible}
dicomUrl={selectedDicom || ''}
onClose={closeDicom}
title="Series Viewer"
/>
</>
);
};
// Example 3: Usage with Error Handling
import { DicomViewerModal } from '../../../shared/components';
const ErrorHandlingExample = () => {
const [dicomModalState, setDicomModalState] = useState({
visible: false,
url: '',
title: '',
});
const openDicomWithValidation = (url: string, title: string) => {
if (!url) {
Alert.alert('Error', 'No DICOM URL available');
return;
}
setDicomModalState({
visible: true,
url,
title,
});
};
const closeDicomModal = () => {
setDicomModalState({
visible: false,
url: '',
title: '',
});
};
return (
<>
<TouchableOpacity
onPress={() => openDicomWithValidation(scan.url, scan.title)}
>
<Text>View Scan</Text>
</TouchableOpacity>
<DicomViewerModal
visible={dicomModalState.visible}
dicomUrl={dicomModalState.url}
onClose={closeDicomModal}
title={dicomModalState.title}
/>
</>
);
};
*/
/*
* End of File: DicomViewerModal.example.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,252 +0,0 @@
/*
* File: DicomViewerTest.tsx
* Description: Test component for DICOM viewer with sample URLs
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, Alert } from 'react-native';
import DicomViewer from './DicomViewer';
// Sample DICOM URLs for testing
const SAMPLE_DICOM_URLS = [
{
name: 'Sample DICOM 1',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm'
},
{
name: 'Sample DICOM 2',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm'
},
{
name: 'Sample DICOM 3',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-003.dcm'
}
];
export default function DicomViewerTest(): React.ReactElement {
const [dicomUrl, setDicomUrl] = useState(SAMPLE_DICOM_URLS[0].url);
const [customUrl, setCustomUrl] = useState('');
const handleUrlSelect = (url: string) => {
setDicomUrl(url);
setCustomUrl(url);
};
const handleCustomUrlSubmit = () => {
if (customUrl.trim()) {
setDicomUrl(customUrl.trim());
} else {
Alert.alert('Error', 'Please enter a valid URL');
}
};
const handleViewerError = (error: string) => {
console.error('DICOM Viewer Error:', error);
Alert.alert('DICOM Viewer Error', error);
};
const handleViewerLoad = () => {
console.log('DICOM Viewer loaded successfully');
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>DICOM Viewer Test</Text>
<Text style={styles.subtitle}>Test different DICOM URLs</Text>
</View>
<ScrollView style={styles.urlSection}>
<Text style={styles.sectionTitle}>Sample DICOM URLs:</Text>
{SAMPLE_DICOM_URLS.map((sample, index) => (
<TouchableOpacity
key={index}
style={[
styles.urlButton,
dicomUrl === sample.url && styles.selectedUrlButton
]}
onPress={() => handleUrlSelect(sample.url)}
>
<Text style={[
styles.urlButtonText,
dicomUrl === sample.url && styles.selectedUrlButtonText
]}>
{sample.name}
</Text>
<Text style={[
styles.urlText,
dicomUrl === sample.url && styles.selectedUrlText
]}>
{sample.url}
</Text>
</TouchableOpacity>
))}
<View style={styles.customUrlSection}>
<Text style={styles.sectionTitle}>Custom DICOM URL:</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
value={customUrl}
onChangeText={setCustomUrl}
placeholder="Enter DICOM URL..."
placeholderTextColor="#999"
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity
style={styles.submitButton}
onPress={handleCustomUrlSubmit}
>
<Text style={styles.submitButtonText}>Load</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.currentUrlSection}>
<Text style={styles.sectionTitle}>Current URL:</Text>
<Text style={styles.currentUrl}>{dicomUrl}</Text>
</View>
</ScrollView>
<View style={styles.viewerContainer}>
<Text style={styles.viewerTitle}>DICOM Viewer:</Text>
<DicomViewer
dicomUrl={dicomUrl}
onError={handleViewerError}
onLoad={handleViewerLoad}
debugMode={true}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
backgroundColor: '#2196F3',
padding: 20,
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#FFFFFF',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#E3F2FD',
},
urlSection: {
flex: 1,
padding: 16,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#212121',
marginBottom: 12,
marginTop: 16,
},
urlButton: {
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
marginBottom: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
selectedUrlButton: {
backgroundColor: '#E3F2FD',
borderColor: '#2196F3',
},
urlButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#212121',
marginBottom: 4,
},
selectedUrlButtonText: {
color: '#1976D2',
},
urlText: {
fontSize: 12,
color: '#757575',
fontFamily: 'monospace',
},
selectedUrlText: {
color: '#1976D2',
},
customUrlSection: {
marginTop: 16,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
},
textInput: {
flex: 1,
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E0E0E0',
borderRadius: 8,
padding: 12,
fontSize: 14,
color: '#212121',
marginRight: 8,
},
submitButton: {
backgroundColor: '#4CAF50',
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 8,
},
submitButtonText: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600',
},
currentUrlSection: {
marginTop: 16,
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
currentUrl: {
fontSize: 12,
color: '#757575',
fontFamily: 'monospace',
backgroundColor: '#F5F5F5',
padding: 8,
borderRadius: 4,
},
viewerContainer: {
flex: 2,
backgroundColor: '#000000',
margin: 16,
borderRadius: 8,
overflow: 'hidden',
},
viewerTitle: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
padding: 12,
backgroundColor: '#333333',
},
});
/*
* End of File: DicomViewerTest.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -21,15 +21,7 @@ export { default as DicomViewer } from './DicomViewer';
// DICOM Viewer Modal Component
export { default as DicomViewerModal } from './DicomViewerModal';
// DICOM Viewer Test Component
export { default as DicomViewerTest } from './DicomViewerTest';
// ============================================================================
// TYPE EXPORTS
// ============================================================================
// Export types for components
// export type { CustomModalProps } from './CustomModal';
/*
* End of File: index.ts

View File

@ -68,6 +68,8 @@ const PersistLoadingComponent: React.FC = () => {
* - Redux Provider for state management
* - PersistGate for state persistence
* - Loading state during rehydration
*
*
* - Error handling for persistence issues
*/
export const StoreProvider: React.FC<StoreProviderProps> = ({ children }) => {
@ -80,6 +82,7 @@ export const StoreProvider: React.FC<StoreProviderProps> = ({ children }) => {
// Called before the store is lifted (rehydrated)
console.log('Redux store is about to be rehydrated');
}}
//@ts-ignore
onAfterLift={() => {
// Called after the store is lifted (rehydrated)
console.log('Redux store has been rehydrated');

View File

@ -14,11 +14,8 @@ import authReducer from '../modules/Auth/redux/authSlice';
import dashboardReducer from '../modules/Dashboard/redux/dashboardSlice';
import aiDashboardReducer from '../modules/Dashboard/redux/aiDashboardSlice';
import patientCareReducer from '../modules/PatientCare/redux/patientCareSlice';
import alertsReducer from '../modules/Dashboard/redux/alertsSlice';
import settingsReducer from '../modules/Settings/redux/settingsSlice';
import uiReducer from '../modules/Dashboard/redux/uiSlice';
import hospitalReducer from '../modules/Auth/redux/hospitalSlice';
import aiPredictionReducer from '../modules/AIPrediction/redux/aiPredictionSlice';
import predictionsReducer from '../modules/Dashboard/redux/predictionsSlice';
// ============================================================================
@ -102,11 +99,8 @@ const rootReducer = combineReducers({
dashboard: dashboardReducer,
aiDashboard: aiDashboardReducer,
patientCare: patientCareReducer,
aiPrediction: aiPredictionReducer,
predictions: predictionsReducer,
alerts: alertsReducer,
settings: settingsReducer,
ui: uiReducer,
hospital: hospitalReducer,
});

View File

@ -3,10 +3,10 @@
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"android": "npx react-native run-android",
"ios": "npx react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"start": "npx react-native start",
"test": "jest"
},
"dependencies": {