first commit
This commit is contained in:
parent
5c241a0a23
commit
7a9e610b7f
351
.cursor/rules/appflow.mdc
Normal file
351
.cursor/rules/appflow.mdc
Normal file
@ -0,0 +1,351 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
# Physician App - Application Flow Rules
|
||||
|
||||
## 🚀 App Launch & Authentication Flow
|
||||
|
||||
### 1. Initial App Launch
|
||||
```
|
||||
App Launch → Splash Screen → Authentication Check
|
||||
↓
|
||||
[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
|
||||
↓
|
||||
├── Critical Alerts (Priority 1)
|
||||
├── Pending Scans (Priority 2)
|
||||
├── Recent Reports (Priority 3)
|
||||
└── All Patients (Complete View)
|
||||
```
|
||||
|
||||
### 5. Real-time Data Flow
|
||||
```
|
||||
WebSocket Connection → Live Updates
|
||||
↓
|
||||
├── Patient Status Changes
|
||||
├── New Scan Results
|
||||
├── Critical Findings
|
||||
├── Bed Assignments
|
||||
└── Shift Changes
|
||||
```
|
||||
|
||||
### 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.
|
||||
308
.cursor/rules/projectstructurerule.mdc
Normal file
308
.cursor/rules/projectstructurerule.mdc
Normal file
@ -0,0 +1,308 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
# Physician App - Project Structure & File Naming Rules
|
||||
|
||||
## 📁 Directory Structure Rules
|
||||
|
||||
### 1. Root Level Organization
|
||||
```
|
||||
NeoScan_Physician/
|
||||
├── app/ # Main application code
|
||||
├── docs/ # Documentation
|
||||
├── android/ # Android native code
|
||||
├── ios/ # iOS native code
|
||||
├── index.js # React Native entry point
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── metro.config.js # Metro bundler config
|
||||
├── babel.config.js # Babel config
|
||||
└── .eslintrc.js # ESLint config
|
||||
```
|
||||
|
||||
### 2. App Directory Structure
|
||||
```
|
||||
app/
|
||||
├── modules/ # Feature-based modules
|
||||
├── shared/ # Shared utilities & components
|
||||
├── store/ # Redux store configuration
|
||||
├── navigation/ # Navigation setup
|
||||
├── theme/ # Styling & theming
|
||||
├── config/ # Configuration files
|
||||
├── assets/ # Static assets
|
||||
├── localization/ # i18n
|
||||
├── App.tsx # Root component
|
||||
└── index.tsx # App entry point
|
||||
```
|
||||
|
||||
## 🏗️ Module Architecture Rules
|
||||
|
||||
### 3. Module Structure (Feature-based)
|
||||
Each module MUST follow this structure:
|
||||
```
|
||||
modules/ModuleName/
|
||||
├── components/ # Reusable UI components
|
||||
├── screens/ # Screen components
|
||||
├── hooks/ # Custom hooks
|
||||
├── redux/ # State management
|
||||
├── services/ # API & external services
|
||||
├── __tests__/ # Test files
|
||||
└── index.ts # Module exports
|
||||
```
|
||||
|
||||
### 4. Required Modules
|
||||
- **Auth/** - Authentication & SSO
|
||||
- **Dashboard/** - ER Dashboard & patient tracking
|
||||
- **PatientCare/** - Patient details & medical records
|
||||
- **Settings/** - User preferences & app settings
|
||||
|
||||
## 📝 File Naming Conventions
|
||||
|
||||
### 5. Component Files
|
||||
- **PascalCase** for all component files
|
||||
- **Suffix with type**: `.tsx` for components, `.ts` for utilities
|
||||
- **Examples**:
|
||||
- `LoginScreen.tsx`
|
||||
- `PatientCard.tsx`
|
||||
- `CriticalAlerts.tsx`
|
||||
- `HospitalSSO.tsx`
|
||||
|
||||
### 6. Hook Files
|
||||
- **camelCase** with `use` prefix
|
||||
- **Examples**:
|
||||
- `useAuth.ts`
|
||||
- `usePatientList.ts`
|
||||
- `useRealTimeAlerts.ts`
|
||||
- `useCriticalAlerts.ts`
|
||||
|
||||
### 7. Service Files
|
||||
- **camelCase** with descriptive names
|
||||
- **Suffix with type**: `API.ts`, `Service.ts`
|
||||
- **Examples**:
|
||||
- `authAPI.ts`
|
||||
- `patientCareAPI.ts`
|
||||
- `notificationService.ts`
|
||||
- `emrIntegration.ts`
|
||||
|
||||
### 8. Redux Files
|
||||
- **camelCase** with descriptive suffixes
|
||||
- **Examples**:
|
||||
- `authSlice.ts`
|
||||
- `erDashboardSlice.ts`
|
||||
- `patientCareActions.ts`
|
||||
- `dashboardSelectors.ts`
|
||||
|
||||
### 9. Test Files
|
||||
- **Same name as source file** + `.test.ts` or `.test.tsx`
|
||||
- **Examples**:
|
||||
- `LoginScreen.test.tsx`
|
||||
- `useAuth.test.ts`
|
||||
- `authSlice.test.ts`
|
||||
- `PatientCard.test.tsx`
|
||||
|
||||
## 🔧 Shared Components Rules
|
||||
|
||||
### 10. UI Components Structure
|
||||
```
|
||||
shared/components/
|
||||
├── UI/ # Basic UI components
|
||||
│ ├── Button.tsx
|
||||
│ ├── Input.tsx
|
||||
│ ├── Card.tsx
|
||||
│ ├── Modal.tsx
|
||||
│ ├── Badge.tsx
|
||||
│ ├── Spinner.tsx
|
||||
│ ├── Alert.tsx
|
||||
│ ├── Dropdown.tsx
|
||||
│ ├── Tabs.tsx
|
||||
│ ├── ProgressBar.tsx
|
||||
│ └── index.ts
|
||||
├── Forms/ # Form-related components
|
||||
│ ├── FormField.tsx
|
||||
│ ├── ValidationMessage.tsx
|
||||
│ ├── FormContainer.tsx
|
||||
│ └── index.ts
|
||||
├── Icons/ # Icon components
|
||||
│ ├── MedicalIcons.tsx
|
||||
│ ├── StatusIcons.tsx
|
||||
│ ├── NavigationIcons.tsx
|
||||
│ └── index.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
### 11. Utility Files
|
||||
```
|
||||
shared/utils/
|
||||
├── api.ts # API utilities
|
||||
├── constants.ts # App constants
|
||||
├── helpers.ts # Helper functions
|
||||
├── validators.ts # Validation functions
|
||||
├── formatters.ts # Data formatting
|
||||
├── dateUtils.ts # Date utilities
|
||||
├── medicalUtils.ts # Medical-specific utilities
|
||||
├── imageUtils.ts # Image processing
|
||||
├── stringUtils.ts # String manipulation
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
## 🎨 Assets Organization
|
||||
|
||||
### 12. Image Assets
|
||||
```
|
||||
assets/images/
|
||||
├── logos/ # Hospital & app logos
|
||||
├── icons/ # UI icons
|
||||
│ ├── medical/ # Medical-specific icons
|
||||
│ ├── ui/ # General UI icons
|
||||
│ └── status/ # Status indicators
|
||||
├── backgrounds/ # Background images
|
||||
└── placeholders/ # Placeholder images
|
||||
```
|
||||
|
||||
### 13. Asset Naming
|
||||
- **kebab-case** for all asset files
|
||||
- **Examples**:
|
||||
- `hospital-logo.png`
|
||||
- `critical-alert.mp3`
|
||||
- `ct-scan-placeholder.png`
|
||||
- `emergency-bg.jpg`
|
||||
|
||||
## 📱 Navigation Structure
|
||||
|
||||
### 14. Navigation Files
|
||||
```
|
||||
navigation/
|
||||
├── AppNavigator.tsx # Root navigator
|
||||
├── AuthNavigator.tsx # Authentication flow
|
||||
├── MainNavigator.tsx # Main app flow
|
||||
├── TabNavigator.tsx # Tab navigation
|
||||
├── navigationTypes.ts # Type definitions
|
||||
├── navigationUtils.ts # Navigation utilities
|
||||
└── __tests__/
|
||||
```
|
||||
|
||||
## 🔐 Configuration Rules
|
||||
|
||||
### 15. Environment & Config
|
||||
```
|
||||
config/
|
||||
├── env.ts # Environment variables
|
||||
├── api.ts # API configuration
|
||||
├── websocket.ts # WebSocket config
|
||||
├── notifications.ts # Notification config
|
||||
├── security.ts # Security settings
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
## 📚 Documentation Rules
|
||||
|
||||
### 16. Documentation Structure
|
||||
```
|
||||
docs/
|
||||
├── README.md # Project overview
|
||||
├── ARCHITECTURE.md # Architecture documentation
|
||||
├── API.md # API documentation
|
||||
├── DEPLOYMENT.md # Deployment guide
|
||||
├── TESTING.md # Testing guidelines
|
||||
├── SECURITY.md # Security guidelines
|
||||
└── wireframes/ # UI wireframes
|
||||
```
|
||||
|
||||
## 🧪 Testing Rules
|
||||
|
||||
### 17. Test Organization
|
||||
- **Unit tests** alongside source files in `__tests__/` folders
|
||||
- **Integration tests** in module-level `__tests__/` folders
|
||||
- **E2E tests** in root-level `__tests__/` folder
|
||||
- **Test utilities** in `shared/__tests__/`
|
||||
|
||||
## 📦 Package Management
|
||||
|
||||
### 18. Dependencies Organization
|
||||
- **Core dependencies** in root `package.json`
|
||||
- **Platform-specific** dependencies in respective folders
|
||||
- **Dev dependencies** clearly separated
|
||||
- **Peer dependencies** explicitly declared
|
||||
|
||||
## 🔄 Import/Export Rules
|
||||
|
||||
### 19. Import Conventions
|
||||
- **Absolute imports** for shared utilities
|
||||
- **Relative imports** for module-internal files
|
||||
- **Index files** for clean imports
|
||||
- **Barrel exports** for module APIs
|
||||
|
||||
### 20. Export Patterns
|
||||
```typescript
|
||||
// Module index.ts
|
||||
export { default as ComponentName } from './components/ComponentName';
|
||||
export { useHookName } from './hooks/useHookName';
|
||||
export { actionName } from './redux/actions';
|
||||
export type { TypeName } from './types';
|
||||
```
|
||||
|
||||
## 🚫 Naming Restrictions
|
||||
|
||||
### 21. Forbidden Patterns
|
||||
- ❌ No spaces in file names
|
||||
- ❌ No special characters except `-` and `_`
|
||||
- ❌ No uppercase in utility/service files
|
||||
- ❌ No generic names like `utils.ts` or `helpers.ts`
|
||||
- ❌ No abbreviations unless universally understood
|
||||
|
||||
### 22. Required Patterns
|
||||
- ✅ Descriptive, self-documenting names
|
||||
- ✅ Consistent casing within categories
|
||||
- ✅ Clear separation of concerns
|
||||
- ✅ Meaningful directory structure
|
||||
- ✅ Proper TypeScript extensions
|
||||
|
||||
## 📋 File Size Guidelines
|
||||
|
||||
### 23. Component Limits
|
||||
- **Single component files**: Max 300 lines
|
||||
- **Complex components**: Split into smaller components
|
||||
- **Utility files**: Max 200 lines
|
||||
- **Service files**: Max 150 lines per service
|
||||
|
||||
### 24. Module Limits
|
||||
- **Module components**: Max 10 files per category
|
||||
- **Module services**: Max 5 files
|
||||
- **Module hooks**: Max 8 files
|
||||
- **Module redux**: Max 6 files
|
||||
|
||||
## 🔍 Code Organization Principles
|
||||
|
||||
### 25. Separation of Concerns
|
||||
- **UI Logic** in components
|
||||
- **Business Logic** in hooks/services
|
||||
- **State Management** in Redux
|
||||
- **Data Fetching** in services
|
||||
- **Utilities** in shared/utils
|
||||
|
||||
### 26. Reusability
|
||||
- **Shared components** in shared/components
|
||||
- **Common utilities** in shared/utils
|
||||
- **Type definitions** in shared/types
|
||||
- **Constants** in shared/constants
|
||||
|
||||
This structure ensures maintainability, scalability, and consistency across the Physician App codebase.
|
||||
|
||||
|
||||
and it should add proper comments in the each file for better understanding the flow.
|
||||
should follow rule file while generating code.Each file should contain this as header
|
||||
/*
|
||||
* File: FILE_NAME.tsx
|
||||
* Description: Main chat screen component
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
|
||||
file footer
|
||||
/*
|
||||
* End of File: ChatScreen.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
and it should add proper comments in the file for better understanding the flow.
|
||||
should follow rule file while generating code.
|
||||
718
.cursor/rules/themeflow.mdc
Normal file
718
.cursor/rules/themeflow.mdc
Normal file
@ -0,0 +1,718 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
# Physician App - Theme & UI Design Rules
|
||||
|
||||
## 🎨 Color Palette - "Modern Healthcare Blue"
|
||||
|
||||
### 1. Primary Color Scheme
|
||||
```typescript
|
||||
// Primary Colors
|
||||
Primary: '#2196F3' // Material Blue - Main brand color
|
||||
Secondary: '#1976D2' // Darker Blue - Secondary actions
|
||||
Tertiary: '#E3F2FD' // Very Light Blue - Backgrounds
|
||||
Quaternary: '#0D47A1' // Deep Blue - Accents
|
||||
|
||||
// Text Colors
|
||||
TextPrimary: '#212121' // Dark Gray - Main text
|
||||
TextSecondary: '#757575' // Medium Gray - Secondary text
|
||||
TextMuted: '#9E9E9E' // Light Gray - Muted text
|
||||
|
||||
// Background Colors
|
||||
Background: '#FFFFFF' // White - Primary background
|
||||
BackgroundAlt: '#FAFAFA' // Light Gray - Alternative background
|
||||
BackgroundAccent: '#F5F5F5' // Soft Gray - Accent backgrounds
|
||||
```
|
||||
|
||||
### 2. Status & Feedback Colors
|
||||
```typescript
|
||||
// Status Colors
|
||||
Success: '#4CAF50' // Material Green - Success states
|
||||
Warning: '#FF9800' // Material Orange - Warning states
|
||||
Error: '#F44336' // Material Red - Error states
|
||||
Info: '#2196F3' // Material Blue - Information states
|
||||
|
||||
// UI Elements
|
||||
Border: '#E0E0E0' // Light Gray Border
|
||||
CardBackground: '#FFFFFF' // White - Card backgrounds
|
||||
Shadow: 'rgba(0, 0, 0, 0.1)' // Subtle Gray Shadow
|
||||
```
|
||||
|
||||
## 🏗️ Typography System
|
||||
|
||||
### 3. Font Hierarchy
|
||||
```typescript
|
||||
// Font Families
|
||||
PrimaryFont: 'Roboto' // Main font family
|
||||
SecondaryFont: 'Medical-Icons' // Icon font
|
||||
|
||||
// Font Weights
|
||||
Light: 300
|
||||
Regular: 400
|
||||
Medium: 500
|
||||
Bold: 700
|
||||
|
||||
// Font Sizes
|
||||
DisplayLarge: 32px // Main headings
|
||||
DisplayMedium: 24px // Section headings
|
||||
DisplaySmall: 20px // Subsection headings
|
||||
BodyLarge: 16px // Body text
|
||||
BodyMedium: 14px // Secondary text
|
||||
BodySmall: 12px // Captions
|
||||
Caption: 10px // Small labels
|
||||
```
|
||||
|
||||
### 4. Line Heights & Spacing
|
||||
```typescript
|
||||
// Line Heights
|
||||
Tight: 1.2 // Headings
|
||||
Normal: 1.4 // Body text
|
||||
Relaxed: 1.6 // Long text
|
||||
|
||||
// Letter Spacing
|
||||
Tight: -0.5px // Headings
|
||||
Normal: 0px // Body text
|
||||
Wide: 0.5px // Labels
|
||||
```
|
||||
|
||||
## 📐 Spacing & Layout
|
||||
|
||||
### 5. Spacing Scale
|
||||
```typescript
|
||||
// Base Spacing Unit: 4px
|
||||
Spacing: {
|
||||
xs: 4, // 4px
|
||||
sm: 8, // 8px
|
||||
md: 16, // 16px
|
||||
lg: 24, // 24px
|
||||
xl: 32, // 32px
|
||||
xxl: 48, // 48px
|
||||
xxxl: 64 // 64px
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Layout Rules
|
||||
```typescript
|
||||
// Container Max Widths
|
||||
Mobile: 375
|
||||
Tablet: 768
|
||||
Desktop: 1024
|
||||
|
||||
// Border Radius
|
||||
Small: 4
|
||||
Medium: 8
|
||||
Large: 12
|
||||
XLarge: 16
|
||||
Round: 50
|
||||
|
||||
// Shadows (React Native StyleSheet)
|
||||
Small: {
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
|
||||
Medium: {
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
|
||||
Large: {
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 16,
|
||||
elevation: 8
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Component Design Rules
|
||||
|
||||
### 7. Button Design System
|
||||
```typescript
|
||||
// Button Variants
|
||||
Primary: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderColor: '#2196F3',
|
||||
borderRadius: 8,
|
||||
shadowColor: '#2196F3',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3
|
||||
}
|
||||
|
||||
Secondary: {
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: '#2196F3',
|
||||
borderRadius: 8
|
||||
}
|
||||
|
||||
Success: {
|
||||
backgroundColor: '#4CAF50',
|
||||
borderColor: '#4CAF50',
|
||||
borderRadius: 8,
|
||||
shadowColor: '#4CAF50',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3
|
||||
}
|
||||
|
||||
Critical: {
|
||||
backgroundColor: '#F44336',
|
||||
borderColor: '#F44336',
|
||||
borderRadius: 8,
|
||||
shadowColor: '#F44336',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3
|
||||
}
|
||||
|
||||
// Button Sizes
|
||||
Small: { paddingHorizontal: 16, paddingVertical: 8, fontSize: 14, borderRadius: 6 }
|
||||
Medium: { paddingHorizontal: 24, paddingVertical: 12, fontSize: 16, borderRadius: 8 }
|
||||
Large: { paddingHorizontal: 32, paddingVertical: 16, fontSize: 18, borderRadius: 10 }
|
||||
```
|
||||
|
||||
### 8. Card Design Rules
|
||||
```typescript
|
||||
// Card Variants
|
||||
Default: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E0E0E0',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
|
||||
Elevated: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E0E0E0',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
|
||||
Critical: {
|
||||
backgroundColor: '#FFEBEE',
|
||||
borderColor: '#F44336',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
shadowColor: '#F44336',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
|
||||
PatientCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E0E0E0',
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Input Field Design
|
||||
```typescript
|
||||
// Input States
|
||||
Default: {
|
||||
borderColor: '#E0E0E0',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12
|
||||
}
|
||||
|
||||
Focused: {
|
||||
borderColor: '#2196F3',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12
|
||||
}
|
||||
|
||||
Error: {
|
||||
borderColor: '#F44336',
|
||||
backgroundColor: '#FFEBEE',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12
|
||||
}
|
||||
|
||||
Disabled: {
|
||||
borderColor: '#E0E0E0',
|
||||
backgroundColor: '#F5F5F5',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 Alert & Status Design (Following Screenshot UI)
|
||||
|
||||
### 10. Alert Priority System
|
||||
```typescript
|
||||
// Critical Alerts
|
||||
Critical: {
|
||||
backgroundColor: '#FFEBEE',
|
||||
borderColor: '#F44336',
|
||||
borderRadius: 8,
|
||||
padding: 16
|
||||
}
|
||||
|
||||
// Warning Alerts
|
||||
Warning: {
|
||||
backgroundColor: '#FFF3E0',
|
||||
borderColor: '#FF9800',
|
||||
borderRadius: 8,
|
||||
padding: 16
|
||||
}
|
||||
|
||||
// Success Alerts
|
||||
Success: {
|
||||
backgroundColor: '#E8F5E8',
|
||||
borderColor: '#4CAF50',
|
||||
borderRadius: 8,
|
||||
padding: 16
|
||||
}
|
||||
|
||||
// Info Alerts
|
||||
Info: {
|
||||
backgroundColor: '#E3F2FD',
|
||||
borderColor: '#2196F3',
|
||||
borderRadius: 8,
|
||||
padding: 16
|
||||
}
|
||||
|
||||
// Patient Status
|
||||
Active: {
|
||||
backgroundColor: '#E3F2FD',
|
||||
borderColor: '#2196F3',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4
|
||||
}
|
||||
|
||||
Pending: {
|
||||
backgroundColor: '#FFF3E0',
|
||||
borderColor: '#FF9800',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4
|
||||
}
|
||||
|
||||
Completed: {
|
||||
backgroundColor: '#E8F5E8',
|
||||
borderColor: '#4CAF50',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4
|
||||
}
|
||||
```
|
||||
|
||||
### 11. Badge & Tag Design (Following Screenshot UI)
|
||||
```typescript
|
||||
// Status Badges
|
||||
StatusBadge: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
fontSize: 12,
|
||||
fontWeight: '500'
|
||||
}
|
||||
|
||||
// Priority Badges
|
||||
PriorityBadge: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 16,
|
||||
fontSize: 12,
|
||||
fontWeight: '600'
|
||||
}
|
||||
|
||||
// Medical Badges
|
||||
MedicalBadge: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 14,
|
||||
fontSize: 11,
|
||||
fontWeight: '500'
|
||||
}
|
||||
```
|
||||
|
||||
## 📱 Screen-Specific Design Rules
|
||||
|
||||
### 12. ER Dashboard Screen Design (Following Screenshot UI)
|
||||
```typescript
|
||||
// Header Section
|
||||
Header: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderBottomColor: '#E0E0E0',
|
||||
borderBottomWidth: 1
|
||||
}
|
||||
|
||||
// Critical Alerts Section (Like promotional banner but for emergencies)
|
||||
CriticalAlerts: {
|
||||
backgroundColor: '#FFEBEE',
|
||||
borderColor: '#F44336',
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
marginHorizontal: 16,
|
||||
marginVertical: 12,
|
||||
shadowColor: '#F44336',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
|
||||
// Patient List Section (Like specialist filter)
|
||||
PatientList: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
backgroundColor: '#FFFFFF'
|
||||
}
|
||||
|
||||
// Quick Actions (Like service icons but for emergency actions)
|
||||
QuickActions: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: '#F5F5F5',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 8
|
||||
}
|
||||
|
||||
// Patient Cards (Like doctor cards but for patients)
|
||||
PatientCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginHorizontal: 8,
|
||||
marginVertical: 4,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
minWidth: 200
|
||||
}
|
||||
```
|
||||
|
||||
### 13. Patient Details Screen Design (Following Screenshot UI)
|
||||
```typescript
|
||||
// Patient Header
|
||||
PatientHeader: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
padding: 20,
|
||||
borderBottomColor: '#E0E0E0',
|
||||
borderBottomWidth: 1
|
||||
}
|
||||
|
||||
// Patient Info Card
|
||||
PatientInfoCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
margin: 16,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
|
||||
// Vital Signs Section (Like contact options but for medical data)
|
||||
VitalSigns: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
paddingVertical: 16,
|
||||
borderBottomColor: '#E0E0E0',
|
||||
borderBottomWidth: 1
|
||||
}
|
||||
|
||||
// Medical History Section (Like calendar section but for medical records)
|
||||
MedicalHistory: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
margin: 16,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
|
||||
// Action Buttons (Like booking button but for medical actions)
|
||||
ActionButton: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
margin: 16,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#2196F3',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 6
|
||||
}
|
||||
```
|
||||
|
||||
### 14. Login Screen Design (Following Screenshot UI)
|
||||
```typescript
|
||||
// Login Container
|
||||
LoginContainer: {
|
||||
backgroundColor: '#F1FDFF',
|
||||
padding: 24,
|
||||
borderRadius: 12,
|
||||
shadowColor: '#2196F3',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
|
||||
// Hospital Logo
|
||||
Logo: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
marginBottom: 32
|
||||
}
|
||||
|
||||
// Form Fields
|
||||
FormField: {
|
||||
marginBottom: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E0E0E0',
|
||||
borderRadius: 8
|
||||
}
|
||||
|
||||
// Login Button
|
||||
LoginButton: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: '600'
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 Icon Design System
|
||||
|
||||
### 15. Icon Specifications
|
||||
```typescript
|
||||
// Icon Sizes
|
||||
IconSizes: {
|
||||
xs: 12, // Extra small
|
||||
sm: 16, // Small
|
||||
md: 24, // Medium (default)
|
||||
lg: 32, // Large
|
||||
xl: 48 // Extra large
|
||||
}
|
||||
|
||||
// Icon Colors
|
||||
IconColors: {
|
||||
Primary: '#2196F3',
|
||||
Secondary: '#1976D2',
|
||||
Success: '#4CAF50',
|
||||
Warning: '#FF9800',
|
||||
Error: '#F44336',
|
||||
Info: '#2196F3',
|
||||
Muted: '#9E9E9E'
|
||||
}
|
||||
|
||||
// Medical Icons
|
||||
MedicalIcons: {
|
||||
Stethoscope: 'medical-stethoscope',
|
||||
Heart: 'medical-heart',
|
||||
Brain: 'medical-brain',
|
||||
Emergency: 'medical-emergency',
|
||||
Patient: 'medical-patient',
|
||||
Bed: 'medical-bed',
|
||||
Alert: 'medical-alert',
|
||||
VitalSigns: 'medical-vital-signs',
|
||||
Medication: 'medical-medication',
|
||||
Lab: 'medical-lab',
|
||||
Imaging: 'medical-imaging',
|
||||
Surgery: 'medical-surgery'
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Data Visualization
|
||||
|
||||
### 16. Chart Colors
|
||||
```typescript
|
||||
// Chart Color Palette
|
||||
ChartColors: {
|
||||
Primary: '#2196F3',
|
||||
Secondary: '#1976D2',
|
||||
Tertiary: '#E3F2FD',
|
||||
Quaternary: '#0D47A1',
|
||||
Success: '#4CAF50',
|
||||
Warning: '#FF9800',
|
||||
Error: '#F44336'
|
||||
}
|
||||
|
||||
// Vital Signs Charts
|
||||
VitalSignsChart: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
gridColor: '#E0E0E0',
|
||||
lineColor: '#2196F3',
|
||||
pointColor: '#1976D2'
|
||||
}
|
||||
|
||||
// Patient Status Charts
|
||||
PatientStatusChart: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
gridColor: '#E0E0E0',
|
||||
lineColor: '#4CAF50',
|
||||
pointColor: '#2E7D32'
|
||||
}
|
||||
```
|
||||
|
||||
## 🌙 Dark Mode Support
|
||||
|
||||
### 17. Dark Mode Colors
|
||||
```typescript
|
||||
// Dark Mode Palette
|
||||
DarkMode: {
|
||||
Background: '#121212',
|
||||
BackgroundAlt: '#1E1E1E',
|
||||
BackgroundAccent: '#2D2D2D',
|
||||
TextPrimary: '#FFFFFF',
|
||||
TextSecondary: '#B0B0B0',
|
||||
TextMuted: '#808080',
|
||||
Border: '#404040',
|
||||
CardBackground: '#1E1E1E',
|
||||
Primary: '#2196F3',
|
||||
Secondary: '#1976D2'
|
||||
}
|
||||
```
|
||||
|
||||
## 📱 Responsive Design
|
||||
|
||||
### 18. Breakpoint System
|
||||
```typescript
|
||||
// Breakpoints
|
||||
Breakpoints: {
|
||||
Mobile: 375,
|
||||
Tablet: 768,
|
||||
Desktop: 1024,
|
||||
LargeDesktop: 1440
|
||||
}
|
||||
|
||||
// Responsive Spacing
|
||||
ResponsiveSpacing: {
|
||||
Mobile: { padding: 16, margin: 8 },
|
||||
Tablet: { padding: 24, margin: 16 },
|
||||
Desktop: { padding: 32, margin: 24 }
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Accessibility Rules
|
||||
|
||||
### 19. Accessibility Standards
|
||||
```typescript
|
||||
// Color Contrast
|
||||
ContrastRatios: {
|
||||
Normal: 4.5, // Minimum for normal text
|
||||
Large: 3.0, // Minimum for large text
|
||||
UI: 3.0 // Minimum for UI elements
|
||||
}
|
||||
|
||||
// Touch Targets
|
||||
TouchTargets: {
|
||||
Minimum: 44, // Minimum touch target size
|
||||
Preferred: 48 // Preferred touch target size
|
||||
}
|
||||
|
||||
// Focus Indicators
|
||||
FocusIndicator: {
|
||||
Color: '#2196F3',
|
||||
Width: 2,
|
||||
Style: 'solid'
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 Animation & Transitions
|
||||
|
||||
### 20. Animation Rules
|
||||
```typescript
|
||||
// Animation Durations
|
||||
Durations: {
|
||||
Fast: 150, // Quick interactions
|
||||
Normal: 300, // Standard transitions
|
||||
Slow: 500 // Complex animations
|
||||
}
|
||||
|
||||
// Easing Functions
|
||||
Easing: {
|
||||
Standard: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
|
||||
Deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
|
||||
Acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)'
|
||||
}
|
||||
|
||||
// Transition Types
|
||||
Transitions: {
|
||||
Fade: { opacity: [0, 1], duration: 300 },
|
||||
Slide: { transform: [{ translateY: [20, 0] }], duration: 300 },
|
||||
Scale: { transform: [{ scale: [0.95, 1] }], duration: 200 }
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 Design Tokens
|
||||
|
||||
### 21. Design Token Structure
|
||||
```typescript
|
||||
// Theme Object Structure
|
||||
Theme: {
|
||||
colors: ColorPalette,
|
||||
typography: TypographySystem,
|
||||
spacing: SpacingScale,
|
||||
borderRadius: BorderRadiusScale,
|
||||
shadows: ShadowSystem,
|
||||
breakpoints: BreakpointSystem,
|
||||
animations: AnimationSystem
|
||||
}
|
||||
|
||||
// Usage Example
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
padding: theme.spacing.md,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This comprehensive theme system ensures consistency, accessibility, and modern healthcare aesthetics across the Physician App, following the clean and professional design patterns shown in the reference screenshots while maintaining the ER workflow functionality.
|
||||
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
BASE_URL='https://neoscan-backend.tech4bizsolutions.com'
|
||||
# BASE_URL='http://192.168.1.87:3000'
|
||||
355
PROJECT_STRUCTURE.md
Normal file
355
PROJECT_STRUCTURE.md
Normal file
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* File: PROJECT_STRUCTURE.md
|
||||
* Description: Complete project structure and architecture documentation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
# NeoScan Physician App - Project Structure
|
||||
|
||||
## 📁 Complete Directory Structure
|
||||
|
||||
```
|
||||
NeoScan_Physician/
|
||||
├── app/ # Main application code
|
||||
│ ├── modules/ # Feature-based modules
|
||||
│ │ ├── Auth/ # Authentication module
|
||||
│ │ │ ├── components/ # Auth-specific components
|
||||
│ │ │ ├── screens/ # Auth screens
|
||||
│ │ │ │ └── LoginScreen.tsx # Main login screen
|
||||
│ │ │ ├── hooks/ # Auth custom hooks
|
||||
│ │ │ ├── redux/ # Auth state management
|
||||
│ │ │ ├── services/ # Auth API services
|
||||
│ │ │ └── index.ts # Auth module exports
|
||||
│ │ ├── Dashboard/ # ER Dashboard module
|
||||
│ │ │ ├── components/ # Dashboard components
|
||||
│ │ │ │ ├── PatientCard.tsx # Patient information card
|
||||
│ │ │ │ ├── CriticalAlerts.tsx # Critical alerts display
|
||||
│ │ │ │ ├── DashboardHeader.tsx # Dashboard statistics header
|
||||
│ │ │ │ ├── QuickActions.tsx # Emergency quick actions
|
||||
│ │ │ │ └── DepartmentStats.tsx # Department statistics
|
||||
│ │ │ ├── screens/ # Dashboard screens
|
||||
│ │ │ │ └── ERDashboardScreen.tsx # Main ER dashboard
|
||||
│ │ │ ├── hooks/ # Dashboard custom hooks
|
||||
│ │ │ ├── redux/ # Dashboard state management
|
||||
│ │ │ ├── services/ # Dashboard API services
|
||||
│ │ │ └── index.ts # Dashboard module exports
|
||||
│ │ ├── PatientCare/ # Patient management module
|
||||
│ │ │ ├── components/ # Patient care components
|
||||
│ │ │ ├── screens/ # Patient care screens
|
||||
│ │ │ ├── hooks/ # Patient care hooks
|
||||
│ │ │ ├── redux/ # Patient care state
|
||||
│ │ │ ├── services/ # Patient care services
|
||||
│ │ │ └── index.ts # Patient care exports
|
||||
│ │ └── Settings/ # App settings module
|
||||
│ │ ├── components/ # Settings components
|
||||
│ │ ├── screens/ # Settings screens
|
||||
│ │ ├── hooks/ # Settings hooks
|
||||
│ │ ├── redux/ # Settings state
|
||||
│ │ ├── services/ # Settings services
|
||||
│ │ └── index.ts # Settings exports
|
||||
│ ├── shared/ # Shared utilities & components
|
||||
│ │ ├── components/ # Reusable UI components
|
||||
│ │ │ ├── UI/ # Basic UI components
|
||||
│ │ │ │ ├── Button.tsx # Button component
|
||||
│ │ │ │ ├── Input.tsx # Input field component
|
||||
│ │ │ │ ├── Card.tsx # Card component
|
||||
│ │ │ │ ├── Modal.tsx # Modal component
|
||||
│ │ │ │ ├── Badge.tsx # Badge component
|
||||
│ │ │ │ ├── Spinner.tsx # Loading spinner
|
||||
│ │ │ │ ├── Alert.tsx # Alert component
|
||||
│ │ │ │ ├── Dropdown.tsx # Dropdown component
|
||||
│ │ │ │ ├── Tabs.tsx # Tab component
|
||||
│ │ │ │ ├── ProgressBar.tsx # Progress bar
|
||||
│ │ │ │ └── index.ts # UI components export
|
||||
│ │ │ ├── Forms/ # Form-related components
|
||||
│ │ │ │ ├── FormField.tsx # Form field component
|
||||
│ │ │ │ ├── ValidationMessage.tsx # Validation message
|
||||
│ │ │ │ ├── FormContainer.tsx # Form container
|
||||
│ │ │ │ └── index.ts # Form components export
|
||||
│ │ │ ├── Icons/ # Icon components
|
||||
│ │ │ │ ├── MedicalIcons.tsx # Medical-specific icons
|
||||
│ │ │ │ ├── StatusIcons.tsx # Status indicators
|
||||
│ │ │ │ ├── NavigationIcons.tsx # Navigation icons
|
||||
│ │ │ │ └── index.ts # Icon components export
|
||||
│ │ │ └── index.ts # Shared components export
|
||||
│ │ ├── utils/ # Utility functions
|
||||
│ │ │ ├── api.ts # API utilities
|
||||
│ │ │ ├── constants.ts # App constants
|
||||
│ │ │ ├── helpers.ts # Helper functions
|
||||
│ │ │ ├── validators.ts # Validation functions
|
||||
│ │ │ ├── formatters.ts # Data formatting
|
||||
│ │ │ ├── dateUtils.ts # Date utilities
|
||||
│ │ │ ├── medicalUtils.ts # Medical-specific utilities
|
||||
│ │ │ ├── imageUtils.ts # Image processing
|
||||
│ │ │ ├── stringUtils.ts # String manipulation
|
||||
│ │ │ └── index.ts # Utils export
|
||||
│ │ ├── types/ # TypeScript type definitions
|
||||
│ │ │ ├── auth.ts # Authentication types
|
||||
│ │ │ ├── patient.ts # Patient-related types
|
||||
│ │ │ ├── dashboard.ts # Dashboard types
|
||||
│ │ │ ├── alerts.ts # Alert types
|
||||
│ │ │ ├── common.ts # Common types
|
||||
│ │ │ └── index.ts # Types export
|
||||
│ │ └── index.ts # Shared module export
|
||||
│ ├── theme/ # Design system & theming
|
||||
│ │ ├── colors.ts # Color palette
|
||||
│ │ ├── typography.ts # Typography system
|
||||
│ │ ├── spacing.ts # Spacing & layout
|
||||
│ │ ├── shadows.ts # Shadow system
|
||||
│ │ ├── animations.ts # Animation system
|
||||
│ │ ├── theme.ts # Main theme object
|
||||
│ │ └── index.ts # Theme export
|
||||
│ ├── navigation/ # Navigation setup
|
||||
│ │ ├── AppNavigator.tsx # Root navigator
|
||||
│ │ ├── AuthNavigator.tsx # Authentication flow
|
||||
│ │ ├── MainNavigator.tsx # Main app flow
|
||||
│ │ ├── TabNavigator.tsx # Tab navigation
|
||||
│ │ ├── navigationTypes.ts # Navigation types
|
||||
│ │ ├── navigationUtils.ts # Navigation utilities
|
||||
│ │ └── __tests__/ # Navigation tests
|
||||
│ ├── store/ # Redux store configuration
|
||||
│ │ ├── index.ts # Store configuration
|
||||
│ │ ├── rootReducer.ts # Root reducer
|
||||
│ │ ├── middleware.ts # Custom middleware
|
||||
│ │ └── __tests__/ # Store tests
|
||||
│ ├── config/ # Configuration files
|
||||
│ │ ├── env.ts # Environment variables
|
||||
│ │ ├── api.ts # API configuration
|
||||
│ │ ├── websocket.ts # WebSocket config
|
||||
│ │ ├── notifications.ts # Notification config
|
||||
│ │ ├── security.ts # Security settings
|
||||
│ │ └── index.ts # Config export
|
||||
│ ├── assets/ # Static assets
|
||||
│ │ ├── images/ # Image assets
|
||||
│ │ │ ├── logos/ # Hospital & app logos
|
||||
│ │ │ ├── icons/ # UI icons
|
||||
│ │ │ │ ├── medical/ # Medical-specific icons
|
||||
│ │ │ │ ├── ui/ # General UI icons
|
||||
│ │ │ │ └── status/ # Status indicators
|
||||
│ │ │ ├── backgrounds/ # Background images
|
||||
│ │ │ └── placeholders/ # Placeholder images
|
||||
│ │ ├── fonts/ # Font files
|
||||
│ │ └── sounds/ # Audio assets
|
||||
│ ├── localization/ # Internationalization
|
||||
│ │ ├── en/ # English translations
|
||||
│ │ ├── es/ # Spanish translations
|
||||
│ │ ├── fr/ # French translations
|
||||
│ │ └── index.ts # i18n configuration
|
||||
│ ├── App.tsx # Root component
|
||||
│ └── index.tsx # App entry point
|
||||
├── android/ # Android native code
|
||||
│ ├── app/ # Android app module
|
||||
│ │ ├── build.gradle # App build configuration
|
||||
│ │ ├── debug.keystore # Debug keystore
|
||||
│ │ ├── proguard-rules.pro # ProGuard rules
|
||||
│ │ └── src/ # Source code
|
||||
│ │ ├── debug/ # Debug configuration
|
||||
│ │ │ └── AndroidManifest.xml # Debug manifest
|
||||
│ │ └── main/ # Main source
|
||||
│ │ ├── AndroidManifest.xml # Main manifest
|
||||
│ │ ├── java/ # Java source
|
||||
│ │ │ └── com/ # Package structure
|
||||
│ │ │ └── neoscan_physician/
|
||||
│ │ │ ├── MainActivity.kt # Main activity
|
||||
│ │ │ └── MainApplication.kt # Application class
|
||||
│ │ └── res/ # Resources
|
||||
│ │ ├── drawable/ # Drawable resources
|
||||
│ │ ├── mipmap-*/ # App icons
|
||||
│ │ └── values/ # Values
|
||||
│ │ ├── strings.xml # String resources
|
||||
│ │ └── styles.xml # Styles
|
||||
│ ├── build.gradle # Project build configuration
|
||||
│ ├── gradle/ # Gradle wrapper
|
||||
│ ├── gradle.properties # Gradle properties
|
||||
│ ├── gradlew # Gradle wrapper script
|
||||
│ ├── gradlew.bat # Windows gradle wrapper
|
||||
│ └── settings.gradle # Gradle settings
|
||||
├── ios/ # iOS native code
|
||||
│ ├── NeoScan_Physician/ # iOS app
|
||||
│ │ ├── AppDelegate.swift # App delegate
|
||||
│ │ ├── Images.xcassets/ # Image assets
|
||||
│ │ │ ├── AppIcon.appiconset/ # App icons
|
||||
│ │ │ └── Contents.json # Asset catalog
|
||||
│ │ ├── Info.plist # App info
|
||||
│ │ ├── LaunchScreen.storyboard # Launch screen
|
||||
│ │ └── PrivacyInfo.xcprivacy # Privacy info
|
||||
│ ├── NeoScan_Physician.xcodeproj/ # Xcode project
|
||||
│ │ ├── project.pbxproj # Project file
|
||||
│ │ └── xcshareddata/ # Shared data
|
||||
│ │ └── xcschemes/ # Build schemes
|
||||
│ │ └── NeoScan_Physician.xcscheme
|
||||
│ └── Podfile # CocoaPods configuration
|
||||
├── __tests__/ # Test files
|
||||
│ ├── App.test.tsx # App component tests
|
||||
│ ├── components/ # Component tests
|
||||
│ ├── utils/ # Utility tests
|
||||
│ └── integration/ # Integration tests
|
||||
├── docs/ # Documentation
|
||||
│ ├── README.md # Project overview
|
||||
│ ├── ARCHITECTURE.md # Architecture documentation
|
||||
│ ├── API.md # API documentation
|
||||
│ ├── DEPLOYMENT.md # Deployment guide
|
||||
│ ├── TESTING.md # Testing guidelines
|
||||
│ ├── SECURITY.md # Security guidelines
|
||||
│ └── wireframes/ # UI wireframes
|
||||
├── .gitignore # Git ignore rules
|
||||
├── .eslintrc.js # ESLint configuration
|
||||
├── .prettierrc.js # Prettier configuration
|
||||
├── .watchmanconfig # Watchman configuration
|
||||
├── app.json # App configuration
|
||||
├── babel.config.js # Babel configuration
|
||||
├── jest.config.js # Jest configuration
|
||||
├── metro.config.js # Metro bundler configuration
|
||||
├── package.json # Dependencies and scripts
|
||||
├── package-lock.json # Locked dependencies
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── index.js # React Native entry point
|
||||
├── setup.sh # Unix setup script
|
||||
├── setup.bat # Windows setup script
|
||||
├── README.md # Project README
|
||||
├── PROJECT_STRUCTURE.md # This file
|
||||
└── Gemfile # Ruby dependencies (iOS)
|
||||
```
|
||||
|
||||
## 🏗️ Module Architecture
|
||||
|
||||
### Auth Module
|
||||
**Purpose**: Handles all authentication and authorization functionality
|
||||
- **LoginScreen**: Hospital SSO, credential login, emergency access
|
||||
- **Components**: Login forms, authentication modals
|
||||
- **Services**: Authentication API, token management
|
||||
- **Redux**: Auth state, user session management
|
||||
|
||||
### Dashboard Module
|
||||
**Purpose**: Main ER dashboard with patient monitoring and alerts
|
||||
- **ERDashboardScreen**: Main dashboard with patient list and statistics
|
||||
- **PatientCard**: Individual patient information display
|
||||
- **CriticalAlerts**: High-priority alert notifications
|
||||
- **QuickActions**: Emergency procedure shortcuts
|
||||
- **DepartmentStats**: Real-time department overview
|
||||
|
||||
### PatientCare Module
|
||||
**Purpose**: Comprehensive patient management and medical records
|
||||
- **PatientDetailsScreen**: Complete patient information
|
||||
- **VitalSigns**: Real-time vital signs monitoring
|
||||
- **MedicalHistory**: Patient medical records
|
||||
- **Medications**: Current medication management
|
||||
- **Allergies**: Allergy information and alerts
|
||||
|
||||
### Settings Module
|
||||
**Purpose**: App configuration and user preferences
|
||||
- **SettingsScreen**: User preferences and app settings
|
||||
- **NotificationSettings**: Alert and notification configuration
|
||||
- **SecuritySettings**: Authentication and security options
|
||||
- **AboutScreen**: App information and version details
|
||||
|
||||
## 📁 File Naming Conventions
|
||||
|
||||
### Components
|
||||
- **PascalCase** for all component files
|
||||
- **Suffix with type**: `.tsx` for components, `.ts` for utilities
|
||||
- **Examples**: `LoginScreen.tsx`, `PatientCard.tsx`, `CriticalAlerts.tsx`
|
||||
|
||||
### Hooks
|
||||
- **camelCase** with `use` prefix
|
||||
- **Examples**: `useAuth.ts`, `usePatientList.ts`, `useRealTimeAlerts.ts`
|
||||
|
||||
### Services
|
||||
- **camelCase** with descriptive names
|
||||
- **Suffix with type**: `API.ts`, `Service.ts`
|
||||
- **Examples**: `authAPI.ts`, `patientCareAPI.ts`, `notificationService.ts`
|
||||
|
||||
### Redux
|
||||
- **camelCase** with descriptive suffixes
|
||||
- **Examples**: `authSlice.ts`, `erDashboardSlice.ts`, `patientCareActions.ts`
|
||||
|
||||
### Tests
|
||||
- **Same name as source file** + `.test.ts` or `.test.tsx`
|
||||
- **Examples**: `LoginScreen.test.tsx`, `useAuth.test.ts`, `authSlice.test.ts`
|
||||
|
||||
## 🔧 Configuration Files
|
||||
|
||||
### Theme System
|
||||
- **colors.ts**: Complete color palette with healthcare blue theme
|
||||
- **typography.ts**: Font families, weights, sizes, and spacing
|
||||
- **spacing.ts**: Spacing scale, border radius, breakpoints
|
||||
- **shadows.ts**: Shadow system for elevation
|
||||
- **animations.ts**: Animation durations and easing functions
|
||||
- **theme.ts**: Main theme object combining all design tokens
|
||||
|
||||
### Type Definitions
|
||||
- **auth.ts**: Authentication types and interfaces
|
||||
- **patient.ts**: Patient data and medical record types
|
||||
- **dashboard.ts**: Dashboard and ER management types
|
||||
- **alerts.ts**: Alert and notification types
|
||||
- **common.ts**: Common utility types and interfaces
|
||||
|
||||
### Utilities
|
||||
- **constants.ts**: App constants, API configs, timeouts
|
||||
- **helpers.ts**: Common utility functions and helpers
|
||||
- **validators.ts**: Form validation and data validation
|
||||
- **formatters.ts**: Data formatting and display utilities
|
||||
- **medicalUtils.ts**: Medical-specific utility functions
|
||||
|
||||
## 🎨 Design System
|
||||
|
||||
### Color Palette
|
||||
- **Primary**: #2196F3 (Material Blue)
|
||||
- **Secondary**: #1976D2 (Darker Blue)
|
||||
- **Critical**: #F44336 (Material Red)
|
||||
- **Warning**: #FF9800 (Material Orange)
|
||||
- **Success**: #4CAF50 (Material Green)
|
||||
|
||||
### Component Library
|
||||
- **UI Components**: Basic building blocks (Button, Input, Card, etc.)
|
||||
- **Form Components**: Form-specific components with validation
|
||||
- **Icon Components**: Medical and UI icons
|
||||
- **Layout Components**: Container and layout components
|
||||
|
||||
### Responsive Design
|
||||
- **Mobile First**: Optimized for mobile devices
|
||||
- **Tablet Support**: Enhanced layouts for tablets
|
||||
- **Breakpoints**: Mobile (375px), Tablet (768px), Desktop (1024px)
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
1. **Clone the repository**
|
||||
2. **Run setup script**: `./setup.sh` (Unix) or `setup.bat` (Windows)
|
||||
3. **Start development server**: `npm start`
|
||||
4. **Run on device**: `npm run android` or `npm run ios`
|
||||
|
||||
## 📱 Key Features Implemented
|
||||
|
||||
### ✅ Completed
|
||||
- **Theme System**: Complete design system with healthcare blue theme
|
||||
- **Authentication**: Login screen with SSO and credential options
|
||||
- **ER Dashboard**: Main dashboard with patient cards and statistics
|
||||
- **Patient Cards**: Comprehensive patient information display
|
||||
- **Critical Alerts**: High-priority alert system
|
||||
- **Quick Actions**: Emergency procedure shortcuts
|
||||
- **Department Stats**: Real-time department overview
|
||||
- **Navigation**: Tab-based navigation structure
|
||||
- **Type Safety**: Complete TypeScript implementation
|
||||
|
||||
### 🔄 In Progress
|
||||
- **Redux Store**: State management implementation
|
||||
- **API Integration**: Backend service integration
|
||||
- **Real-time Updates**: WebSocket implementation
|
||||
- **Push Notifications**: Critical alert notifications
|
||||
|
||||
### 📋 Planned
|
||||
- **Patient Details**: Comprehensive patient management screens
|
||||
- **Medical Records**: Complete medical history management
|
||||
- **Settings**: App configuration and preferences
|
||||
- **Offline Support**: Offline data access and sync
|
||||
- **Testing**: Comprehensive test suite
|
||||
- **Documentation**: Complete API and user documentation
|
||||
|
||||
This structure provides a solid foundation for a comprehensive healthcare application with proper separation of concerns, type safety, and modern React Native best practices.
|
||||
|
||||
/*
|
||||
* End of File: PROJECT_STRUCTURE.md
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
344
README.md
344
README.md
@ -1,97 +1,317 @@
|
||||
This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
|
||||
/*
|
||||
* File: README.md
|
||||
* Description: Project documentation and setup instructions
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
# Getting Started
|
||||
# NeoScan Physician App
|
||||
|
||||
> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding.
|
||||
A comprehensive React Native application designed for emergency department physicians to manage patient care, critical alerts, and medical workflows in real-time.
|
||||
|
||||
## Step 1: Start Metro
|
||||
## 🚀 Features
|
||||
|
||||
First, you will need to run **Metro**, the JavaScript build tool for React Native.
|
||||
### 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
|
||||
|
||||
To start the Metro dev server, run the following command from the root of your React Native project:
|
||||
### 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)
|
||||
|
||||
```sh
|
||||
# Using npm
|
||||
npm start
|
||||
### 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
|
||||
|
||||
# OR using Yarn
|
||||
yarn start
|
||||
### 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
|
||||
```
|
||||
|
||||
## Step 2: Build and run your app
|
||||
### 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
|
||||
|
||||
With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app:
|
||||
## 🎨 Design System
|
||||
|
||||
### Android
|
||||
### 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)
|
||||
|
||||
```sh
|
||||
# Using npm
|
||||
npm run android
|
||||
### 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)
|
||||
|
||||
# OR using Yarn
|
||||
yarn android
|
||||
### 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
|
||||
```
|
||||
|
||||
### iOS
|
||||
## 📱 Platform Support
|
||||
|
||||
For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps).
|
||||
### iOS Features
|
||||
- Face ID authentication
|
||||
- Apple Health integration
|
||||
- Siri shortcuts
|
||||
- iOS notifications
|
||||
|
||||
The first time you create a new project, run the Ruby bundler to install CocoaPods itself:
|
||||
### Android Features
|
||||
- Fingerprint authentication
|
||||
- Google Fit integration
|
||||
- Android Auto
|
||||
- Android notifications
|
||||
|
||||
```sh
|
||||
bundle install
|
||||
```
|
||||
## 🔄 Real-time Updates
|
||||
|
||||
Then, and every time you update your native dependencies, run:
|
||||
### 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
|
||||
|
||||
```sh
|
||||
bundle exec pod install
|
||||
```
|
||||
### Data Synchronization
|
||||
- **Server Priority**: Server data overrides local
|
||||
- **Timestamp Comparison**: Latest data wins
|
||||
- **User Confirmation**: Manual resolution for conflicts
|
||||
- **Audit Trail**: All changes tracked
|
||||
|
||||
For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html).
|
||||
## 📋 Development Guidelines
|
||||
|
||||
```sh
|
||||
# Using npm
|
||||
npm run ios
|
||||
### Code Style
|
||||
- **TypeScript**: Strict type checking
|
||||
- **ESLint**: Code quality enforcement
|
||||
- **Prettier**: Code formatting
|
||||
- **Conventional Commits**: Git commit messages
|
||||
|
||||
# OR using Yarn
|
||||
yarn ios
|
||||
```
|
||||
### Component Guidelines
|
||||
- **Single Responsibility**: One component, one purpose
|
||||
- **Reusability**: Shared components in shared/
|
||||
- **Type Safety**: Full TypeScript coverage
|
||||
- **Accessibility**: WCAG 2.1 compliance
|
||||
|
||||
If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.
|
||||
### Performance Guidelines
|
||||
- **Lazy Loading**: Components loaded on demand
|
||||
- **Memoization**: React.memo for expensive components
|
||||
- **Image Optimization**: Compressed and cached images
|
||||
- **Bundle Size**: Minimal dependencies
|
||||
|
||||
This is one way to run your app — you can also build it directly from Android Studio or Xcode.
|
||||
## 🚀 Deployment
|
||||
|
||||
## Step 3: Modify your app
|
||||
### 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
|
||||
|
||||
Now that you have successfully run the app, let's make changes!
|
||||
### 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
|
||||
|
||||
Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh).
|
||||
## 📞 Support
|
||||
|
||||
When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload:
|
||||
### Documentation
|
||||
- **API Documentation**: Complete API reference
|
||||
- **User Guide**: End-user documentation
|
||||
- **Developer Guide**: Technical documentation
|
||||
- **Troubleshooting**: Common issues and solutions
|
||||
|
||||
- **Android**: Press the <kbd>R</kbd> key twice or select **"Reload"** from the **Dev Menu**, accessed via <kbd>Ctrl</kbd> + <kbd>M</kbd> (Windows/Linux) or <kbd>Cmd ⌘</kbd> + <kbd>M</kbd> (macOS).
|
||||
- **iOS**: Press <kbd>R</kbd> in iOS Simulator.
|
||||
### Contact
|
||||
- **Technical Support**: dev-support@neoscan.com
|
||||
- **Emergency Support**: emergency-support@neoscan.com
|
||||
- **Feature Requests**: features@neoscan.com
|
||||
|
||||
## Congratulations! :tada:
|
||||
## 📄 License
|
||||
|
||||
You've successfully run and modified your React Native App. :partying_face:
|
||||
This project is proprietary software developed for healthcare institutions. All rights reserved.
|
||||
|
||||
### Now what?
|
||||
---
|
||||
|
||||
- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
|
||||
- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started).
|
||||
**NeoScan Physician App** - Empowering emergency care with real-time intelligence and seamless workflows.
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
|
||||
|
||||
# Learn More
|
||||
|
||||
To learn more about React Native, take a look at the following resources:
|
||||
|
||||
- [React Native Website](https://reactnative.dev) - learn more about React Native.
|
||||
- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
|
||||
- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
|
||||
- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
|
||||
- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.
|
||||
/*
|
||||
* End of File: README.md
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
618
THEME_FLOW.md
Normal file
618
THEME_FLOW.md
Normal file
@ -0,0 +1,618 @@
|
||||
# Physician App - Theme System & Design Flow
|
||||
|
||||
## 🎨 Design System Overview
|
||||
|
||||
### **Project Theme Structure**
|
||||
```
|
||||
app/theme/
|
||||
├── theme.ts # Main theme configuration
|
||||
├── colors.ts # Color palette definitions
|
||||
├── typography.ts # Font families, sizes, weights
|
||||
├── spacing.ts # Spacing scale and breakpoints
|
||||
├── shadows.ts # Shadow and elevation system
|
||||
└── animations.ts # Animation durations and easing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Color Palette - "Modern Healthcare Blue"
|
||||
|
||||
### **Primary Colors**
|
||||
```typescript
|
||||
Primary: '#2196F3' // Material Blue - Main brand color
|
||||
Secondary: '#1976D2' // Darker Blue - Secondary actions
|
||||
Tertiary: '#E3F2FD' // Very Light Blue - Backgrounds
|
||||
Quaternary: '#0D47A1' // Deep Blue - Accents
|
||||
```
|
||||
|
||||
### **Text Colors**
|
||||
```typescript
|
||||
TextPrimary: '#212121' // Dark Gray - Main text
|
||||
TextSecondary: '#757575' // Medium Gray - Secondary text
|
||||
TextMuted: '#9E9E9E' // Light Gray - Muted text
|
||||
```
|
||||
|
||||
### **Background Colors**
|
||||
```typescript
|
||||
Background: '#FFFFFF' // White - Primary background
|
||||
BackgroundAlt: '#FAFAFA' // Light Gray - Alternative background
|
||||
BackgroundAccent: '#F5F5F5' // Soft Gray - Accent backgrounds
|
||||
```
|
||||
|
||||
### **Status & Feedback Colors**
|
||||
```typescript
|
||||
Success: '#4CAF50' // Material Green - Success states
|
||||
Warning: '#FF9800' // Material Orange - Warning states
|
||||
Error: '#F44336' // Material Red - Error states
|
||||
Info: '#2196F3' // Material Blue - Information states
|
||||
Critical: '#F44336' // Critical alerts and emergencies
|
||||
```
|
||||
|
||||
### **UI Elements**
|
||||
```typescript
|
||||
Border: '#E0E0E0' // Light Gray Border
|
||||
CardBackground: '#FFFFFF' // White - Card backgrounds
|
||||
Shadow: 'rgba(0, 0, 0, 0.1)' // Subtle Gray Shadow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Typography System
|
||||
|
||||
### **Font Families (Roboto)**
|
||||
```typescript
|
||||
// Available Font Families
|
||||
fontFamily: {
|
||||
bold: 'Roboto-Bold',
|
||||
medium: 'Roboto-Medium',
|
||||
regular: 'Roboto-Regular',
|
||||
light: 'Roboto-Light',
|
||||
semibold: 'Roboto-SemiBold',
|
||||
extrabold: 'Roboto-ExtraBold',
|
||||
}
|
||||
```
|
||||
|
||||
### **Font Weights**
|
||||
```typescript
|
||||
fontWeight: {
|
||||
light: '300',
|
||||
regular: '400',
|
||||
medium: '500',
|
||||
bold: '700',
|
||||
}
|
||||
```
|
||||
|
||||
### **Font Sizes**
|
||||
```typescript
|
||||
fontSize: {
|
||||
displayLarge: 32, // Main headings
|
||||
displayMedium: 24, // Section headings
|
||||
displaySmall: 20, // Subsection headings
|
||||
bodyLarge: 16, // Body text
|
||||
bodyMedium: 14, // Secondary text
|
||||
bodySmall: 12, // Captions
|
||||
caption: 10, // Small labels
|
||||
}
|
||||
```
|
||||
|
||||
### **Line Heights & Spacing**
|
||||
```typescript
|
||||
lineHeight: {
|
||||
tight: 1.2, // Headings
|
||||
normal: 1.4, // Body text
|
||||
relaxed: 1.6, // Long text
|
||||
}
|
||||
|
||||
letterSpacing: {
|
||||
tight: -0.5, // Headings
|
||||
normal: 0, // Body text
|
||||
wide: 0.5, // Labels
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📐 Spacing & Layout
|
||||
|
||||
### **Spacing Scale (Base: 4px)**
|
||||
```typescript
|
||||
spacing: {
|
||||
xs: 4, // 4px
|
||||
sm: 8, // 8px
|
||||
md: 16, // 16px
|
||||
lg: 24, // 24px
|
||||
xl: 32, // 32px
|
||||
xxl: 48, // 48px
|
||||
xxxl: 64 // 64px
|
||||
}
|
||||
```
|
||||
|
||||
### **Border Radius**
|
||||
```typescript
|
||||
borderRadius: {
|
||||
small: 4,
|
||||
medium: 8,
|
||||
large: 12,
|
||||
xlarge: 16,
|
||||
round: 50,
|
||||
}
|
||||
```
|
||||
|
||||
### **Breakpoints**
|
||||
```typescript
|
||||
breakpoints: {
|
||||
mobile: 375,
|
||||
tablet: 768,
|
||||
desktop: 1024,
|
||||
largeDesktop: 1440,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Icon System
|
||||
|
||||
### **React Native Vector Icons**
|
||||
|
||||
#### **Available Icon Sets**
|
||||
```typescript
|
||||
// Primary Icon Sets
|
||||
import Icon from 'react-native-vector-icons/Feather'; // Clean, minimal icons
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons'; // Material Design icons
|
||||
import Icon from 'react-native-vector-icons/Ionicons'; // iOS-style icons
|
||||
```
|
||||
|
||||
#### **Icon Usage Guidelines**
|
||||
```typescript
|
||||
// Standard Icon Implementation
|
||||
<Icon
|
||||
name="mail" // Icon name
|
||||
size={20} // Size in pixels
|
||||
color={theme.colors.textSecondary} // Color from theme
|
||||
style={styles.icon} // Additional styling
|
||||
/>
|
||||
|
||||
// Icon Sizes
|
||||
IconSizes: {
|
||||
xs: 12, // Extra small
|
||||
sm: 16, // Small
|
||||
md: 24, // Medium (default)
|
||||
lg: 32, // Large
|
||||
xl: 48 // Extra large
|
||||
}
|
||||
```
|
||||
|
||||
#### **Common Icon Names**
|
||||
```typescript
|
||||
// Navigation Icons
|
||||
'home', 'menu', 'arrow-left', 'arrow-right', 'chevron-down', 'chevron-up'
|
||||
|
||||
// Action Icons
|
||||
'plus', 'minus', 'edit', 'delete', 'save', 'cancel', 'check', 'close'
|
||||
|
||||
// Medical Icons
|
||||
'heart', 'user', 'settings', 'bell', 'search', 'filter', 'calendar'
|
||||
|
||||
// Status Icons
|
||||
'check-circle', 'alert-circle', 'info', 'warning', 'error'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Component Design Rules
|
||||
|
||||
### **Button Design System**
|
||||
```typescript
|
||||
// Button Variants
|
||||
Primary: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
shadowColor: theme.colors.primary,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3
|
||||
}
|
||||
|
||||
Secondary: {
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium
|
||||
}
|
||||
|
||||
Success: {
|
||||
backgroundColor: theme.colors.success,
|
||||
borderColor: theme.colors.success,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
shadowColor: theme.colors.success,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3
|
||||
}
|
||||
|
||||
Critical: {
|
||||
backgroundColor: theme.colors.critical,
|
||||
borderColor: theme.colors.critical,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
shadowColor: theme.colors.critical,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3
|
||||
}
|
||||
```
|
||||
|
||||
### **Input Field Design**
|
||||
```typescript
|
||||
// Input States
|
||||
Default: {
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md
|
||||
}
|
||||
|
||||
Focused: {
|
||||
borderColor: theme.colors.primary,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md
|
||||
}
|
||||
|
||||
Error: {
|
||||
borderColor: theme.colors.critical,
|
||||
backgroundColor: theme.colors.criticalBackground,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md
|
||||
}
|
||||
```
|
||||
|
||||
### **Card Design Rules**
|
||||
```typescript
|
||||
// Card Variants
|
||||
Default: {
|
||||
backgroundColor: theme.colors.cardBackground,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.lg,
|
||||
shadowColor: theme.colors.shadow,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
}
|
||||
|
||||
Elevated: {
|
||||
backgroundColor: theme.colors.cardBackground,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.lg,
|
||||
shadowColor: theme.colors.shadow,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Alert & Status Design
|
||||
|
||||
### **Alert Priority System**
|
||||
```typescript
|
||||
// Critical Alerts
|
||||
Critical: {
|
||||
backgroundColor: theme.colors.criticalBackground,
|
||||
borderColor: theme.colors.critical,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md
|
||||
}
|
||||
|
||||
// Warning Alerts
|
||||
Warning: {
|
||||
backgroundColor: theme.colors.warningBackground,
|
||||
borderColor: theme.colors.warning,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md
|
||||
}
|
||||
|
||||
// Success Alerts
|
||||
Success: {
|
||||
backgroundColor: theme.colors.successBackground,
|
||||
borderColor: theme.colors.success,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md
|
||||
}
|
||||
|
||||
// Info Alerts
|
||||
Info: {
|
||||
backgroundColor: theme.colors.infoBackground,
|
||||
borderColor: theme.colors.info,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md
|
||||
}
|
||||
```
|
||||
|
||||
### **Status Badges**
|
||||
```typescript
|
||||
// Status Badges
|
||||
StatusBadge: {
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
borderRadius: theme.borderRadius.round,
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontWeight: theme.typography.fontWeight.medium
|
||||
}
|
||||
|
||||
// Priority Badges
|
||||
PriorityBadge: {
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.round,
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontWeight: theme.typography.fontWeight.bold
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Screen-Specific Design Rules
|
||||
|
||||
### **Login Screen Design**
|
||||
```typescript
|
||||
// Login Container
|
||||
LoginContainer: {
|
||||
backgroundColor: theme.colors.background,
|
||||
padding: theme.spacing.lg,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
shadowColor: theme.colors.shadow,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
|
||||
// Input Container with Icons
|
||||
InputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderColor: theme.colors.border,
|
||||
borderWidth: 1,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: 2,
|
||||
marginBottom: theme.spacing.md
|
||||
}
|
||||
|
||||
// Input Icon
|
||||
InputIcon: {
|
||||
marginRight: theme.spacing.sm,
|
||||
color: theme.colors.textSecondary
|
||||
}
|
||||
```
|
||||
|
||||
### **Dashboard Screen Design**
|
||||
```typescript
|
||||
// Header Section
|
||||
Header: {
|
||||
backgroundColor: theme.colors.background,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.sm,
|
||||
borderBottomColor: theme.colors.border,
|
||||
borderBottomWidth: 1
|
||||
}
|
||||
|
||||
// Critical Alerts Section
|
||||
CriticalAlerts: {
|
||||
backgroundColor: theme.colors.criticalBackground,
|
||||
borderColor: theme.colors.critical,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.lg,
|
||||
marginHorizontal: theme.spacing.md,
|
||||
marginVertical: theme.spacing.md,
|
||||
shadowColor: theme.colors.critical,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 8,
|
||||
elevation: 4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Animation & Transitions
|
||||
|
||||
### **Animation Rules**
|
||||
```typescript
|
||||
// Animation Durations
|
||||
Durations: {
|
||||
Fast: 150, // Quick interactions
|
||||
Normal: 300, // Standard transitions
|
||||
Slow: 500 // Complex animations
|
||||
}
|
||||
|
||||
// Easing Functions
|
||||
Easing: {
|
||||
Standard: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
|
||||
Deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
|
||||
Acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)'
|
||||
}
|
||||
|
||||
// Transition Types
|
||||
Transitions: {
|
||||
Fade: { opacity: [0, 1], duration: 300 },
|
||||
Slide: { transform: [{ translateY: [20, 0] }], duration: 300 },
|
||||
Scale: { transform: [{ scale: [0.95, 1] }], duration: 200 }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Usage Guidelines
|
||||
|
||||
### **Theme Import**
|
||||
```typescript
|
||||
// Import theme in components
|
||||
import { theme } from '../theme/theme';
|
||||
|
||||
// Usage in StyleSheet
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
padding: theme.spacing.md,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
shadowColor: theme.colors.shadow,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 2
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displayMedium,
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
fontFamily: theme.typography.fontFamily.bold
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### **Icon Implementation**
|
||||
```typescript
|
||||
// Import icon
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// Usage in components
|
||||
<Icon
|
||||
name="mail"
|
||||
size={20}
|
||||
color={theme.colors.textSecondary}
|
||||
style={styles.icon}
|
||||
/>
|
||||
```
|
||||
|
||||
### **Font Usage**
|
||||
```typescript
|
||||
// Typography with font families
|
||||
const styles = StyleSheet.create({
|
||||
heading: {
|
||||
fontSize: theme.typography.fontSize.displayMedium,
|
||||
fontWeight: theme.typography.fontWeight.bold,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
lineHeight: theme.typography.lineHeight.tight
|
||||
},
|
||||
body: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontWeight: theme.typography.fontWeight.regular,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
lineHeight: theme.typography.lineHeight.normal
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration Files
|
||||
|
||||
### **TypeScript Declaration**
|
||||
```typescript
|
||||
// app/types/react-native-vector-icons.d.ts
|
||||
declare module 'react-native-vector-icons/Feather' {
|
||||
import { Component } from 'react';
|
||||
import { TextProps } from 'react-native';
|
||||
|
||||
interface IconProps extends TextProps {
|
||||
name: string;
|
||||
size?: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export default class Icon extends Component<IconProps> {}
|
||||
}
|
||||
```
|
||||
|
||||
### **Font Configuration**
|
||||
```json
|
||||
// react-native.config.js
|
||||
module.exports = {
|
||||
assets: ['./app/assets/fonts/'],
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Best Practices
|
||||
|
||||
### **Design Consistency**
|
||||
- ✅ Always use theme colors instead of hardcoded values
|
||||
- ✅ Use consistent spacing from the spacing scale
|
||||
- ✅ Apply proper typography hierarchy
|
||||
- ✅ Use appropriate icon sizes and colors
|
||||
- ✅ Maintain consistent border radius values
|
||||
|
||||
### **Performance**
|
||||
- ✅ Use StyleSheet.create for all styles
|
||||
- ✅ Minimize inline styles
|
||||
- ✅ Use theme constants for repeated values
|
||||
- ✅ Optimize icon usage with proper sizing
|
||||
|
||||
### **Accessibility**
|
||||
- ✅ Maintain proper color contrast ratios
|
||||
- ✅ Use semantic color names
|
||||
- ✅ Provide adequate touch targets (44px minimum)
|
||||
- ✅ Support dynamic text sizing
|
||||
|
||||
### **Maintainability**
|
||||
- ✅ Keep theme centralized and well-documented
|
||||
- ✅ Use semantic naming for colors and spacing
|
||||
- ✅ Document any custom theme extensions
|
||||
- ✅ Version control theme changes
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Reference
|
||||
|
||||
### **Common Theme Values**
|
||||
```typescript
|
||||
// Colors
|
||||
theme.colors.primary // #2196F3
|
||||
theme.colors.textPrimary // #212121
|
||||
theme.colors.background // #FFFFFF
|
||||
theme.colors.critical // #F44336
|
||||
|
||||
// Spacing
|
||||
theme.spacing.md // 16px
|
||||
theme.spacing.lg // 24px
|
||||
theme.spacing.xl // 32px
|
||||
|
||||
// Typography
|
||||
theme.typography.fontSize.bodyLarge // 16px
|
||||
theme.typography.fontWeight.bold // 700
|
||||
theme.typography.fontFamily.bold // 'Roboto-Bold'
|
||||
|
||||
// Border Radius
|
||||
theme.borderRadius.medium // 8px
|
||||
theme.borderRadius.large // 12px
|
||||
theme.borderRadius.round // 50px
|
||||
```
|
||||
|
||||
### **Icon Quick Reference**
|
||||
```typescript
|
||||
// Common Icons
|
||||
<Icon name="mail" size={20} color={theme.colors.textSecondary} />
|
||||
<Icon name="lock" size={20} color={theme.colors.textSecondary} />
|
||||
<Icon name="eye" size={22} color={theme.colors.textSecondary} />
|
||||
<Icon name="eye-off" size={22} color={theme.colors.textSecondary} />
|
||||
<Icon name="check" size={24} color={theme.colors.success} />
|
||||
<Icon name="alert-circle" size={24} color={theme.colors.critical} />
|
||||
```
|
||||
|
||||
This comprehensive theme system ensures consistency, accessibility, and maintainability across the Physician App while providing a modern healthcare-focused design experience.
|
||||
@ -1,6 +1,7 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "com.facebook.react"
|
||||
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
@ -117,3 +118,5 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
|
||||
|
||||
BIN
android/app/src/main/assets/fonts/Roboto-Black.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Bold.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Light.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Medium.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Regular.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-SemiBold.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-SemiBold.ttf
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
37
android/link-assets-manifest.json
Normal file
37
android/link-assets-manifest.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"migIndex": 1,
|
||||
"data": [
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-Black.ttf",
|
||||
"sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-Bold.ttf",
|
||||
"sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-ExtraBold.ttf",
|
||||
"sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-ExtraLight.ttf",
|
||||
"sha1": "df556e64732e5c272349e13cb5f87591a1ae779b"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-Light.ttf",
|
||||
"sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-Medium.ttf",
|
||||
"sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-Regular.ttf",
|
||||
"sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400"
|
||||
},
|
||||
{
|
||||
"path": "app/assets/fonts/Roboto-SemiBold.ttf",
|
||||
"sha1": "9ca139684fe902c8310dd82991648376ac9838db"
|
||||
}
|
||||
]
|
||||
}
|
||||
224
app/App.tsx
Normal file
224
app/App.tsx
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* File: App.tsx
|
||||
* Description: Main application component with navigation setup
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { StatusBar } from 'react-native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native';
|
||||
import { theme } from './theme/theme';
|
||||
import { RootStackNavigator, setNavigationRef } from './navigation';
|
||||
import { StoreProvider } from './store/Provider';
|
||||
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)
|
||||
*
|
||||
* Purpose: Inner component that uses Redux hooks for authentication state
|
||||
*
|
||||
* Features:
|
||||
* 1. Connect to Redux store for authentication state
|
||||
* 2. Set up navigation container with global reference
|
||||
* 3. Configure status bar appearance
|
||||
* 4. Render the main app navigator based on authentication state
|
||||
*
|
||||
* Navigation Flow:
|
||||
* 1. App starts → Check Redux authentication status
|
||||
* 2. If not authenticated → Show LoginScreen
|
||||
* 3. If authenticated → Show MainTabNavigator (dashboard)
|
||||
*/
|
||||
function AppContent() {
|
||||
// ============================================================================
|
||||
// REDUX STATE
|
||||
// ============================================================================
|
||||
|
||||
// Get authentication state from Redux
|
||||
const isAuthenticated = useAppSelector(selectIsAuthenticated);
|
||||
|
||||
// Navigation reference for programmatic navigation
|
||||
const navigationRef = useRef<NavigationContainerRef<any> | null>(null);
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
// Set up navigation reference for global access
|
||||
React.useEffect(() => {
|
||||
setNavigationRef(navigationRef);
|
||||
}, []);
|
||||
|
||||
// ============================================================================
|
||||
// RENDER SECTION
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<NavigationContainer ref={navigationRef}>
|
||||
<StatusBar
|
||||
barStyle="dark-content" // Dark text on light background
|
||||
backgroundColor={theme.colors.background} // Status bar background color
|
||||
/>
|
||||
<RootStackNavigator isAuthenticated={isAuthenticated} />
|
||||
<Toast
|
||||
position='bottom'
|
||||
bottomOffset={20}
|
||||
/>
|
||||
</NavigationContainer>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* App Component (Root Component)
|
||||
*
|
||||
* Purpose: Root component that wraps the entire application with Redux Provider
|
||||
*
|
||||
* Features:
|
||||
* 1. Provide Redux store context
|
||||
* 2. Wrap the main app content
|
||||
* 3. Enable Redux state management throughout the app
|
||||
*/
|
||||
export default function App() {
|
||||
return (
|
||||
<StoreProvider>
|
||||
<AppContent />
|
||||
</StoreProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: App.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
BIN
app/assets/fonts/Roboto-Black.ttf
Normal file
BIN
app/assets/fonts/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-Bold.ttf
Normal file
BIN
app/assets/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-ExtraBold.ttf
Normal file
BIN
app/assets/fonts/Roboto-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-ExtraLight.ttf
Normal file
BIN
app/assets/fonts/Roboto-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-Light.ttf
Normal file
BIN
app/assets/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-Medium.ttf
Normal file
BIN
app/assets/fonts/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-Regular.ttf
Normal file
BIN
app/assets/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/Roboto-SemiBold.ttf
Normal file
BIN
app/assets/fonts/Roboto-SemiBold.ttf
Normal file
Binary file not shown.
BIN
app/assets/images/default-avatar.png
Normal file
BIN
app/assets/images/default-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
app/assets/images/hospital-logo.png
Normal file
BIN
app/assets/images/hospital-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
634
app/modules/Auth/components/signup/DocumentUploadStep.tsx
Normal file
634
app/modules/Auth/components/signup/DocumentUploadStep.tsx
Normal file
@ -0,0 +1,634 @@
|
||||
/*
|
||||
* File: DocumentUploadStep.tsx
|
||||
* Description: Document upload step component for signup flow with image picker
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
Image,
|
||||
Alert,
|
||||
PermissionsAndroid,
|
||||
Platform,
|
||||
KeyboardAvoidingView,
|
||||
} from 'react-native';
|
||||
import {
|
||||
launchImageLibrary,
|
||||
launchCamera,
|
||||
ImagePickerResponse,
|
||||
MediaType,
|
||||
} from 'react-native-image-picker';
|
||||
import { theme } from '../../../../theme/theme';
|
||||
import { DocumentUploadStepProps } from '../../types/signup';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
import { showError, showSuccess } from '../../../../shared/utils/toast';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* ImageData Interface
|
||||
*
|
||||
* Purpose: Defines the structure for image data
|
||||
*/
|
||||
interface ImageData {
|
||||
uri: string;
|
||||
name: string;
|
||||
type: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DOCUMENT UPLOAD STEP COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* DocumentUploadStep Component
|
||||
*
|
||||
* Purpose: Fourth step of signup flow - document upload with image picker
|
||||
*
|
||||
* Features:
|
||||
* - Camera and gallery image selection
|
||||
* - Image preview with file details
|
||||
* - Real-time file size and type display
|
||||
* - Permission handling for camera
|
||||
* - Modern UI with proper header alignment
|
||||
* - Continue button with loading state
|
||||
* - Back navigation
|
||||
*/
|
||||
const DocumentUploadStep: React.FC<DocumentUploadStepProps> = ({
|
||||
onContinue,
|
||||
onBack,
|
||||
data,
|
||||
isLoading,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
const [selectedImage, setSelectedImage] = useState<ImageData | null>(
|
||||
data.id_photo_url ? {
|
||||
uri: data.id_photo_url,
|
||||
name: 'uploaded_document.jpg',
|
||||
type: 'image/jpeg',
|
||||
} : null
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// PERMISSION HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Request Camera Permission
|
||||
*
|
||||
* Purpose: Request camera permission for Android devices
|
||||
*
|
||||
* @returns Promise<boolean> - Whether permission was granted
|
||||
*/
|
||||
const requestCameraPermission = async (): Promise<boolean> => {
|
||||
if (Platform.OS === 'android') {
|
||||
try {
|
||||
const granted = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.CAMERA,
|
||||
{
|
||||
title: 'Camera Permission',
|
||||
message: 'This app needs camera permission to capture images.',
|
||||
buttonNeutral: 'Ask Me Later',
|
||||
buttonNegative: 'Cancel',
|
||||
buttonPositive: 'OK',
|
||||
}
|
||||
);
|
||||
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
||||
} catch (err) {
|
||||
console.warn('Camera permission error:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// IMAGE PICKER HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle Image Picker Selection
|
||||
*
|
||||
* Purpose: Show options for camera or gallery selection
|
||||
*/
|
||||
const handleImagePicker = () => {
|
||||
Alert.alert(
|
||||
'Select Image',
|
||||
'Choose how you want to upload your document',
|
||||
[
|
||||
{
|
||||
text: 'Camera',
|
||||
onPress: () => handleCameraCapture(),
|
||||
},
|
||||
{
|
||||
text: 'Gallery',
|
||||
onPress: () => handleGalleryPicker(),
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Camera Capture
|
||||
*
|
||||
* Purpose: Launch camera to capture image
|
||||
*/
|
||||
const handleCameraCapture = async () => {
|
||||
const hasPermission = await requestCameraPermission();
|
||||
|
||||
if (!hasPermission) {
|
||||
showError('Permission Error', 'Camera permission is required to capture images.');
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
mediaType: 'photo' as MediaType,
|
||||
maxWidth: 2000,
|
||||
maxHeight: 2000,
|
||||
includeBase64: false,
|
||||
};
|
||||
|
||||
launchCamera(options, (response: ImagePickerResponse) => {
|
||||
if (response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.errorMessage) {
|
||||
showError('Camera Error', response.errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.assets && response.assets[0]) {
|
||||
const asset = response.assets[0];
|
||||
const imageData: ImageData = {
|
||||
uri: asset.uri!,
|
||||
name: asset.fileName || `document_${Date.now()}.jpg`,
|
||||
type: asset.type || 'image/jpeg',
|
||||
size: asset.fileSize,
|
||||
};
|
||||
|
||||
setSelectedImage(imageData);
|
||||
showSuccess('Success', 'Document captured successfully!');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Gallery Picker
|
||||
*
|
||||
* Purpose: Launch image library to select image
|
||||
*/
|
||||
const handleGalleryPicker = () => {
|
||||
const options = {
|
||||
mediaType: 'photo' as MediaType,
|
||||
maxWidth: 2000,
|
||||
maxHeight: 2000,
|
||||
includeBase64: false,
|
||||
};
|
||||
|
||||
launchImageLibrary(options, (response: ImagePickerResponse) => {
|
||||
if (response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.errorMessage) {
|
||||
showError('Gallery Error', response.errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.assets && response.assets[0]) {
|
||||
const asset = response.assets[0];
|
||||
const imageData: ImageData = {
|
||||
uri: asset.uri!,
|
||||
name: asset.fileName || `document_${Date.now()}.jpg`,
|
||||
type: asset.type || 'image/jpeg',
|
||||
size: asset.fileSize,
|
||||
};
|
||||
|
||||
setSelectedImage(imageData);
|
||||
showSuccess('Success', 'Document selected from gallery!');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Format File Size
|
||||
*
|
||||
* Purpose: Convert bytes to human readable format
|
||||
*
|
||||
* @param bytes - File size in bytes
|
||||
* @returns Formatted file size string
|
||||
*/
|
||||
const formatFileSize = (bytes?: number): string => {
|
||||
if (!bytes) return '';
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get File Type Display
|
||||
*
|
||||
* Purpose: Get display name for file type
|
||||
*
|
||||
* @param type - MIME type
|
||||
* @returns Display name for file type
|
||||
*/
|
||||
const getFileTypeDisplay = (type: string): string => {
|
||||
if (type.includes('jpeg') || type.includes('jpg')) return 'JPEG';
|
||||
if (type.includes('png')) return 'PNG';
|
||||
if (type.includes('gif')) return 'GIF';
|
||||
if (type.includes('webp')) return 'WebP';
|
||||
return 'Image';
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle Continue
|
||||
*
|
||||
* Purpose: Proceed to next step with selected image
|
||||
*/
|
||||
const handleContinue = () => {
|
||||
if (!selectedImage) {
|
||||
showError('Validation Error', 'Please upload a document to continue.');
|
||||
return;
|
||||
}
|
||||
|
||||
onContinue(selectedImage.uri);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Remove Image
|
||||
*
|
||||
* Purpose: Remove selected image
|
||||
*/
|
||||
const handleRemoveImage = () => {
|
||||
setSelectedImage(null);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
||||
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.headerContent}>
|
||||
<Text style={styles.title}>Upload Document</Text>
|
||||
<Text style={styles.subtitle}>Step 4 of 5</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.headerSpacer} />
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Upload your ID document</Text>
|
||||
<Text style={styles.description}>
|
||||
Please upload a clear photo of your government-issued ID for verification.
|
||||
</Text>
|
||||
|
||||
{/* Document Upload Area */}
|
||||
<TouchableOpacity
|
||||
style={styles.uploadContainer}
|
||||
onPress={handleImagePicker}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<View style={styles.uploadContent}>
|
||||
{selectedImage ? (
|
||||
<View style={styles.imagePreviewContainer}>
|
||||
<Image source={{ uri: selectedImage.uri }} style={styles.imagePreview} />
|
||||
<TouchableOpacity
|
||||
style={styles.imageOverlay}
|
||||
onPress={handleRemoveImage}
|
||||
>
|
||||
<Icon name="x" size={20} color={theme.colors.background} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.uploadedText}>Document Uploaded</Text>
|
||||
<Text style={styles.fileName}>{selectedImage.name}</Text>
|
||||
<View style={styles.fileDetails}>
|
||||
<Text style={styles.fileType}>{getFileTypeDisplay(selectedImage.type)}</Text>
|
||||
{selectedImage.size && (
|
||||
<Text style={styles.fileSize}> • {formatFileSize(selectedImage.size)}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Icon name="image" size={48} color={theme.colors.textSecondary} />
|
||||
<Text style={styles.uploadText}>Tap to upload document</Text>
|
||||
<Text style={styles.uploadSubtext}>JPG, PNG supported</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{selectedImage && (
|
||||
<TouchableOpacity
|
||||
style={styles.changeButton}
|
||||
onPress={handleImagePicker}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Text style={styles.changeButtonText}>Change Document</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Continue Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.continueButton,
|
||||
(!selectedImage || isLoading) && styles.continueButtonDisabled,
|
||||
]}
|
||||
onPress={handleContinue}
|
||||
disabled={!selectedImage || isLoading}
|
||||
>
|
||||
<Text style={[
|
||||
styles.continueButtonText,
|
||||
(!selectedImage || isLoading) && styles.continueButtonTextDisabled,
|
||||
]}>
|
||||
{isLoading ? 'Processing...' : 'Continue'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Scroll view
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Scroll content
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: theme.spacing.xl,
|
||||
paddingBottom: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Back button
|
||||
backButton: {
|
||||
padding: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
},
|
||||
|
||||
// Header content
|
||||
headerContent: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Header spacer
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Content section
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Section title
|
||||
sectionTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Description
|
||||
description: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Upload container
|
||||
uploadContainer: {
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderWidth: 2,
|
||||
borderColor: theme.colors.border,
|
||||
borderStyle: 'dashed',
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.xl,
|
||||
marginBottom: theme.spacing.md,
|
||||
minHeight: 200,
|
||||
},
|
||||
|
||||
// Upload content
|
||||
uploadContent: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Image preview container
|
||||
imagePreviewContainer: {
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
|
||||
// Image preview
|
||||
imagePreview: {
|
||||
width: '100%',
|
||||
height: 150,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
marginBottom: theme.spacing.sm,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
|
||||
// Image overlay (remove button)
|
||||
imageOverlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 20,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||
borderRadius: 17,
|
||||
width: 34,
|
||||
height: 34,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Upload text
|
||||
uploadText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginTop: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Upload subtext
|
||||
uploadSubtext: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginTop: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Uploaded text
|
||||
uploadedText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.success,
|
||||
marginTop: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// File name
|
||||
fileName: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginTop: theme.spacing.xs,
|
||||
textAlign: 'center',
|
||||
maxWidth: '80%',
|
||||
},
|
||||
|
||||
// File details
|
||||
fileDetails: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// File type
|
||||
fileType: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
|
||||
// File size
|
||||
fileSize: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
|
||||
// Change button
|
||||
changeButton: {
|
||||
alignSelf: 'center',
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Change button text
|
||||
changeButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Continue button
|
||||
continueButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Continue button disabled
|
||||
continueButtonDisabled: {
|
||||
backgroundColor: theme.colors.border,
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Continue button text
|
||||
continueButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Continue button text disabled
|
||||
continueButtonTextDisabled: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
export default DocumentUploadStep;
|
||||
|
||||
/*
|
||||
* End of File: DocumentUploadStep.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* File: EmailAlreadyRegisteredModal.tsx
|
||||
* Description: Modal for when email is already registered
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Modal,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../../theme/theme';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
interface EmailAlreadyRegisteredModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onGoToLogin: () => void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EMAIL ALREADY REGISTERED MODAL COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* EmailAlreadyRegisteredModal Component
|
||||
*
|
||||
* Purpose: Modal shown when user tries to register with an existing email
|
||||
*
|
||||
* Features:
|
||||
* - Informative message about existing email
|
||||
* - Option to go to login
|
||||
* - Option to close and try different email
|
||||
*/
|
||||
const EmailAlreadyRegisteredModal: React.FC<EmailAlreadyRegisteredModalProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
onGoToLogin,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle Go To Login
|
||||
*
|
||||
* Purpose: Navigate to login screen
|
||||
*/
|
||||
const handleGoToLogin = () => {
|
||||
onClose();
|
||||
onGoToLogin();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Try Different Email
|
||||
*
|
||||
* Purpose: Close modal and allow user to try different email
|
||||
*/
|
||||
const handleTryDifferentEmail = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.modalContainer}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Email Already Registered</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
This email address is already associated with an account.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.message}>
|
||||
It looks like you already have an account with us. You can either:
|
||||
</Text>
|
||||
|
||||
<View style={styles.optionsContainer}>
|
||||
<Text style={styles.optionText}>
|
||||
• Sign in to your existing account
|
||||
</Text>
|
||||
<Text style={styles.optionText}>
|
||||
• Try a different email address
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Actions */}
|
||||
<View style={styles.actions}>
|
||||
<TouchableOpacity
|
||||
style={styles.secondaryButton}
|
||||
onPress={handleTryDifferentEmail}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Try Different Email</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.primaryButton}
|
||||
onPress={handleGoToLogin}
|
||||
>
|
||||
<Text style={styles.primaryButtonText}>Go to Login</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
},
|
||||
modalContainer: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.xl,
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
},
|
||||
header: {
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
textAlign: 'center',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: theme.typography.bodyMedium,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
content: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
message: {
|
||||
fontSize: theme.typography.bodyMedium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
optionsContainer: {
|
||||
marginLeft: theme.spacing.sm,
|
||||
},
|
||||
optionText: {
|
||||
fontSize: theme.typography.bodyMedium,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
actions: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
gap: theme.spacing.md,
|
||||
},
|
||||
secondaryButton: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
fontSize: theme.typography.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
primaryButton: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
shadowColor: theme.colors.primary,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
primaryButtonText: {
|
||||
fontSize: theme.typography.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
});
|
||||
|
||||
export default EmailAlreadyRegisteredModal;
|
||||
|
||||
/*
|
||||
* End of File: EmailAlreadyRegisteredModal.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
370
app/modules/Auth/components/signup/EmailStep.tsx
Normal file
370
app/modules/Auth/components/signup/EmailStep.tsx
Normal file
@ -0,0 +1,370 @@
|
||||
/*
|
||||
* File: EmailStep.tsx
|
||||
* Description: Email step component for signup flow
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../../theme/theme';
|
||||
import { EmailStepProps } from '../../types/signup';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// ============================================================================
|
||||
// EMAIL STEP COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* EmailStep Component
|
||||
*
|
||||
* Purpose: First step of signup flow - email validation
|
||||
*
|
||||
* Features:
|
||||
* - Email input with validation
|
||||
* - Real-time email format checking
|
||||
* - Continue button with loading state
|
||||
* - Back navigation
|
||||
* - Modern header with proper alignment
|
||||
*/
|
||||
const EmailStep: React.FC<EmailStepProps> = ({
|
||||
onContinue,
|
||||
onBack,
|
||||
data,
|
||||
isLoading,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
const [email, setEmail] = useState(data.email || '');
|
||||
const [emailError, setEmailError] = useState('');
|
||||
|
||||
// ============================================================================
|
||||
// VALIDATION FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Validate Email Format
|
||||
*
|
||||
* Purpose: Check if email format is valid
|
||||
*/
|
||||
const validateEmailFormat = (email: string): boolean => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Email Change
|
||||
*
|
||||
* Purpose: Update email and clear errors
|
||||
*/
|
||||
const handleEmailChange = (text: string) => {
|
||||
setEmail(text);
|
||||
setEmailError('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Continue
|
||||
*
|
||||
* Purpose: Validate email and proceed to next step
|
||||
*/
|
||||
const handleContinue = () => {
|
||||
// Clear previous errors
|
||||
setEmailError('');
|
||||
|
||||
// Validate email format
|
||||
if (!email.trim()) {
|
||||
setEmailError('Email is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateEmailFormat(email.trim())) {
|
||||
setEmailError('Please enter a valid email address');
|
||||
return;
|
||||
}
|
||||
|
||||
// Call parent handler
|
||||
onContinue(email.trim());
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
size={24}
|
||||
color={theme.colors.textPrimary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.headerContent}>
|
||||
<Text style={styles.title}>Create Account</Text>
|
||||
<Text style={styles.subtitle}>Step 1 of 5</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.headerSpacer} />
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Enter your email address</Text>
|
||||
<Text style={styles.description}>
|
||||
We'll use this email to create your account and send you important updates.
|
||||
</Text>
|
||||
|
||||
{/* Email Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>Email Address</Text>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
emailError ? styles.inputError : null,
|
||||
]}
|
||||
placeholder="Enter your email address"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={email}
|
||||
onChangeText={handleEmailChange}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
autoComplete="email"
|
||||
textContentType="emailAddress"
|
||||
editable={!isLoading}
|
||||
/>
|
||||
{emailError ? (
|
||||
<Text style={styles.errorText}>{emailError}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Continue Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.continueButton,
|
||||
(!email.trim() || isLoading) ? styles.continueButtonDisabled : null,
|
||||
]}
|
||||
onPress={handleContinue}
|
||||
disabled={!email.trim() || isLoading}
|
||||
>
|
||||
<Text style={[
|
||||
styles.continueButtonText,
|
||||
(!email.trim() || isLoading) ? styles.continueButtonTextDisabled : null,
|
||||
]}>
|
||||
{isLoading ? 'Validating...' : 'Continue'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Additional Info */}
|
||||
<View style={styles.infoContainer}>
|
||||
<Text style={styles.infoText}>
|
||||
By continuing, you agree to our Terms of Service and Privacy Policy.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Scroll view
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Scroll content
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: theme.spacing.xl,
|
||||
paddingBottom: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Back button
|
||||
backButton: {
|
||||
padding: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
},
|
||||
|
||||
// Header content
|
||||
headerContent: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Header spacer
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Content section
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Section title
|
||||
sectionTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Description
|
||||
description: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input container
|
||||
inputContainer: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input label
|
||||
inputLabel: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Input field
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textPrimary,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Input error state
|
||||
inputError: {
|
||||
borderColor: theme.colors.error,
|
||||
},
|
||||
|
||||
// Error text
|
||||
errorText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.error,
|
||||
marginTop: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Continue button
|
||||
continueButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Continue button disabled
|
||||
continueButtonDisabled: {
|
||||
backgroundColor: theme.colors.border,
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Continue button text
|
||||
continueButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Continue button text disabled
|
||||
continueButtonTextDisabled: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
|
||||
// Info container
|
||||
infoContainer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Info text
|
||||
infoText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default EmailStep;
|
||||
|
||||
/*
|
||||
* End of File: EmailStep.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
592
app/modules/Auth/components/signup/HospitalSelectionStep.tsx
Normal file
592
app/modules/Auth/components/signup/HospitalSelectionStep.tsx
Normal file
@ -0,0 +1,592 @@
|
||||
/*
|
||||
* File: HospitalSelectionStep.tsx
|
||||
* Description: Hospital selection step component for signup flow
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
TextInput,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../../theme/theme';
|
||||
import { HospitalSelectionStepProps, Hospital } from '../../types/signup';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// ============================================================================
|
||||
// HOSPITAL SELECTION STEP COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* HospitalSelectionStep Component
|
||||
*
|
||||
* Purpose: Fifth step of signup flow - hospital selection
|
||||
*
|
||||
* Features:
|
||||
* - Hospital list display from Redux state
|
||||
* - Hospital selection with visual feedback
|
||||
* - Search functionality for hospitals
|
||||
* - Loading states and error handling
|
||||
* - Continue button with validation (sticky bottom)
|
||||
* - Back navigation with modern header
|
||||
* - Scrollable hospital list
|
||||
*/
|
||||
const HospitalSelectionStep: React.FC<HospitalSelectionStepProps> = ({
|
||||
onContinue,
|
||||
onBack,
|
||||
data,
|
||||
isLoading,
|
||||
hospitals,
|
||||
hospitalLoading,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
const [selectedHospitalId, setSelectedHospitalId] = useState(data.hospital_id || '');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// ============================================================================
|
||||
// COMPUTED VALUES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Filtered Hospitals
|
||||
*
|
||||
* Purpose: Filter hospitals based on search query
|
||||
*/
|
||||
const filteredHospitals = useMemo(() => {
|
||||
if (!hospitals) return [];
|
||||
|
||||
if (!searchQuery.trim()) return hospitals;
|
||||
|
||||
const query = searchQuery.toLowerCase();
|
||||
return hospitals.filter(hospital =>
|
||||
hospital.hospital_name?.toLowerCase().includes(query)
|
||||
);
|
||||
}, [hospitals, searchQuery]);
|
||||
|
||||
// ============================================================================
|
||||
// HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle Hospital Selection
|
||||
*
|
||||
* Purpose: Select a hospital
|
||||
*/
|
||||
const handleHospitalSelect = (hospitalId: string) => {
|
||||
setSelectedHospitalId(hospitalId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Continue
|
||||
*
|
||||
* Purpose: Validate selection and proceed to next step
|
||||
*/
|
||||
const handleContinue = () => {
|
||||
if (!selectedHospitalId) {
|
||||
// Show error or alert
|
||||
return;
|
||||
}
|
||||
onContinue(selectedHospitalId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Search Input Change
|
||||
*
|
||||
* Purpose: Update search query
|
||||
*/
|
||||
const handleSearchChange = (text: string) => {
|
||||
setSearchQuery(text);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear Search
|
||||
*
|
||||
* Purpose: Clear search query
|
||||
*/
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery('');
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Render Hospital Item
|
||||
*
|
||||
* Purpose: Render individual hospital item
|
||||
*/
|
||||
const renderHospitalItem = ({ item }: { item: Hospital }) => {
|
||||
const isSelected = selectedHospitalId === item.hospital_id;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.hospitalItem,
|
||||
isSelected && styles.hospitalItemSelected,
|
||||
]}
|
||||
onPress={() => handleHospitalSelect(item.hospital_id || '')}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<View style={styles.hospitalContent}>
|
||||
<View style={styles.hospitalInfo}>
|
||||
<Text style={[
|
||||
styles.hospitalName,
|
||||
isSelected && styles.hospitalNameSelected,
|
||||
]}>
|
||||
{item.hospital_name || 'Unknown Hospital'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{isSelected && (
|
||||
<View style={styles.selectedIcon}>
|
||||
<Icon name="check" size={20} color={theme.colors.background} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render Search Input
|
||||
*
|
||||
* Purpose: Render search input field
|
||||
*/
|
||||
const renderSearchInput = () => (
|
||||
<View style={styles.searchContainer}>
|
||||
<View style={styles.searchInputWrapper}>
|
||||
<Icon name="search" size={20} color={theme.colors.textMuted} style={styles.searchIcon} />
|
||||
<TextInput
|
||||
style={styles.searchInput}
|
||||
placeholder="Search hospitals..."
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={searchQuery}
|
||||
onChangeText={handleSearchChange}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
editable={!hospitalLoading}
|
||||
/>
|
||||
{searchQuery.length > 0 && (
|
||||
<TouchableOpacity onPress={handleClearSearch} style={styles.clearButton}>
|
||||
<Icon name="x" size={18} color={theme.colors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* Render Loading State
|
||||
*
|
||||
* Purpose: Show loading indicator while fetching hospitals
|
||||
*/
|
||||
const renderLoadingState = () => (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
||||
<Text style={styles.loadingText}>Loading hospitals...</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* Render Empty State
|
||||
*
|
||||
* Purpose: Show message when no hospitals are available
|
||||
*/
|
||||
const renderEmptyState = () => (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Icon name="building" size={48} color={theme.colors.textMuted} />
|
||||
<Text style={styles.emptyTitle}>
|
||||
{searchQuery ? 'No Hospitals Found' : 'No Hospitals Available'}
|
||||
</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
{searchQuery
|
||||
? 'Try adjusting your search terms or browse all hospitals.'
|
||||
: 'There are no hospitals available at the moment. Please try again later.'
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* Render Search Results Info
|
||||
*
|
||||
* Purpose: Show search results count
|
||||
*/
|
||||
const renderSearchResultsInfo = () => {
|
||||
if (!searchQuery.trim()) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.searchResultsInfo}>
|
||||
<Text style={styles.searchResultsText}>
|
||||
{filteredHospitals.length} hospital{filteredHospitals.length !== 1 ? 's' : ''} found
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
||||
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.headerContent}>
|
||||
<Text style={styles.title}>Create Account</Text>
|
||||
<Text style={styles.subtitle}>Step 5 of 5</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.headerSpacer} />
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Select Your Hospital</Text>
|
||||
<Text style={styles.description}>
|
||||
Choose the hospital where you work or will be practicing.
|
||||
</Text>
|
||||
|
||||
{/* Search Input */}
|
||||
{renderSearchInput()}
|
||||
|
||||
{/* Search Results Info */}
|
||||
{renderSearchResultsInfo()}
|
||||
|
||||
{/* Hospital List */}
|
||||
<View style={styles.hospitalListContainer}>
|
||||
{hospitalLoading ? (
|
||||
renderLoadingState()
|
||||
) : filteredHospitals.length > 0 ? (
|
||||
<FlatList
|
||||
data={filteredHospitals}
|
||||
renderItem={renderHospitalItem}
|
||||
keyExtractor={(item) => item.hospital_id || ''}
|
||||
showsVerticalScrollIndicator={true}
|
||||
contentContainerStyle={styles.hospitalList}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
/>
|
||||
) : (
|
||||
renderEmptyState()
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Sticky Continue Button */}
|
||||
<View style={styles.stickyButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.continueButton,
|
||||
(!selectedHospitalId || isLoading || hospitalLoading)
|
||||
? styles.continueButtonDisabled
|
||||
: null,
|
||||
]}
|
||||
onPress={handleContinue}
|
||||
disabled={!selectedHospitalId || isLoading || hospitalLoading}
|
||||
>
|
||||
<Text style={[
|
||||
styles.continueButtonText,
|
||||
(!selectedHospitalId || isLoading || hospitalLoading)
|
||||
? styles.continueButtonTextDisabled
|
||||
: null,
|
||||
]}>
|
||||
{isLoading ? 'Creating Account...' : 'Create Account'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: theme.spacing.xl,
|
||||
paddingBottom: theme.spacing.lg,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Back button
|
||||
backButton: {
|
||||
padding: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
},
|
||||
|
||||
// Header content
|
||||
headerContent: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Header spacer
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Content section
|
||||
content: {
|
||||
flex: 1,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
paddingTop: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Section title
|
||||
sectionTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Description
|
||||
description: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Search container
|
||||
searchContainer: {
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Search input wrapper
|
||||
searchInputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Search icon
|
||||
searchIcon: {
|
||||
marginRight: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Search input
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
paddingVertical: theme.spacing.md,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Clear button
|
||||
clearButton: {
|
||||
padding: theme.spacing.xs,
|
||||
marginLeft: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Search results info
|
||||
searchResultsInfo: {
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Search results text
|
||||
searchResultsText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Hospital list container
|
||||
hospitalListContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Hospital list
|
||||
hospitalList: {
|
||||
paddingBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Hospital item
|
||||
hospitalItem: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Hospital item selected
|
||||
hospitalItemSelected: {
|
||||
borderColor: theme.colors.primary,
|
||||
backgroundColor: theme.colors.tertiary,
|
||||
},
|
||||
|
||||
// Hospital content
|
||||
hospitalContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
|
||||
// Hospital info
|
||||
hospitalInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Hospital name
|
||||
hospitalName: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Hospital name selected
|
||||
hospitalNameSelected: {
|
||||
color: theme.colors.primary,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
},
|
||||
|
||||
// Selected icon
|
||||
selectedIcon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
backgroundColor: theme.colors.primary,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
// Loading container
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: theme.spacing.xxl,
|
||||
},
|
||||
|
||||
// Loading text
|
||||
loadingText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginTop: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Empty container
|
||||
emptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: theme.spacing.xxl,
|
||||
},
|
||||
|
||||
// Empty title
|
||||
emptyTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginTop: theme.spacing.md,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Empty text
|
||||
emptyText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Sticky button container
|
||||
stickyButtonContainer: {
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
paddingVertical: theme.spacing.lg,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Continue button
|
||||
continueButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Continue button disabled
|
||||
continueButtonDisabled: {
|
||||
backgroundColor: theme.colors.border,
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Continue button text
|
||||
continueButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Continue button text disabled
|
||||
continueButtonTextDisabled: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
export default HospitalSelectionStep;
|
||||
|
||||
/*
|
||||
* End of File: HospitalSelectionStep.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
399
app/modules/Auth/components/signup/NameStep.tsx
Normal file
399
app/modules/Auth/components/signup/NameStep.tsx
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
* File: NameStep.tsx
|
||||
* Description: Name step component for signup flow
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../../theme/theme';
|
||||
import { NameStepProps } from '../../types/signup';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// ============================================================================
|
||||
// NAME STEP COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* NameStep Component
|
||||
*
|
||||
* Purpose: Third step of signup flow - personal information
|
||||
*
|
||||
* Features:
|
||||
* - First name, last name, and username inputs
|
||||
* - Real-time validation with error handling
|
||||
* - Continue button with loading state
|
||||
* - Back navigation with modern header
|
||||
*/
|
||||
const NameStep: React.FC<NameStepProps> = ({
|
||||
onContinue,
|
||||
onBack,
|
||||
data,
|
||||
isLoading,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
const [firstName, setFirstName] = useState(data.first_name || '');
|
||||
const [lastName, setLastName] = useState(data.last_name || '');
|
||||
const [username, setUsername] = useState(data.username || '');
|
||||
const [errors, setErrors] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
username: '',
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// VALIDATION FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Validate Input Fields
|
||||
*
|
||||
* Purpose: Check if all fields are valid
|
||||
*/
|
||||
const validateFields = (): boolean => {
|
||||
const newErrors = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
username: '',
|
||||
};
|
||||
|
||||
if (!firstName.trim()) {
|
||||
newErrors.firstName = 'First name is required';
|
||||
}
|
||||
|
||||
if (!lastName.trim()) {
|
||||
newErrors.lastName = 'Last name is required';
|
||||
}
|
||||
|
||||
if (!username.trim()) {
|
||||
newErrors.username = 'Username is required';
|
||||
} else if (username.length < 3) {
|
||||
newErrors.username = 'Username must be at least 3 characters';
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return !Object.values(newErrors).some(error => error !== '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Continue
|
||||
*
|
||||
* Purpose: Validate fields and proceed to next step
|
||||
*/
|
||||
const handleContinue = () => {
|
||||
if (validateFields()) {
|
||||
onContinue(firstName.trim(), lastName.trim(), username.trim());
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
||||
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.headerContent}>
|
||||
<Text style={styles.title}>Create Account</Text>
|
||||
<Text style={styles.subtitle}>Step 3 of 5</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.headerSpacer} />
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Tell us about yourself</Text>
|
||||
<Text style={styles.description}>
|
||||
Please provide your name and choose a username for your account.
|
||||
</Text>
|
||||
|
||||
{/* First Name Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>First Name</Text>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
errors.firstName ? styles.inputError : null,
|
||||
]}
|
||||
placeholder="Enter your first name"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={firstName}
|
||||
onChangeText={(text) => {
|
||||
setFirstName(text);
|
||||
setErrors(prev => ({ ...prev, firstName: '' }));
|
||||
}}
|
||||
autoCapitalize="words"
|
||||
autoCorrect={false}
|
||||
editable={!isLoading}
|
||||
/>
|
||||
{errors.firstName ? (
|
||||
<Text style={styles.errorText}>{errors.firstName}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Last Name Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>Last Name</Text>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
errors.lastName ? styles.inputError : null,
|
||||
]}
|
||||
placeholder="Enter your last name"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={lastName}
|
||||
onChangeText={(text) => {
|
||||
setLastName(text);
|
||||
setErrors(prev => ({ ...prev, lastName: '' }));
|
||||
}}
|
||||
autoCapitalize="words"
|
||||
autoCorrect={false}
|
||||
editable={!isLoading}
|
||||
/>
|
||||
{errors.lastName ? (
|
||||
<Text style={styles.errorText}>{errors.lastName}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Username Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>Username</Text>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
errors.username ? styles.inputError : null,
|
||||
]}
|
||||
placeholder="Choose a username"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={username}
|
||||
onChangeText={(text) => {
|
||||
setUsername(text);
|
||||
setErrors(prev => ({ ...prev, username: '' }));
|
||||
}}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
editable={!isLoading}
|
||||
/>
|
||||
{errors.username ? (
|
||||
<Text style={styles.errorText}>{errors.username}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Continue Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.continueButton,
|
||||
(!firstName.trim() || !lastName.trim() || !username.trim() || isLoading)
|
||||
? styles.continueButtonDisabled
|
||||
: null,
|
||||
]}
|
||||
onPress={handleContinue}
|
||||
disabled={!firstName.trim() || !lastName.trim() || !username.trim() || isLoading}
|
||||
>
|
||||
<Text style={[
|
||||
styles.continueButtonText,
|
||||
(!firstName.trim() || !lastName.trim() || !username.trim() || isLoading)
|
||||
? styles.continueButtonTextDisabled
|
||||
: null,
|
||||
]}>
|
||||
{isLoading ? 'Validating...' : 'Continue'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Scroll view
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Scroll content
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: theme.spacing.xl,
|
||||
paddingBottom: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Back button
|
||||
backButton: {
|
||||
padding: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
},
|
||||
|
||||
// Header content
|
||||
headerContent: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Header spacer
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Content section
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Section title
|
||||
sectionTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Description
|
||||
description: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input container
|
||||
inputContainer: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input label
|
||||
inputLabel: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Input field
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textPrimary,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Input error state
|
||||
inputError: {
|
||||
borderColor: theme.colors.error,
|
||||
},
|
||||
|
||||
// Error text
|
||||
errorText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.error,
|
||||
marginTop: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Continue button
|
||||
continueButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Continue button disabled
|
||||
continueButtonDisabled: {
|
||||
backgroundColor: theme.colors.border,
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Continue button text
|
||||
continueButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Continue button text disabled
|
||||
continueButtonTextDisabled: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
export default NameStep;
|
||||
|
||||
/*
|
||||
* End of File: NameStep.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
628
app/modules/Auth/components/signup/PasswordStep.tsx
Normal file
628
app/modules/Auth/components/signup/PasswordStep.tsx
Normal file
@ -0,0 +1,628 @@
|
||||
/*
|
||||
* File: PasswordStep.tsx
|
||||
* Description: Password step component for signup flow with comprehensive validation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../../theme/theme';
|
||||
import { PasswordStepProps } from '../../types/signup';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* PasswordRule Interface
|
||||
*
|
||||
* Purpose: Defines the structure for password validation rules
|
||||
*/
|
||||
interface PasswordRule {
|
||||
id: string;
|
||||
label: string;
|
||||
validator: (password: string) => boolean;
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PASSWORD STEP COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* PasswordStep Component
|
||||
*
|
||||
* Purpose: Second step of signup flow - password creation with comprehensive validation
|
||||
*
|
||||
* Features:
|
||||
* - Password input with visibility toggle
|
||||
* - Comprehensive password validation rules
|
||||
* - Real-time password strength checking
|
||||
* - Visual feedback for each requirement
|
||||
* - Continue button with loading state
|
||||
* - Back navigation with modern header
|
||||
*/
|
||||
const PasswordStep: React.FC<PasswordStepProps> = ({
|
||||
onContinue,
|
||||
onBack,
|
||||
data,
|
||||
isLoading,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
const [password, setPassword] = useState(data.password || '');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [confirmPasswordError, setConfirmPasswordError] = useState('');
|
||||
const [isPasswordVisible, setPasswordVisible] = useState(false);
|
||||
const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false);
|
||||
|
||||
// Password validation rules
|
||||
const [passwordRules, setPasswordRules] = useState<PasswordRule[]>([
|
||||
{
|
||||
id: 'length',
|
||||
label: 'At least 8 characters',
|
||||
validator: (pwd: string) => pwd.length >= 8,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'uppercase',
|
||||
label: 'One uppercase letter',
|
||||
validator: (pwd: string) => /[A-Z]/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'lowercase',
|
||||
label: 'One lowercase letter',
|
||||
validator: (pwd: string) => /[a-z]/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'number',
|
||||
label: 'One number',
|
||||
validator: (pwd: string) => /\d/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'special',
|
||||
label: 'One special character',
|
||||
validator: (pwd: string) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'match',
|
||||
label: 'Passwords match',
|
||||
validator: (pwd: string) => pwd === confirmPassword && confirmPassword.length > 0,
|
||||
isValid: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useEffect for password validation
|
||||
*
|
||||
* Purpose: Update password rules when password or confirm password changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
updatePasswordRules(password);
|
||||
}, [password, confirmPassword]);
|
||||
|
||||
// ============================================================================
|
||||
// VALIDATION FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Update Password Rules
|
||||
*
|
||||
* Purpose: Update password validation rules based on current password and confirm password
|
||||
*
|
||||
* @param pwd - Current password value
|
||||
*/
|
||||
const updatePasswordRules = (pwd: string) => {
|
||||
setPasswordRules(prevRules =>
|
||||
prevRules.map(rule => {
|
||||
if (rule.id === 'match') {
|
||||
return {
|
||||
...rule,
|
||||
isValid: pwd === confirmPassword && confirmPassword.length > 0,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...rule,
|
||||
isValid: rule.validator(pwd),
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate Password
|
||||
*
|
||||
* Purpose: Check if all password requirements are met
|
||||
*
|
||||
* @param pwd - Password to validate
|
||||
* @returns boolean indicating if password meets all requirements
|
||||
*/
|
||||
const validatePassword = (pwd: string): boolean => {
|
||||
return passwordRules.every(rule => rule.isValid);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Password Change
|
||||
*
|
||||
* Purpose: Update password and clear errors
|
||||
*/
|
||||
const handlePasswordChange = (text: string) => {
|
||||
setPassword(text);
|
||||
setPasswordError('');
|
||||
// Clear confirm password error if passwords now match
|
||||
if (confirmPassword && text === confirmPassword) {
|
||||
setConfirmPasswordError('');
|
||||
}
|
||||
// Update password rules to reflect the match status
|
||||
updatePasswordRules(text);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Confirm Password Change
|
||||
*
|
||||
* Purpose: Update confirm password and validate match
|
||||
*/
|
||||
const handleConfirmPasswordChange = (text: string) => {
|
||||
setConfirmPassword(text);
|
||||
setConfirmPasswordError('');
|
||||
|
||||
// Check if passwords match
|
||||
if (text && text !== password) {
|
||||
setConfirmPasswordError('Passwords do not match');
|
||||
}
|
||||
|
||||
// Update password rules to reflect the match status
|
||||
updatePasswordRules(password);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Continue
|
||||
*
|
||||
* Purpose: Validate password and proceed to next step
|
||||
*/
|
||||
const handleContinue = () => {
|
||||
// Clear previous errors
|
||||
setPasswordError('');
|
||||
setConfirmPasswordError('');
|
||||
|
||||
// Validate password
|
||||
if (!password.trim()) {
|
||||
setPasswordError('Password is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validatePassword(password.trim())) {
|
||||
setPasswordError('Please meet all password requirements');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate confirm password
|
||||
if (!confirmPassword.trim()) {
|
||||
setConfirmPasswordError('Please confirm your password');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.trim() !== confirmPassword.trim()) {
|
||||
setConfirmPasswordError('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
// Call parent handler
|
||||
onContinue(password.trim());
|
||||
};
|
||||
|
||||
/**
|
||||
* Render Password Rule
|
||||
*
|
||||
* Purpose: Render individual password validation rule
|
||||
*
|
||||
* @param rule - Password rule to render
|
||||
* @returns JSX element for the rule
|
||||
*/
|
||||
const renderPasswordRule = (rule: PasswordRule) => (
|
||||
<View key={rule.id} style={styles.ruleContainer}>
|
||||
<View style={[styles.checkbox, rule.isValid && styles.checkboxChecked]}>
|
||||
{rule.isValid && (
|
||||
<Icon name="check" size={12} color={theme.colors.background} />
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.ruleText, rule.isValid && styles.ruleTextValid]}>
|
||||
{rule.label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
||||
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.headerContent}>
|
||||
<Text style={styles.title}>Create Account</Text>
|
||||
<Text style={styles.subtitle}>Step 2 of 5</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.headerSpacer} />
|
||||
</View>
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.sectionTitle}>Create a strong password</Text>
|
||||
<Text style={styles.description}>
|
||||
Choose a password that meets all the security requirements below.
|
||||
</Text>
|
||||
|
||||
{/* Password Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>Password</Text>
|
||||
<View style={[
|
||||
styles.inputWrapper,
|
||||
passwordError ? styles.inputWrapperError : null,
|
||||
]}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Enter your password"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={password}
|
||||
onChangeText={handlePasswordChange}
|
||||
secureTextEntry={!isPasswordVisible}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
editable={!isLoading}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => setPasswordVisible(!isPasswordVisible)}
|
||||
style={styles.eyeIcon}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Icon
|
||||
name={isPasswordVisible ? 'eye-off' : 'eye'}
|
||||
size={22}
|
||||
color={theme.colors.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{passwordError ? (
|
||||
<Text style={styles.errorText}>{passwordError}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Confirm Password Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>Confirm Password</Text>
|
||||
<View style={[
|
||||
styles.inputWrapper,
|
||||
confirmPasswordError ? styles.inputWrapperError : null,
|
||||
]}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Confirm your password"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
value={confirmPassword}
|
||||
onChangeText={handleConfirmPasswordChange}
|
||||
secureTextEntry={!isConfirmPasswordVisible}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
editable={!isLoading}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => setConfirmPasswordVisible(!isConfirmPasswordVisible)}
|
||||
style={styles.eyeIcon}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Icon
|
||||
name={isConfirmPasswordVisible ? 'eye-off' : 'eye'}
|
||||
size={22}
|
||||
color={theme.colors.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{confirmPasswordError ? (
|
||||
<Text style={styles.errorText}>{confirmPasswordError}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Password Requirements */}
|
||||
<View style={styles.requirementsContainer}>
|
||||
<Text style={styles.requirementsTitle}>Password Requirements:</Text>
|
||||
<View style={styles.rulesGrid}>
|
||||
{passwordRules.map(renderPasswordRule)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Continue Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.continueButton,
|
||||
(!password.trim() || !confirmPassword.trim() || isLoading || !validatePassword(password) || password !== confirmPassword) ? styles.continueButtonDisabled : null,
|
||||
]}
|
||||
onPress={handleContinue}
|
||||
disabled={!password.trim() || !confirmPassword.trim() || isLoading || !validatePassword(password) || password !== confirmPassword}
|
||||
>
|
||||
<Text style={[
|
||||
styles.continueButtonText,
|
||||
(!password.trim() || !confirmPassword.trim() || isLoading || !validatePassword(password) || password !== confirmPassword) ? styles.continueButtonTextDisabled : null,
|
||||
]}>
|
||||
{isLoading ? 'Processing...' : 'Continue'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Scroll view
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Scroll content
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: theme.spacing.xl,
|
||||
paddingBottom: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Back button
|
||||
backButton: {
|
||||
padding: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
},
|
||||
|
||||
// Header content
|
||||
headerContent: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Header spacer
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Content section
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Section title
|
||||
sectionTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Description
|
||||
description: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input container
|
||||
inputContainer: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input label
|
||||
inputLabel: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Input wrapper
|
||||
inputWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Input field
|
||||
input: {
|
||||
flex: 1,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Eye icon
|
||||
eyeIcon: {
|
||||
padding: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Input wrapper error state
|
||||
inputWrapperError: {
|
||||
borderColor: theme.colors.error,
|
||||
},
|
||||
|
||||
// Error text
|
||||
errorText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.error,
|
||||
marginTop: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Requirements container
|
||||
requirementsContainer: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
padding: theme.spacing.md,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
},
|
||||
|
||||
// Requirements title
|
||||
requirementsTitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Rules grid
|
||||
rulesGrid: {
|
||||
gap: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Rule container
|
||||
ruleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Checkbox
|
||||
checkbox: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 4,
|
||||
borderWidth: 2,
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
marginRight: theme.spacing.sm,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
// Checkbox checked
|
||||
checkboxChecked: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderColor: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Rule text
|
||||
ruleText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Rule text valid
|
||||
ruleTextValid: {
|
||||
color: theme.colors.success,
|
||||
},
|
||||
|
||||
// Continue button
|
||||
continueButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Continue button disabled
|
||||
continueButtonDisabled: {
|
||||
backgroundColor: theme.colors.border,
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Continue button text
|
||||
continueButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Continue button text disabled
|
||||
continueButtonTextDisabled: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
export default PasswordStep;
|
||||
|
||||
/*
|
||||
* End of File: PasswordStep.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
20
app/modules/Auth/components/signup/index.ts
Normal file
20
app/modules/Auth/components/signup/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Barrel exports for signup components
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Export all signup step components
|
||||
export { default as EmailStep } from './EmailStep';
|
||||
export { default as PasswordStep } from './PasswordStep';
|
||||
export { default as NameStep } from './NameStep';
|
||||
export { default as DocumentUploadStep } from './DocumentUploadStep';
|
||||
export { default as HospitalSelectionStep } from './HospitalSelectionStep';
|
||||
export { default as EmailAlreadyRegisteredModal } from './EmailAlreadyRegisteredModal';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
79
app/modules/Auth/index.ts
Normal file
79
app/modules/Auth/index.ts
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Main exports for Auth module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Export screens
|
||||
export { default as LoginScreen } from './screens/LoginScreen';
|
||||
export { default as SignUpScreen } from './screens/SignUpScreen';
|
||||
|
||||
// Export navigation
|
||||
export {
|
||||
AuthStackNavigator,
|
||||
AuthStackParamList,
|
||||
AuthNavigationProp,
|
||||
AuthScreenProps,
|
||||
LoginScreenProps,
|
||||
SignUpScreenProps,
|
||||
navigateToLogin,
|
||||
navigateToSignUp,
|
||||
goBack,
|
||||
resetToLogin,
|
||||
resetToSignUp,
|
||||
replaceWithLogin,
|
||||
replaceWithSignUp,
|
||||
navigateToSignUpAndClearStack,
|
||||
navigateToLoginAndClearStack,
|
||||
} from './navigation';
|
||||
|
||||
// Export signup components
|
||||
export {
|
||||
EmailStep,
|
||||
PasswordStep,
|
||||
NameStep,
|
||||
DocumentUploadStep,
|
||||
HospitalSelectionStep,
|
||||
EmailAlreadyRegisteredModal,
|
||||
} from './components/signup';
|
||||
|
||||
// Export services
|
||||
export { authAPI } from './services/signupAPI';
|
||||
|
||||
// Export types
|
||||
export type {
|
||||
SignUpData,
|
||||
SignUpStep,
|
||||
EmailStepProps,
|
||||
PasswordStepProps,
|
||||
NameStepProps,
|
||||
DocumentUploadStepProps,
|
||||
HospitalSelectionStepProps,
|
||||
EmailValidationApiResponse,
|
||||
UsernameValidationApiResponse,
|
||||
HospitalListApiResponse,
|
||||
SignUpApiResponse,
|
||||
Hospital,
|
||||
} from './types/signup';
|
||||
|
||||
// Export Redux
|
||||
export {
|
||||
loginUser,
|
||||
ssoLogin,
|
||||
emergencyAccess,
|
||||
logoutUser,
|
||||
clearError,
|
||||
setBiometricEnabled,
|
||||
setRememberDevice,
|
||||
updateUserProfile,
|
||||
setSessionToken,
|
||||
clearSession,
|
||||
setEmergencyAccess,
|
||||
} from './redux/authSlice';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
101
app/modules/Auth/navigation/AuthStackNavigator.tsx
Normal file
101
app/modules/Auth/navigation/AuthStackNavigator.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* File: AuthStackNavigator.tsx
|
||||
* Description: Stack navigator for authentication screens within the Auth module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
||||
// Import authentication screens
|
||||
import LoginScreen from '../screens/LoginScreen';
|
||||
import SignUpScreen from '../screens/SignUpScreen';
|
||||
|
||||
// Import navigation types
|
||||
import { AuthStackParamList } from './navigationTypes';
|
||||
import { theme } from '../../../theme';
|
||||
|
||||
// Create stack navigator for Auth module
|
||||
const Stack = createStackNavigator<AuthStackParamList>();
|
||||
|
||||
/**
|
||||
* AuthStackNavigator - Manages navigation between authentication screens
|
||||
*
|
||||
* This navigator handles the flow between:
|
||||
* - LoginScreen: Main authentication screen
|
||||
* - SignUpScreen: Multi-step registration process
|
||||
*
|
||||
* Features:
|
||||
* - Clean header styling
|
||||
* - Smooth transitions between screens
|
||||
* - Type-safe navigation parameters
|
||||
*/
|
||||
const AuthStackNavigator: React.FC = () => {
|
||||
return (
|
||||
<Stack.Navigator
|
||||
initialRouteName="Login"
|
||||
screenOptions={{
|
||||
// Header styling for authentication screens
|
||||
headerStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
elevation: 0, // Remove shadow on Android
|
||||
shadowOpacity: 0, // Remove shadow on iOS
|
||||
},
|
||||
headerTitleStyle: {
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
fontSize: 18,
|
||||
color: '#212121',
|
||||
},
|
||||
headerTintColor: '#2196F3', // Back button and title color
|
||||
// headerBackTitleVisible: false, // Hide back title on iOS
|
||||
cardStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
// Smooth transitions
|
||||
transitionSpec: {
|
||||
open: {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 300,
|
||||
},
|
||||
},
|
||||
close: {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Login Screen - Main authentication entry point */}
|
||||
<Stack.Screen
|
||||
name="Login"
|
||||
component={LoginScreen}
|
||||
options={{
|
||||
title: 'Sign In',
|
||||
headerShown: false, // Hide header for login screen
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Sign Up Screen - Multi-step registration process */}
|
||||
<Stack.Screen
|
||||
name="SignUp"
|
||||
component={SignUpScreen}
|
||||
options={{
|
||||
title: 'Create Account',
|
||||
headerShown: false, // Hide header for signup screen
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthStackNavigator;
|
||||
|
||||
/*
|
||||
* End of File: AuthStackNavigator.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
37
app/modules/Auth/navigation/index.ts
Normal file
37
app/modules/Auth/navigation/index.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Barrel exports for Auth module navigation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Export main navigator
|
||||
export { default as AuthStackNavigator } from './AuthStackNavigator';
|
||||
|
||||
// Export navigation types
|
||||
export type {
|
||||
AuthStackParamList,
|
||||
AuthNavigationProp,
|
||||
AuthScreenProps,
|
||||
LoginScreenProps,
|
||||
SignUpScreenProps,
|
||||
} from './navigationTypes';
|
||||
|
||||
// Export navigation utilities
|
||||
export {
|
||||
navigateToLogin,
|
||||
navigateToSignUp,
|
||||
goBack,
|
||||
resetToLogin,
|
||||
resetToSignUp,
|
||||
replaceWithLogin,
|
||||
replaceWithSignUp,
|
||||
navigateToSignUpAndClearStack,
|
||||
navigateToLoginAndClearStack,
|
||||
} from './navigationUtils';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
63
app/modules/Auth/navigation/navigationTypes.ts
Normal file
63
app/modules/Auth/navigation/navigationTypes.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* File: navigationTypes.ts
|
||||
* Description: TypeScript types for Auth module navigation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
/**
|
||||
* AuthStackParamList - Defines the parameter list for Auth stack navigator
|
||||
*
|
||||
* This interface defines all the screens available in the Auth module
|
||||
* and their associated navigation parameters.
|
||||
*/
|
||||
export type AuthStackParamList = {
|
||||
// Login screen - Main authentication entry point
|
||||
Login: undefined;
|
||||
|
||||
// Sign Up screen - Multi-step registration process
|
||||
SignUp: undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* AuthNavigationProp - Type for navigation prop in Auth screens
|
||||
*
|
||||
* This type provides type-safe navigation methods for screens
|
||||
* within the Auth module.
|
||||
*/
|
||||
export type AuthNavigationProp = StackNavigationProp<AuthStackParamList>;
|
||||
|
||||
/**
|
||||
* AuthScreenProps - Base props interface for Auth screens
|
||||
*
|
||||
* This interface provides the common props that all Auth screens
|
||||
* will receive, including navigation and route.
|
||||
*/
|
||||
export interface AuthScreenProps<T extends keyof AuthStackParamList> {
|
||||
navigation: AuthNavigationProp;
|
||||
route: {
|
||||
key: string;
|
||||
name: T;
|
||||
params: AuthStackParamList[T];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* LoginScreenProps - Props for LoginScreen component
|
||||
*/
|
||||
export type LoginScreenProps = AuthScreenProps<'Login'>;
|
||||
|
||||
/**
|
||||
* SignUpScreenProps - Props for SignUpScreen component
|
||||
*/
|
||||
export type SignUpScreenProps = AuthScreenProps<'SignUp'>;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* End of File: navigationTypes.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
107
app/modules/Auth/navigation/navigationUtils.ts
Normal file
107
app/modules/Auth/navigation/navigationUtils.ts
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* File: navigationUtils.ts
|
||||
* Description: Navigation utilities for Auth module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { AuthNavigationProp } from './navigationTypes';
|
||||
|
||||
/**
|
||||
* AuthNavigationUtils - Utility functions for Auth module navigation
|
||||
*
|
||||
* This module provides helper functions for common navigation patterns
|
||||
* within the Auth module, ensuring consistent navigation behavior.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Navigate to Login screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const navigateToLogin = (navigation: AuthNavigationProp): void => {
|
||||
navigation.navigate('Login');
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Sign Up screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const navigateToSignUp = (navigation: AuthNavigationProp): void => {
|
||||
navigation.navigate('SignUp');
|
||||
};
|
||||
|
||||
/**
|
||||
* Go back to previous screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const goBack = (navigation: AuthNavigationProp): void => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset navigation stack to Login screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const resetToLogin = (navigation: AuthNavigationProp): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'Login' }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset navigation stack to Sign Up screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const resetToSignUp = (navigation: AuthNavigationProp): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'SignUp' }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace current screen with Login screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const replaceWithLogin = (navigation: AuthNavigationProp): void => {
|
||||
navigation.replace('Login');
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace current screen with Sign Up screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const replaceWithSignUp = (navigation: AuthNavigationProp): void => {
|
||||
navigation.replace('SignUp');
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Sign Up screen and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const navigateToSignUpAndClearStack = (navigation: AuthNavigationProp): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'SignUp' }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Login screen and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const navigateToLoginAndClearStack = (navigation: AuthNavigationProp): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'Login' }],
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: navigationUtils.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
74
app/modules/Auth/redux/authActions.ts
Normal file
74
app/modules/Auth/redux/authActions.ts
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* File: authActions.ts
|
||||
* Description: Async actions (thunks) for Auth state
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { logout } from './authSlice';
|
||||
import { authAPI } from '../services/authAPI';
|
||||
import { showError, showSuccess } from '../../../shared/utils/toast';
|
||||
|
||||
/**
|
||||
* Thunk to login user
|
||||
*/
|
||||
export const login = createAsyncThunk(
|
||||
'auth/login',
|
||||
async (credentials: { email: string; password: string }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response:any = await authAPI.login(credentials.email, credentials.password,'web');
|
||||
console.log('user response',response)
|
||||
|
||||
if(response.data.message && !response.data.success){
|
||||
showError(response.data.message)
|
||||
return rejectWithValue(response.data.message);
|
||||
}
|
||||
|
||||
if(response.data.message && response.data.success){
|
||||
showSuccess(response.data.message)
|
||||
}
|
||||
|
||||
if (response.ok && response.data && response.data.data) {
|
||||
// Return the user data for the fulfilled case
|
||||
return {...response.data.data.user,access_token:response.data.data.access_token};
|
||||
} else {
|
||||
const errorMessage = response.data?.message || response.problem || 'Unknown error';
|
||||
return rejectWithValue(errorMessage);
|
||||
}
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Thunk to logout user
|
||||
*/
|
||||
export const logoutUser = createAsyncThunk(
|
||||
'auth/logout',
|
||||
async (_, { dispatch, rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Add logout API call if needed
|
||||
// const response = await authAPI.logout();
|
||||
|
||||
// For now, just dispatch the logout action
|
||||
dispatch(logout());
|
||||
|
||||
// Show success message
|
||||
showSuccess('Logged out successfully');
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Logout error:', error);
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* End of File: authActions.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
48
app/modules/Auth/redux/authSelectors.ts
Normal file
48
app/modules/Auth/redux/authSelectors.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* File: authSelectors.ts
|
||||
* Description: Selectors for Auth redux state
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { RootState } from '../../../store';
|
||||
|
||||
export const selectUser = (state: RootState) => state.auth.user;
|
||||
export const selectAuthLoading = (state: RootState) => state.auth.loading;
|
||||
export const selectAuthError = (state: RootState) => state.auth.error;
|
||||
export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated;
|
||||
export const selectHospitals = (state: RootState) => state.hospital.hospitals;
|
||||
|
||||
// User profile selectors
|
||||
export const selectUserProfile = (state: RootState) => state.auth.user;
|
||||
export const selectUserDisplayName = (state: RootState) => state.auth.user?.display_name;
|
||||
export const selectUserEmail = (state: RootState) => state.auth.user?.email;
|
||||
export const selectUserFirstName = (state: RootState) => state.auth.user?.first_name;
|
||||
export const selectUserLastName = (state: RootState) => state.auth.user?.last_name;
|
||||
export const selectUserHospitalId = (state: RootState) => state.auth.user?.hospital_id;
|
||||
export const selectUserProfilePhoto = (state: RootState) => state.auth.user?.profile_photo_url;
|
||||
export const selectUserThemeColor = (state: RootState) => state.auth.user?.theme_color;
|
||||
export const selectUserAccentColor = (state: RootState) => state.auth.user?.accent_color;
|
||||
|
||||
// Onboarding selectors
|
||||
export const selectIsOnboarded = (state: RootState) => state.auth.user?.onboarded;
|
||||
export const selectOnboardingCompleted = (state: RootState) => state.auth.user?.onboarding_completed;
|
||||
export const selectOnboardingStep = (state: RootState) => state.auth.user?.onboarding_step;
|
||||
export const selectOnboardingMessage = (state: RootState) => state.auth.user?.onboarding_message;
|
||||
|
||||
// Dashboard settings selectors
|
||||
export const selectDashboardSettings = (state: RootState) => state.auth.user?.dashboard_settings;
|
||||
export const selectDashboardTheme = (state: RootState) => state.auth.user?.dashboard_settings?.theme;
|
||||
export const selectDashboardLanguage = (state: RootState) => state.auth.user?.dashboard_settings?.language;
|
||||
export const selectDashboardTimezone = (state: RootState) => state.auth.user?.dashboard_settings?.timezone;
|
||||
|
||||
// Notification preferences selectors
|
||||
export const selectNotificationPreferences = (state: RootState) => state.auth.user?.notification_preferences;
|
||||
export const selectCriticalAlertsPreferences = (state: RootState) => state.auth.user?.notification_preferences?.critical_alerts;
|
||||
export const selectSystemNotificationsPreferences = (state: RootState) => state.auth.user?.notification_preferences?.system_notifications;
|
||||
|
||||
/*
|
||||
* End of File: authSelectors.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
103
app/modules/Auth/redux/authSlice.ts
Normal file
103
app/modules/Auth/redux/authSlice.ts
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* File: authSlice.ts
|
||||
* Description: Redux slice for Auth state management
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { User, NotificationPreferences, DashboardSettings } from '../../../shared/types/auth';
|
||||
import { login } from './authActions';
|
||||
|
||||
// Use User type from shared types as UserProfile
|
||||
type UserProfile = User;
|
||||
|
||||
// Auth state type
|
||||
interface AuthState {
|
||||
user: UserProfile | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
isAuthenticated: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth slice for managing authentication state
|
||||
*/
|
||||
const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
state.isAuthenticated = false;
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
},
|
||||
updateOnboarded(state, action: PayloadAction<boolean>) {
|
||||
if (state.user) {
|
||||
state.user.onboarded = action.payload;
|
||||
}
|
||||
},
|
||||
updateUserProfile(state, action: PayloadAction<Partial<UserProfile>>) {
|
||||
if (state.user) {
|
||||
state.user = { ...state.user, ...action.payload };
|
||||
}
|
||||
},
|
||||
updateNotificationPreferences(state, action: PayloadAction<NotificationPreferences>) {
|
||||
if (state.user) {
|
||||
state.user.notification_preferences = action.payload;
|
||||
}
|
||||
},
|
||||
updateDashboardSettings(state, action: PayloadAction<DashboardSettings>) {
|
||||
if (state.user) {
|
||||
state.user.dashboard_settings = action.payload;
|
||||
}
|
||||
},
|
||||
clearError(state) {
|
||||
state.error = null;
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Login thunk cases
|
||||
builder
|
||||
.addCase(login.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(login.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.user = action.payload as UserProfile;
|
||||
state.isAuthenticated = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(login.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string || 'Login failed';
|
||||
state.isAuthenticated = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
logout,
|
||||
updateOnboarded,
|
||||
updateUserProfile,
|
||||
updateNotificationPreferences,
|
||||
updateDashboardSettings,
|
||||
clearError
|
||||
} = authSlice.actions;
|
||||
|
||||
export default authSlice.reducer;
|
||||
|
||||
/*
|
||||
* End of File: authSlice.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
129
app/modules/Auth/redux/hospitalSelectors.ts
Normal file
129
app/modules/Auth/redux/hospitalSelectors.ts
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* File: hospitalSelectors.ts
|
||||
* Description: Redux selectors for Hospital state
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from '../../../store';
|
||||
|
||||
// ============================================================================
|
||||
// BASE SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Hospital State
|
||||
*
|
||||
* Purpose: Get the entire hospital state from Redux store
|
||||
*/
|
||||
export const selectHospitalState = (state: RootState) => state.hospital;
|
||||
|
||||
/**
|
||||
* Select Hospitals List
|
||||
*
|
||||
* Purpose: Get the list of hospitals
|
||||
*/
|
||||
export const selectHospitals = (state: RootState) => state.hospital.hospitals;
|
||||
|
||||
/**
|
||||
* Select Hospital Loading State
|
||||
*
|
||||
* Purpose: Get the loading state for hospital operations
|
||||
*/
|
||||
export const selectHospitalLoading = (state: RootState) => state.hospital.loading;
|
||||
|
||||
/**
|
||||
* Select Hospital Error
|
||||
*
|
||||
* Purpose: Get any error from hospital operations
|
||||
*/
|
||||
export const selectHospitalError = (state: RootState) => state.hospital.error;
|
||||
|
||||
// ============================================================================
|
||||
// DERIVED SELECTORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Select Hospitals Count
|
||||
*
|
||||
* Purpose: Get the total number of hospitals
|
||||
*/
|
||||
export const selectHospitalsCount = createSelector(
|
||||
[selectHospitals],
|
||||
(hospitals) => hospitals?.length || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Hospital by ID
|
||||
*
|
||||
* Purpose: Get a specific hospital by its ID
|
||||
*/
|
||||
export const selectHospitalById = createSelector(
|
||||
[selectHospitals, (_state: RootState, hospitalId: string) => hospitalId],
|
||||
(hospitals, hospitalId) =>
|
||||
hospitals?.find(hospital => hospital.hospital_id === hospitalId) || null
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Hospital Names
|
||||
*
|
||||
* Purpose: Get an array of hospital names for display
|
||||
*/
|
||||
export const selectHospitalNames = createSelector(
|
||||
[selectHospitals],
|
||||
(hospitals) =>
|
||||
hospitals?.map(hospital => hospital.hospital_name).filter(Boolean) || []
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Hospitals for Dropdown
|
||||
*
|
||||
* Purpose: Get hospitals formatted for dropdown/select components
|
||||
*/
|
||||
export const selectHospitalsForDropdown = createSelector(
|
||||
[selectHospitals],
|
||||
(hospitals) =>
|
||||
hospitals?.map(hospital => ({
|
||||
label: hospital.hospital_name || 'Unknown Hospital',
|
||||
value: hospital.hospital_id || '',
|
||||
})).filter(item => item.value) || []
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Filtered Hospitals
|
||||
*
|
||||
* Purpose: Get hospitals filtered by search term
|
||||
*/
|
||||
export const selectFilteredHospitals = createSelector(
|
||||
[selectHospitals, (_state: RootState, searchTerm: string) => searchTerm],
|
||||
(hospitals, searchTerm) => {
|
||||
if (!searchTerm.trim()) return hospitals || [];
|
||||
|
||||
const term = searchTerm.toLowerCase();
|
||||
return hospitals?.filter(hospital =>
|
||||
hospital.hospital_name?.toLowerCase().includes(term)
|
||||
) || [];
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Select Hospital Loading Status
|
||||
*
|
||||
* Purpose: Get comprehensive loading status for hospital operations
|
||||
*/
|
||||
export const selectHospitalStatus = createSelector(
|
||||
[selectHospitalLoading, selectHospitalError, selectHospitalsCount],
|
||||
(loading, error, count) => ({
|
||||
loading,
|
||||
error,
|
||||
hasData: count > 0,
|
||||
isEmpty: count === 0,
|
||||
})
|
||||
);
|
||||
|
||||
/*
|
||||
* End of File: hospitalSelectors.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
170
app/modules/Auth/redux/hospitalSlice.ts
Normal file
170
app/modules/Auth/redux/hospitalSlice.ts
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* File: hospitalSlice.ts
|
||||
* Description: Redux slice for Hospital state management (non-persisted)
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { authAPI } from '../services/authAPI';
|
||||
import { showError } from '../../../shared/utils/toast';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Hospital Interface
|
||||
*
|
||||
* Purpose: Defines the structure for hospital data
|
||||
*/
|
||||
interface Hospital {
|
||||
hospital_id: string | null;
|
||||
hospital_name: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hospital State Interface
|
||||
*
|
||||
* Purpose: Defines the structure for hospital state
|
||||
*/
|
||||
interface HospitalState {
|
||||
hospitals: Hospital[] | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ASYNC THUNKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch Hospitals Async Thunk
|
||||
*
|
||||
* Purpose: Fetch hospital list from API
|
||||
*
|
||||
* Features:
|
||||
* - API integration with error handling
|
||||
* - Loading state management
|
||||
* - Toast notifications for errors
|
||||
* - Automatic state updates
|
||||
*/
|
||||
export const fetchHospitals = createAsyncThunk(
|
||||
'hospital/fetchHospitals',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response: any = await authAPI.gethospitals();
|
||||
console.log('hospital response', response);
|
||||
|
||||
if (response.ok && response.data && response.data.data) {
|
||||
return response.data.data;
|
||||
} else {
|
||||
showError('Error while fetching hospital list');
|
||||
return rejectWithValue('Failed to fetch hospitals');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('Hospital fetch error:', error);
|
||||
showError('Error while fetching hospital list');
|
||||
return rejectWithValue(error.message || 'Network error');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// INITIAL STATE
|
||||
// ============================================================================
|
||||
|
||||
const initialState: HospitalState = {
|
||||
hospitals: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HOSPITAL SLICE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Hospital Slice
|
||||
*
|
||||
* Purpose: Manages hospital-related state
|
||||
*
|
||||
* Features:
|
||||
* - Hospital list management
|
||||
* - Loading states for API calls
|
||||
* - Error handling and display
|
||||
* - Non-persisted state (not stored in AsyncStorage)
|
||||
*/
|
||||
const hospitalSlice = createSlice({
|
||||
name: 'hospital',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* Set Hospitals Action
|
||||
*
|
||||
* Purpose: Manually set hospital list
|
||||
*/
|
||||
setHospitals(state, action: PayloadAction<Hospital[]>) {
|
||||
state.hospitals = action.payload;
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear Hospitals Action
|
||||
*
|
||||
* Purpose: Clear hospital list and reset state
|
||||
*/
|
||||
clearHospitals(state) {
|
||||
state.hospitals = [];
|
||||
state.loading = false;
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear Error Action
|
||||
*
|
||||
* Purpose: Clear error state
|
||||
*/
|
||||
clearError(state) {
|
||||
state.error = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
// Fetch Hospitals - Pending
|
||||
.addCase(fetchHospitals.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
// Fetch Hospitals - Fulfilled
|
||||
.addCase(fetchHospitals.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.hospitals = action.payload;
|
||||
state.error = null;
|
||||
})
|
||||
// Fetch Hospitals - Rejected
|
||||
.addCase(fetchHospitals.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string || 'Failed to fetch hospitals';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export const {
|
||||
setHospitals,
|
||||
clearHospitals,
|
||||
clearError,
|
||||
} = hospitalSlice.actions;
|
||||
|
||||
export default hospitalSlice.reducer;
|
||||
|
||||
/*
|
||||
* End of File: hospitalSlice.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
16
app/modules/Auth/redux/index.ts
Normal file
16
app/modules/Auth/redux/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Barrel export for Auth redux
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export { default as authReducer } from './authSlice';
|
||||
export * from './authActions';
|
||||
export * from './authSelectors';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
420
app/modules/Auth/screens/LoginScreen.tsx
Normal file
420
app/modules/Auth/screens/LoginScreen.tsx
Normal file
@ -0,0 +1,420 @@
|
||||
/*
|
||||
* File: LoginScreen.tsx
|
||||
* Description: Login screen with credential-based authentication
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
Keyboard,
|
||||
TouchableOpacity,
|
||||
TextInput,
|
||||
ScrollView,
|
||||
KeyboardAvoidingView,
|
||||
Alert,
|
||||
Platform,
|
||||
Image,
|
||||
} from 'react-native';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
||||
import { login } from '../redux/authActions';
|
||||
import { selectAuthLoading } from '../redux/authSelectors';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { validateEmail } from '../../../shared/utils/validators';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
import { AuthNavigationProp } from '../navigation';
|
||||
|
||||
/**
|
||||
* LoginScreenProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the LoginScreen component
|
||||
*
|
||||
* Props:
|
||||
* - navigation: React Navigation object for screen navigation
|
||||
*/
|
||||
interface LoginScreenProps {
|
||||
navigation: AuthNavigationProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* LoginScreen Component
|
||||
*
|
||||
* Purpose: Main authentication screen for credential-based login
|
||||
*
|
||||
* Authentication Flow:
|
||||
* 1. Email/Password validation
|
||||
* 2. Redux action dispatch for login
|
||||
* 3. Loading state management
|
||||
* 4. Error handling and user feedback
|
||||
*
|
||||
* Features:
|
||||
* - Keyboard-aware layout for better UX
|
||||
* - Form validation and error handling
|
||||
* - Loading states during authentication
|
||||
* - Password visibility toggle
|
||||
* - Navigation to sign up screen
|
||||
* - Responsive design for different screen sizes
|
||||
*/
|
||||
const LoginScreen: React.FC<LoginScreenProps> = ({ navigation }) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
// Form input states
|
||||
const [email, setEmail] = useState(''); // User's email address
|
||||
const [password, setPassword] = useState(''); // User's password
|
||||
const [isPasswordVisible, setPasswordVisible] = useState(false); // Password visibility toggle
|
||||
|
||||
// Redux state
|
||||
const dispatch = useAppDispatch();
|
||||
const loading = useAppSelector(selectAuthLoading);
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* handleLogin Function
|
||||
*
|
||||
* Purpose: Process credential-based login with email and password
|
||||
*
|
||||
* Flow:
|
||||
* 1. Validate that both email and password are provided
|
||||
* 2. Validate email format
|
||||
* 3. Show error alert if validation fails
|
||||
* 4. Dispatch Redux login action with credentials
|
||||
*/
|
||||
const handleLogin = () => {
|
||||
// Validate required fields
|
||||
if (!email.trim() || !password.trim()) {
|
||||
Alert.alert('Validation Error', 'Email and password are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
if (!validateEmail(email)) {
|
||||
Alert.alert('Validation Error', 'Please enter a valid email address.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch login action
|
||||
dispatch(login({ email, password }));
|
||||
};
|
||||
|
||||
/**
|
||||
* handleSignUp Function
|
||||
*
|
||||
* Purpose: Navigate to the SignUpScreen for new user registration
|
||||
*
|
||||
* Flow: Navigate to SignUp screen using React Navigation
|
||||
*/
|
||||
const handleSignUp = () => {
|
||||
navigation.navigate('SignUp');
|
||||
};
|
||||
|
||||
/**
|
||||
* togglePasswordVisibility Function
|
||||
*
|
||||
* Purpose: Toggle password field visibility for better UX
|
||||
*/
|
||||
const togglePasswordVisibility = () => {
|
||||
setPasswordVisible(!isPasswordVisible);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER SECTION
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<KeyboardAvoidingView behavior="padding" style={styles.container}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.content}>
|
||||
{/* ========================================================================
|
||||
* HEADER SECTION - App branding and title
|
||||
* ======================================================================== */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Physician</Text>
|
||||
<Text style={styles.subtitle}>Emergency Department Access</Text>
|
||||
</View>
|
||||
<View style={styles.imageContainer}>
|
||||
<Image source={require('../../../assets/images/hospital-logo.png')} style={styles.image} />
|
||||
</View>
|
||||
|
||||
{/* ========================================================================
|
||||
* LOGIN FORM - Main authentication interface
|
||||
* ======================================================================== */}
|
||||
<View style={styles.formContainer}>
|
||||
{/* Email Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Icon name="mail" size={20} color={theme.colors.textSecondary} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={styles.inputField}
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
editable={!loading}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Password Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Icon name="lock" size={20} color={theme.colors.textSecondary} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
style={styles.inputField}
|
||||
secureTextEntry={!isPasswordVisible}
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
editable={!loading}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={togglePasswordVisibility}
|
||||
style={styles.eyeIcon}
|
||||
disabled={loading}
|
||||
>
|
||||
<Icon
|
||||
name={isPasswordVisible ? 'eye-off' : 'eye'}
|
||||
size={22}
|
||||
color={theme.colors.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
|
||||
|
||||
{/* Login Button */}
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.loginButton, loading && styles.buttonDisabled]}
|
||||
onPress={handleLogin}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text style={styles.buttonText}>Logging in...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.buttonText}>Login</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Divider */}
|
||||
<View style={styles.divider}>
|
||||
<View style={styles.dividerLine} />
|
||||
<Text style={styles.dividerText}>OR</Text>
|
||||
<View style={styles.dividerLine} />
|
||||
</View>
|
||||
|
||||
{/* Sign Up Button */}
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.signUpButton]}
|
||||
onPress={handleSignUp}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={styles.signUpButtonText}>Sign Up</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* ========================================================================
|
||||
* FOOTER - Security and information message
|
||||
* ======================================================================== */}
|
||||
<View style={styles.footer}>
|
||||
<Text style={styles.footerText}>
|
||||
Secure access to patient information and critical alerts
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Content wrapper
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.xxl,
|
||||
},
|
||||
|
||||
// App title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displayMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.primary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// App subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// Form container
|
||||
formContainer: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input container
|
||||
inputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
marginBottom: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: 2,
|
||||
},
|
||||
|
||||
// Input icon
|
||||
inputIcon: {
|
||||
marginRight: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Input field
|
||||
inputField: {
|
||||
flex: 1,
|
||||
paddingVertical: theme.spacing.md,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Eye icon for password visibility
|
||||
eyeIcon: {
|
||||
paddingLeft: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Base button styling
|
||||
button: {
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Login button
|
||||
loginButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Sign up button
|
||||
signUpButton: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Disabled button
|
||||
buttonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Button text
|
||||
buttonText: {
|
||||
color: theme.colors.background,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
},
|
||||
|
||||
// Sign up button text
|
||||
signUpButtonText: {
|
||||
color: theme.colors.primary,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
},
|
||||
|
||||
// Loading container
|
||||
loadingContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Divider
|
||||
divider: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Divider line
|
||||
dividerLine: {
|
||||
flex: 1,
|
||||
height: 1,
|
||||
backgroundColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Divider text
|
||||
dividerText: {
|
||||
marginHorizontal: theme.spacing.md,
|
||||
color: theme.colors.textSecondary,
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
},
|
||||
|
||||
// Footer
|
||||
footer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Footer text
|
||||
footerText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textMuted,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// Image container
|
||||
imageContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Image
|
||||
image: {
|
||||
width: '100%',
|
||||
height: 150,
|
||||
},
|
||||
});
|
||||
|
||||
export default LoginScreen;
|
||||
|
||||
/*
|
||||
* End of File: LoginScreen.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
641
app/modules/Auth/screens/ResetPasswordScreen.tsx
Normal file
641
app/modules/Auth/screens/ResetPasswordScreen.tsx
Normal file
@ -0,0 +1,641 @@
|
||||
/*
|
||||
* File: ResetPasswordScreen.tsx
|
||||
* Description: Password reset screen for onboarding flow
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
||||
import { updateOnboarded, logout } from '../redux/authSlice';
|
||||
import { authAPI } from '../services/authAPI';
|
||||
import { showError, showSuccess } from '../../../shared/utils/toast';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
import { AuthNavigationProp } from '../navigation/navigationTypes';
|
||||
|
||||
/**
|
||||
* ResetPasswordScreenProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the ResetPasswordScreen component
|
||||
*
|
||||
* Props:
|
||||
* - navigation: React Navigation object for screen navigation (optional when used in root stack)
|
||||
*/
|
||||
interface ResetPasswordScreenProps {
|
||||
navigation?: AuthNavigationProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* PasswordRule Interface
|
||||
*
|
||||
* Purpose: Defines the structure for password validation rules
|
||||
*/
|
||||
interface PasswordRule {
|
||||
id: string;
|
||||
label: string;
|
||||
validator: (password: string) => boolean;
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ResetPasswordScreen Component
|
||||
*
|
||||
* Purpose: Password reset screen for users who need to set their initial password
|
||||
*
|
||||
* Features:
|
||||
* 1. Password and confirm password input fields
|
||||
* 2. Real-time password validation with visual feedback
|
||||
* 3. Password visibility toggles
|
||||
* 4. Password strength requirements display
|
||||
* 5. Integration with Redux for onboarding status
|
||||
* 6. API integration for password change
|
||||
*
|
||||
* Password Requirements:
|
||||
* - At least 8 characters
|
||||
* - One uppercase letter
|
||||
* - One lowercase letter
|
||||
* - One number
|
||||
* - One special character
|
||||
* - Passwords must match
|
||||
*/
|
||||
export const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
// Form input states
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [isPasswordVisible, setPasswordVisible] = useState(false);
|
||||
const [isConfirmPasswordVisible, setConfirmPasswordVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Redux state
|
||||
const dispatch = useAppDispatch();
|
||||
const user = useAppSelector((state) => state.auth.user);
|
||||
|
||||
// Password validation rules
|
||||
const [passwordRules, setPasswordRules] = useState<PasswordRule[]>([
|
||||
{
|
||||
id: 'length',
|
||||
label: 'At least 8 characters',
|
||||
validator: (pwd: string) => pwd.length >= 8,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'uppercase',
|
||||
label: 'One uppercase letter',
|
||||
validator: (pwd: string) => /[A-Z]/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'lowercase',
|
||||
label: 'One lowercase letter',
|
||||
validator: (pwd: string) => /[a-z]/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'number',
|
||||
label: 'One number',
|
||||
validator: (pwd: string) => /\d/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'special',
|
||||
label: 'One special character',
|
||||
validator: (pwd: string) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd),
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
id: 'match',
|
||||
label: 'Passwords match',
|
||||
validator: (pwd: string) => pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword,
|
||||
isValid: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useEffect for password validation
|
||||
*
|
||||
* Purpose: Update password rules when password or confirm password changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
updatePasswordRules(password);
|
||||
}, [password, confirmPassword]);
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* validatePassword Function
|
||||
*
|
||||
* Purpose: Check if all password requirements are met
|
||||
*
|
||||
* @param pwd - Password to validate
|
||||
* @returns boolean indicating if password meets all requirements
|
||||
*/
|
||||
const validatePassword = (pwd: string): boolean => {
|
||||
return passwordRules.every(rule => rule.isValid);
|
||||
};
|
||||
|
||||
/**
|
||||
* updatePasswordRules Function
|
||||
*
|
||||
* Purpose: Update password validation rules based on current password
|
||||
*
|
||||
* @param pwd - Current password value
|
||||
*/
|
||||
const updatePasswordRules = (pwd: string) => {
|
||||
setPasswordRules(prevRules =>
|
||||
prevRules.map(rule => {
|
||||
if (rule.id === 'match') {
|
||||
return {
|
||||
...rule,
|
||||
isValid: pwd.length > 0 && confirmPassword.length > 0 && pwd === confirmPassword,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...rule,
|
||||
isValid: rule.validator(pwd),
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* handlePasswordChange Function
|
||||
*
|
||||
* Purpose: Handle password input changes
|
||||
*
|
||||
* @param pwd - New password value
|
||||
*/
|
||||
const handlePasswordChange = (pwd: string) => {
|
||||
setPassword(pwd);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleConfirmPasswordChange Function
|
||||
*
|
||||
* Purpose: Handle confirm password input changes
|
||||
*
|
||||
* @param pwd - New confirm password value
|
||||
*/
|
||||
const handleConfirmPasswordChange = (pwd: string) => {
|
||||
setConfirmPassword(pwd);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleReset Function
|
||||
*
|
||||
* Purpose: Handle password reset submission
|
||||
*
|
||||
* Flow:
|
||||
* 1. Validate required fields
|
||||
* 2. Validate password requirements
|
||||
* 3. Check password match
|
||||
* 4. Call API to change password
|
||||
* 5. Update onboarding status on success
|
||||
*/
|
||||
const handleReset = async () => {
|
||||
// Validate required fields
|
||||
if (!password.trim() || !confirmPassword.trim()) {
|
||||
Alert.alert('Validation Error', 'Both password fields are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate password requirements
|
||||
if (!validatePassword(password)) {
|
||||
Alert.alert('Validation Error', 'Please meet all password requirements.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check password match
|
||||
if (password !== confirmPassword) {
|
||||
Alert.alert('Validation Error', 'Passwords do not match.');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// Call API to change password
|
||||
const response: any = await authAPI.changepassword({
|
||||
password,
|
||||
token: user?.access_token,
|
||||
});
|
||||
|
||||
console.log('reset response', response);
|
||||
|
||||
if (response.data && response.data.message) {
|
||||
if (response.data.success) {
|
||||
showSuccess(response.data.message);
|
||||
|
||||
// Update onboarding status
|
||||
dispatch(updateOnboarded(true));
|
||||
|
||||
// Navigate to main app
|
||||
// The app will automatically navigate to MainTabNavigator due to Redux state change
|
||||
} else {
|
||||
showError(response.data.message);
|
||||
}
|
||||
} else {
|
||||
showError('Error while changing password');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Password reset error:', error);
|
||||
showError('Failed to reset password. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handleBack Function
|
||||
*
|
||||
* Purpose: Handle back navigation - logs out user since they can't go back to login
|
||||
*/
|
||||
const handleBack = () => {
|
||||
Alert.alert(
|
||||
'Sign Out',
|
||||
'Are you sure you want to sign out? You will need to log in again.',
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Sign Out',
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
// Dispatch logout action
|
||||
dispatch(logout());
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* renderPasswordRule Function
|
||||
*
|
||||
* Purpose: Render individual password validation rule
|
||||
*
|
||||
* @param rule - Password rule to render
|
||||
* @returns JSX element for the rule
|
||||
*/
|
||||
const renderPasswordRule = (rule: PasswordRule) => (
|
||||
<View key={rule.id} style={styles.ruleContainer}>
|
||||
<View style={[styles.checkbox, rule.isValid && styles.checkboxChecked]}>
|
||||
{rule.isValid && (
|
||||
<Icon name="check" size={12} color={theme.colors.background} />
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.ruleText, rule.isValid && styles.ruleTextValid]}>
|
||||
{rule.label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// RENDER SECTION
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={handleBack} style={styles.backButton}>
|
||||
<Icon name="log-out" size={24} color={theme.colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Set Your Password</Text>
|
||||
<View style={styles.headerSpacer} />
|
||||
</View>
|
||||
|
||||
{/* Icon */}
|
||||
<View style={styles.iconContainer}>
|
||||
<Icon name="lock" size={64} color={theme.colors.primary} />
|
||||
</View>
|
||||
|
||||
{/* Title and Subtitle */}
|
||||
<Text style={styles.title}>Set your password</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Create a strong password to complete your account setup
|
||||
</Text>
|
||||
|
||||
{/* Password Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Icon name="lock" size={20} color={theme.colors.textSecondary} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={handlePasswordChange}
|
||||
style={styles.inputField}
|
||||
secureTextEntry={!isPasswordVisible}
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
editable={!loading}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => setPasswordVisible(!isPasswordVisible)}
|
||||
style={styles.eyeIcon}
|
||||
disabled={loading}
|
||||
>
|
||||
<Icon
|
||||
name={isPasswordVisible ? 'eye-off' : 'eye'}
|
||||
size={22}
|
||||
color={theme.colors.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Confirm Password Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Icon name="lock" size={20} color={theme.colors.textSecondary} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
placeholder="Confirm Password"
|
||||
value={confirmPassword}
|
||||
onChangeText={handleConfirmPasswordChange}
|
||||
style={styles.inputField}
|
||||
secureTextEntry={!isConfirmPasswordVisible}
|
||||
placeholderTextColor={theme.colors.textMuted}
|
||||
editable={!loading}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => setConfirmPasswordVisible(!isConfirmPasswordVisible)}
|
||||
style={styles.eyeIcon}
|
||||
disabled={loading}
|
||||
>
|
||||
<Icon
|
||||
name={isConfirmPasswordVisible ? 'eye-off' : 'eye'}
|
||||
size={22}
|
||||
color={theme.colors.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Password Rules Section */}
|
||||
<View style={styles.rulesContainer}>
|
||||
<Text style={styles.rulesTitle}>Password Requirements:</Text>
|
||||
<View style={styles.rulesGrid}>
|
||||
{passwordRules.map(renderPasswordRule)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Reset Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
styles.resetButton,
|
||||
loading && styles.buttonDisabled,
|
||||
]}
|
||||
onPress={handleReset}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text style={styles.buttonText}>Setting Password...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.buttonText}>Set Password</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Scroll view
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Scroll content
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Header section
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Back button
|
||||
backButton: {
|
||||
padding: theme.spacing.sm,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
},
|
||||
|
||||
// Header title
|
||||
headerTitle: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Header spacer
|
||||
headerSpacer: {
|
||||
width: 40,
|
||||
},
|
||||
|
||||
// Icon container
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displayMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Subtitle
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Input container
|
||||
inputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
marginBottom: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: 2,
|
||||
},
|
||||
|
||||
// Input icon
|
||||
inputIcon: {
|
||||
marginRight: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Input field
|
||||
inputField: {
|
||||
flex: 1,
|
||||
paddingVertical: theme.spacing.md,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
|
||||
// Eye icon
|
||||
eyeIcon: {
|
||||
padding: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Rules container
|
||||
rulesContainer: {
|
||||
marginTop: theme.spacing.sm,
|
||||
marginBottom: theme.spacing.xl,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Rules title
|
||||
rulesTitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Rules grid
|
||||
rulesGrid: {
|
||||
gap: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Rule container
|
||||
ruleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Checkbox
|
||||
checkbox: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 4,
|
||||
borderWidth: 2,
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
marginRight: theme.spacing.sm,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
// Checkbox checked
|
||||
checkboxChecked: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderColor: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Rule text
|
||||
ruleText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Rule text valid
|
||||
ruleTextValid: {
|
||||
color: theme.colors.success,
|
||||
},
|
||||
|
||||
// Base button
|
||||
button: {
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Reset button
|
||||
resetButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Disabled button
|
||||
buttonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
|
||||
// Button text
|
||||
buttonText: {
|
||||
color: theme.colors.background,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
},
|
||||
|
||||
// Loading container
|
||||
loadingContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default ResetPasswordScreen;
|
||||
|
||||
/*
|
||||
* End of File: ResetPasswordScreen.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
503
app/modules/Auth/screens/SignUpScreen.tsx
Normal file
503
app/modules/Auth/screens/SignUpScreen.tsx
Normal file
@ -0,0 +1,503 @@
|
||||
/*
|
||||
* File: SignUpScreen.tsx
|
||||
* Description: Multi-step signup screen with validation and Redux integration
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
StatusBar,
|
||||
Alert,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
KeyboardAvoidingView,
|
||||
Platform
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// Import signup step components
|
||||
import EmailStep from '../components/signup/EmailStep';
|
||||
import PasswordStep from '../components/signup/PasswordStep';
|
||||
import NameStep from '../components/signup/NameStep';
|
||||
import DocumentUploadStep from '../components/signup/DocumentUploadStep';
|
||||
import HospitalSelectionStep from '../components/signup/HospitalSelectionStep';
|
||||
import EmailAlreadyRegisteredModal from '../components/signup/EmailAlreadyRegisteredModal';
|
||||
|
||||
// Import API service
|
||||
|
||||
|
||||
// Import hospital Redux functionality
|
||||
import { fetchHospitals } from '../redux/hospitalSlice';
|
||||
import { selectHospitalLoading, selectHospitals } from '../redux/hospitalSelectors';
|
||||
|
||||
// Import types
|
||||
import { SignUpData, SignUpStep } from '../types/signup';
|
||||
import { authAPI } from '../services/authAPI';
|
||||
import { showError, showSuccess } from '../../../shared/utils/toast';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
interface SignUpScreenProps {
|
||||
navigation: any;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SIGNUP SCREEN COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SignUpScreen Component
|
||||
*
|
||||
* Purpose: Multi-step signup flow with validation and Redux integration
|
||||
*
|
||||
* Features:
|
||||
* - Step-by-step signup process (email → password → name → document → hospital)
|
||||
* - Real-time validation with visual feedback
|
||||
* - Email and username availability checks
|
||||
* - Hospital selection with search
|
||||
* - Document upload with preview
|
||||
* - Progress tracking with visual progress bar
|
||||
* - Modern UI with icons and proper typography
|
||||
* - Keyboard-aware layout for better UX
|
||||
* - Redux state management
|
||||
* - Loading states and error handling
|
||||
*/
|
||||
const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<SignUpStep>('email');
|
||||
const [showEmailRegisteredModal, setShowEmailRegisteredModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [signUpData, setSignUpData] = useState<Partial<SignUpData>>({
|
||||
email: '',
|
||||
password: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
username: '',
|
||||
id_photo_url: null,
|
||||
hospital_id: '',
|
||||
});
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// ============================================================================
|
||||
// REDUX STATE
|
||||
// ============================================================================
|
||||
|
||||
const hospitals = useAppSelector(selectHospitals);
|
||||
const hospitalLoading = useAppSelector(selectHospitalLoading);
|
||||
|
||||
// ============================================================================
|
||||
// STEP CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
const steps: SignUpStep[] = ['email', 'password', 'name', 'document', 'hospital'];
|
||||
const currentStepIndex = steps.indexOf(currentStep);
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch hospitals on component mount
|
||||
dispatch(fetchHospitals());
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// STEP HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle Email Step Continue
|
||||
*
|
||||
* Purpose: Validate email and proceed to next step
|
||||
*/
|
||||
const handleEmailContinue = async (email: string) => {
|
||||
setIsLoading(true);
|
||||
|
||||
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
|
||||
setShowEmailRegisteredModal(true)
|
||||
}
|
||||
if(response.status==200&&response.data.message){
|
||||
setSignUpData(prev => ({ ...prev, email }));
|
||||
setCurrentStep('password');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to validate email. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Password Step Continue
|
||||
*
|
||||
* Purpose: Validate password and proceed to next step
|
||||
*/
|
||||
const handlePasswordContinue = (password: string) => {
|
||||
setSignUpData(prev => ({ ...prev, password }));
|
||||
setCurrentStep('name');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Name Step Continue
|
||||
*
|
||||
* Purpose: Validate name and username, then proceed to next step
|
||||
*/
|
||||
const handleNameContinue = async (firstName: string, lastName: string, username: string) => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response:any = await authAPI.validateusername(username);
|
||||
console.log('response', response);
|
||||
|
||||
if(response.status==409&&response.data.message){
|
||||
showError(response.data.message);
|
||||
}
|
||||
if(response.status==200&&response.data.message){
|
||||
setSignUpData(prev => ({
|
||||
...prev,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
username: username
|
||||
}));
|
||||
setCurrentStep('document');
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert('Error', 'Failed to validate username. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Document Upload Step Continue
|
||||
*
|
||||
* Purpose: Save document and proceed to next step
|
||||
*/
|
||||
const handleDocumentContinue = (documentUri: string) => {
|
||||
setSignUpData(prev => ({ ...prev, id_photo_url: documentUri }));
|
||||
setCurrentStep('hospital');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Hospital Selection Step Continue
|
||||
*
|
||||
* Purpose: Complete signup process
|
||||
*/
|
||||
const handleHospitalContinue = async (hospitalId: string) => {
|
||||
const finalData: SignUpData = {
|
||||
...signUpData,
|
||||
hospital_id: hospitalId,
|
||||
} as SignUpData;
|
||||
|
||||
setSignUpData(finalData);
|
||||
|
||||
// Call completion handler
|
||||
await onSignUpComplete(finalData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete Signup Process
|
||||
*
|
||||
* Purpose: Submit final signup data to API
|
||||
*/
|
||||
const onSignUpComplete = async (payload: SignUpData) => {
|
||||
console.log('final payload', payload);
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
let role = 'er_physician';
|
||||
|
||||
formData.append('email', payload.email);
|
||||
formData.append('password', payload.password);
|
||||
formData.append('first_name', payload.first_name);
|
||||
formData.append('last_name', payload.last_name);
|
||||
formData.append('username', payload.username);
|
||||
formData.append('dashboard_role', role);
|
||||
formData.append('hospital_id', payload.hospital_id);
|
||||
|
||||
// Attach file if exists
|
||||
if (payload.id_photo_url) {
|
||||
const filePath = payload.id_photo_url;
|
||||
const file = {
|
||||
uri: filePath,
|
||||
name: 'id_photo',
|
||||
type: 'image/jpg',
|
||||
};
|
||||
formData.append('id_photo_url', file as any);
|
||||
}
|
||||
|
||||
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
|
||||
showSuccess('Sign Up Successfully')
|
||||
navigation.navigate('Login');
|
||||
// dispatch(setHospitals(response.data.data))
|
||||
} else {
|
||||
showError('error while signup');
|
||||
if( response.data && response.data.message ) {
|
||||
//@ts-ignore
|
||||
showError(response.data.message)
|
||||
}
|
||||
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('error', error);
|
||||
Alert.alert('Error', 'Failed to create account. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle Back Navigation
|
||||
*
|
||||
* Purpose: Navigate to previous step or go back to previous screen
|
||||
*/
|
||||
const handleBack = () => {
|
||||
if (currentStepIndex > 0) {
|
||||
const previousStep = steps[currentStepIndex - 1];
|
||||
setCurrentStep(previousStep);
|
||||
} else {
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Modal Close
|
||||
*
|
||||
* Purpose: Close email already registered modal
|
||||
*/
|
||||
const handleCloseModal = () => {
|
||||
setShowEmailRegisteredModal(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle Go To Login
|
||||
*
|
||||
* Purpose: Navigate to login screen
|
||||
*/
|
||||
const handleGoToLogin = () => {
|
||||
setShowEmailRegisteredModal(false);
|
||||
navigation.navigate('Login');
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Render Current Step
|
||||
*
|
||||
* Purpose: Render the appropriate step component based on current step
|
||||
*/
|
||||
const renderCurrentStep = () => {
|
||||
console.log('signupdate', signUpData);
|
||||
|
||||
const commonProps = {
|
||||
onBack: handleBack,
|
||||
data: signUpData,
|
||||
isLoading,
|
||||
};
|
||||
|
||||
switch (currentStep) {
|
||||
case 'email':
|
||||
return (
|
||||
<EmailStep
|
||||
{...commonProps}
|
||||
onContinue={handleEmailContinue}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'password':
|
||||
return (
|
||||
<PasswordStep
|
||||
{...commonProps}
|
||||
onContinue={handlePasswordContinue}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'name':
|
||||
return (
|
||||
<NameStep
|
||||
{...commonProps}
|
||||
onContinue={handleNameContinue}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'document':
|
||||
return (
|
||||
<DocumentUploadStep
|
||||
{...commonProps}
|
||||
onContinue={handleDocumentContinue}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'hospital':
|
||||
return (
|
||||
<HospitalSelectionStep
|
||||
{...commonProps}
|
||||
onContinue={handleHospitalContinue}
|
||||
hospitals={hospitals}
|
||||
hospitalLoading={hospitalLoading}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<EmailStep
|
||||
{...commonProps}
|
||||
onContinue={handleEmailContinue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<StatusBar
|
||||
barStyle="dark-content"
|
||||
backgroundColor={theme.colors.background}
|
||||
/>
|
||||
|
||||
{/* Conditional Content Rendering */}
|
||||
{currentStep === 'hospital' ? (
|
||||
// For hospital step, render without ScrollView to avoid conflicts
|
||||
<View style={styles.content}>
|
||||
{renderCurrentStep()}
|
||||
</View>
|
||||
) : (
|
||||
// For other steps, use ScrollView for proper scrolling
|
||||
<ScrollView
|
||||
style={styles.content}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
>
|
||||
{renderCurrentStep()}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
{/* Progress Bar - Bottom */}
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={styles.progressBar}>
|
||||
<View
|
||||
style={[
|
||||
styles.progressFill,
|
||||
{ width: `${((currentStepIndex + 1) / steps.length) * 100}%` }
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.progressText}>
|
||||
{Math.round(((currentStepIndex + 1) / steps.length) * 100)}% Complete
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<EmailAlreadyRegisteredModal
|
||||
visible={showEmailRegisteredModal}
|
||||
onClose={handleCloseModal}
|
||||
onGoToLogin={handleGoToLogin}
|
||||
/>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
|
||||
|
||||
// Progress container
|
||||
progressContainer: {
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
paddingVertical: theme.spacing.md,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Progress bar
|
||||
progressBar: {
|
||||
height: 4,
|
||||
backgroundColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.round,
|
||||
marginBottom: theme.spacing.sm,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
||||
// Progress fill
|
||||
progressFill: {
|
||||
height: '100%',
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.round,
|
||||
},
|
||||
|
||||
// Progress text
|
||||
progressText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
// Content area
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Content container
|
||||
contentContainer: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing.lg,
|
||||
paddingBottom: theme.spacing.xl,
|
||||
},
|
||||
});
|
||||
|
||||
export default SignUpScreen;
|
||||
|
||||
/*
|
||||
* End of File: SignUpScreen.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
38
app/modules/Auth/services/authAPI.ts
Normal file
38
app/modules/Auth/services/authAPI.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* File: authAPI.ts
|
||||
* Description: API service for authentication using apisauce
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { create } from 'apisauce';
|
||||
import { API_CONFIG } from '../../../shared/utils/constants';
|
||||
import { buildHeaders } from '../../../shared/utils/api';
|
||||
|
||||
const api = create({
|
||||
baseURL: API_CONFIG.BASE_URL, // TODO: Replace with actual endpoint
|
||||
});
|
||||
|
||||
/**
|
||||
* login - authenticates user with email and password
|
||||
*/
|
||||
export const authAPI = {
|
||||
login: (email: string, password: string,platform:string) => api.post('/api/auth/auth/login', { email, password,platform },buildHeaders()),
|
||||
//fetch hospital list
|
||||
gethospitals: () => api.get('/api/hospitals/hospitals/app_user/hospitals', {},buildHeaders()),
|
||||
//user signup
|
||||
signup: (formData:any) => api.post('/api/auth/auth/admin/create-user-fromapp', formData,buildHeaders({ contentType: 'multipart/form-data' })),
|
||||
//validate email
|
||||
validatemail: (payload:{email:string}) => api.post('/api/auth/auth/check-email', payload,buildHeaders()),
|
||||
//change password
|
||||
changepassword: (payload:{password:string,token:string | undefined}) => api.post('/api/auth/onboarding/change-password', {password:payload.password},buildHeaders({token:payload.token})),
|
||||
//validate username
|
||||
validateusername: (username:string|undefined) => api.post('/api/auth/auth/check-username', {username},buildHeaders())
|
||||
// Add more endpoints as needed
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: authAPI.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
23
app/modules/Auth/services/biometricService.ts
Normal file
23
app/modules/Auth/services/biometricService.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
15
app/modules/Auth/services/index.ts
Normal file
15
app/modules/Auth/services/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Barrel export for Auth services
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export * from './';
|
||||
export * from './biometricService';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
392
app/modules/Auth/services/signupAPI.ts
Normal file
392
app/modules/Auth/services/signupAPI.ts
Normal file
@ -0,0 +1,392 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
256
app/modules/Auth/types/signup.ts
Normal file
256
app/modules/Auth/types/signup.ts
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* File: signup.ts
|
||||
* Description: Type definitions for signup flow
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// SIGNUP STEP TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SignUpStep Type
|
||||
*
|
||||
* Purpose: Define the different steps in the signup process
|
||||
*/
|
||||
export type SignUpStep = 'email' | 'password' | 'name' | 'document' | 'hospital';
|
||||
|
||||
// ============================================================================
|
||||
// SIGNUP DATA INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SignUpData Interface
|
||||
*
|
||||
* Purpose: Complete signup data structure matching reference code
|
||||
*/
|
||||
export interface SignUpData {
|
||||
// Email and Password
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
// Personal Information
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
|
||||
// Document
|
||||
id_photo_url: string | null;
|
||||
|
||||
// Hospital Information
|
||||
hospital_id: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STEP COMPONENT PROPS INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Base Step Props Interface
|
||||
*
|
||||
* Purpose: Common props for all step components
|
||||
*/
|
||||
export interface BaseStepProps {
|
||||
onBack: () => void;
|
||||
data: Partial<SignUpData>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email Step Props Interface
|
||||
*
|
||||
* Purpose: Props for email step component
|
||||
*/
|
||||
export interface EmailStepProps extends BaseStepProps {
|
||||
onContinue: (email: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Password Step Props Interface
|
||||
*
|
||||
* Purpose: Props for password step component
|
||||
*/
|
||||
export interface PasswordStepProps extends BaseStepProps {
|
||||
onContinue: (password: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name Step Props Interface
|
||||
*
|
||||
* Purpose: Props for name step component
|
||||
*/
|
||||
export interface NameStepProps extends BaseStepProps {
|
||||
onContinue: (firstName: string, lastName: string, username: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Document Upload Step Props Interface
|
||||
*
|
||||
* Purpose: Props for document upload step component
|
||||
*/
|
||||
export interface DocumentUploadStepProps extends BaseStepProps {
|
||||
onContinue: (documentUri: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hospital Selection Step Props Interface
|
||||
*
|
||||
* Purpose: Props for hospital selection step component
|
||||
*/
|
||||
export interface HospitalSelectionStepProps extends BaseStepProps {
|
||||
onContinue: (hospitalId: string) => void;
|
||||
hospitals: Hospital[] | null;
|
||||
hospitalLoading: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VALIDATION INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Validation Result Interface
|
||||
*
|
||||
* Purpose: Result of validation operations
|
||||
*/
|
||||
export interface ValidationResult {
|
||||
isValid: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email Validation Result Interface
|
||||
*
|
||||
* Purpose: Result of email validation
|
||||
*/
|
||||
export interface EmailValidationResult extends ValidationResult {
|
||||
isAvailable: boolean;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Username Validation Result Interface
|
||||
*
|
||||
* Purpose: Result of username validation
|
||||
*/
|
||||
export interface UsernameValidationResult extends ValidationResult {
|
||||
isAvailable: boolean;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// API RESPONSE INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SignUp API Response Interface
|
||||
*
|
||||
* Purpose: Response from signup API
|
||||
*/
|
||||
export interface SignUpApiResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data?: {
|
||||
userId: string;
|
||||
email: string;
|
||||
token?: string;
|
||||
};
|
||||
errors?: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Email Validation API Response Interface
|
||||
*
|
||||
* Purpose: Response from email validation API
|
||||
*/
|
||||
export interface EmailValidationApiResponse {
|
||||
success: boolean;
|
||||
isAvailable: boolean;
|
||||
message: string;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Username Validation API Response Interface
|
||||
*
|
||||
* Purpose: Response from username validation API
|
||||
*/
|
||||
export interface UsernameValidationApiResponse {
|
||||
success: boolean;
|
||||
isAvailable: boolean;
|
||||
message: string;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HOSPITAL INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Hospital Interface
|
||||
*
|
||||
* Purpose: Hospital information for selection
|
||||
*/
|
||||
export interface Hospital {
|
||||
hospital_id: string | null;
|
||||
hospital_name: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hospital List API Response Interface
|
||||
*
|
||||
* Purpose: Response from hospital list API
|
||||
*/
|
||||
export interface HospitalListApiResponse {
|
||||
success: boolean;
|
||||
data: Hospital[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FORM VALIDATION INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Form Validation Rules Interface
|
||||
*
|
||||
* Purpose: Validation rules for form fields
|
||||
*/
|
||||
export interface FormValidationRules {
|
||||
required?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
pattern?: RegExp;
|
||||
custom?: (value: any) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form Field Validation Interface
|
||||
*
|
||||
* Purpose: Validation state for form fields
|
||||
*/
|
||||
export interface FormFieldValidation {
|
||||
isValid: boolean;
|
||||
message: string;
|
||||
isDirty: boolean;
|
||||
isTouched: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form Validation State Interface
|
||||
*
|
||||
* Purpose: Overall form validation state
|
||||
*/
|
||||
export interface FormValidationState {
|
||||
[key: string]: FormFieldValidation;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: signup.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
188
app/modules/Dashboard/components/CriticalAlerts.tsx
Normal file
188
app/modules/Dashboard/components/CriticalAlerts.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* File: CriticalAlerts.tsx
|
||||
* Description: Critical alerts component displaying emergency notifications
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { Alert } from '../../../shared/types/alerts';
|
||||
|
||||
interface CriticalAlertsProps {
|
||||
alerts: Alert[];
|
||||
onAlertPress: (alert: Alert) => void;
|
||||
}
|
||||
|
||||
export const CriticalAlerts: React.FC<CriticalAlertsProps> = ({
|
||||
alerts,
|
||||
onAlertPress,
|
||||
}) => {
|
||||
if (alerts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>🚨 Critical Alerts</Text>
|
||||
<Text style={styles.count}>{alerts.length}</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContainer}
|
||||
>
|
||||
{alerts.map((alert) => (
|
||||
<TouchableOpacity
|
||||
key={alert.id}
|
||||
style={styles.alertCard}
|
||||
onPress={() => onAlertPress(alert)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.alertHeader}>
|
||||
<Text style={styles.alertType}>{alert.type.replace('_', ' ')}</Text>
|
||||
<Text style={styles.alertTime}>
|
||||
{new Date(alert.timestamp).toLocaleTimeString()}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.alertTitle}>{alert.title}</Text>
|
||||
<Text style={styles.alertMessage} numberOfLines={2}>
|
||||
{alert.message}
|
||||
</Text>
|
||||
|
||||
{alert.patientName && (
|
||||
<View style={styles.patientInfo}>
|
||||
<Text style={styles.patientName}>{alert.patientName}</Text>
|
||||
{alert.bedNumber && (
|
||||
<Text style={styles.bedNumber}>Bed {alert.bedNumber}</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{alert.actionRequired && (
|
||||
<View style={styles.actionRequired}>
|
||||
<Text style={styles.actionText}>Action Required</Text>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.colors.criticalBackground,
|
||||
borderColor: theme.colors.critical,
|
||||
borderWidth: 1,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.md,
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.critical,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.critical,
|
||||
},
|
||||
count: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.critical,
|
||||
backgroundColor: theme.colors.background,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
},
|
||||
scrollContainer: {
|
||||
paddingRight: theme.spacing.md,
|
||||
},
|
||||
alertCard: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md,
|
||||
marginRight: theme.spacing.md,
|
||||
minWidth: 280,
|
||||
...theme.shadows.small,
|
||||
},
|
||||
alertHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
alertType: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.critical,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
alertTime: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
alertTitle: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
alertMessage: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
lineHeight: theme.typography.lineHeight.normal * theme.typography.fontSize.bodyMedium,
|
||||
},
|
||||
patientInfo: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
patientName: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
bedNumber: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
backgroundColor: theme.colors.backgroundAccent,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
},
|
||||
actionRequired: {
|
||||
backgroundColor: theme.colors.critical,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
actionText: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
color: theme.colors.background,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: CriticalAlerts.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
120
app/modules/Dashboard/components/DashboardHeader.tsx
Normal file
120
app/modules/Dashboard/components/DashboardHeader.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* File: DashboardHeader.tsx
|
||||
* Description: Dashboard header component displaying ER department overview and statistics
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { ERDashboard } from '../../../shared/types/dashboard';
|
||||
|
||||
interface DashboardHeaderProps {
|
||||
dashboard: ERDashboard;
|
||||
}
|
||||
|
||||
export const DashboardHeader: React.FC<DashboardHeaderProps> = ({
|
||||
dashboard,
|
||||
}) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Emergency Department</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{dashboard.shiftInfo.currentShift} Shift • {dashboard.shiftInfo.attendingPhysician}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{dashboard.totalPatients}</Text>
|
||||
<Text style={styles.statLabel}>Total Patients</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={[styles.statValue, styles.criticalValue]}>
|
||||
{dashboard.criticalPatients}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>Critical</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{dashboard.pendingScans}</Text>
|
||||
<Text style={styles.statLabel}>Pending Scans</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{dashboard.bedOccupancy}%</Text>
|
||||
<Text style={styles.statLabel}>Bed Occupancy</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.lastUpdated}>
|
||||
<Text style={styles.lastUpdatedText}>
|
||||
Last updated: {dashboard.lastUpdated.toLocaleTimeString()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.medium,
|
||||
},
|
||||
header: {
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displayMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
statsContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
statItem: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
statValue: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.primary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
criticalValue: {
|
||||
color: theme.colors.critical,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
lastUpdated: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
lastUpdatedText: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: DashboardHeader.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
102
app/modules/Dashboard/components/DepartmentStats.tsx
Normal file
102
app/modules/Dashboard/components/DepartmentStats.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* File: DepartmentStats.tsx
|
||||
* Description: Department statistics component displaying patient counts per department
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { DepartmentStats as DepartmentStatsType } from '../../../shared/types/dashboard';
|
||||
|
||||
interface DepartmentStatsProps {
|
||||
stats: DepartmentStatsType;
|
||||
}
|
||||
|
||||
export const DepartmentStats: React.FC<DepartmentStatsProps> = ({
|
||||
stats,
|
||||
}) => {
|
||||
const departments = [
|
||||
{ key: 'emergency', label: 'Emergency', color: theme.colors.primary },
|
||||
{ key: 'trauma', label: 'Trauma', color: theme.colors.critical },
|
||||
{ key: 'cardiac', label: 'Cardiac', color: theme.colors.warning },
|
||||
{ key: 'neurology', label: 'Neurology', color: theme.colors.info },
|
||||
{ key: 'pediatrics', label: 'Pediatrics', color: theme.colors.success },
|
||||
{ key: 'icu', label: 'ICU', color: theme.colors.secondary },
|
||||
];
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Department Overview</Text>
|
||||
<View style={styles.statsGrid}>
|
||||
{departments.map((dept) => (
|
||||
<View key={dept.key} style={styles.statItem}>
|
||||
<View style={[styles.colorIndicator, { backgroundColor: dept.color }]} />
|
||||
<View style={styles.statContent}>
|
||||
<Text style={styles.statValue}>
|
||||
{stats[dept.key as keyof DepartmentStatsType]}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>{dept.label}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.lg,
|
||||
...theme.shadows.medium,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
statsGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
statItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: '48%',
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
colorIndicator: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 6,
|
||||
marginRight: theme.spacing.sm,
|
||||
},
|
||||
statContent: {
|
||||
flex: 1,
|
||||
},
|
||||
statValue: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: DepartmentStats.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
362
app/modules/Dashboard/components/PatientCard.tsx
Normal file
362
app/modules/Dashboard/components/PatientCard.tsx
Normal file
@ -0,0 +1,362 @@
|
||||
/*
|
||||
* File: PatientCard.tsx
|
||||
* Description: Patient card component displaying patient information and status
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { Patient } from '../../../shared/types/patient';
|
||||
import { getPriorityColor, getStatusColor, calculateAge } from '../../../shared/utils/helpers';
|
||||
|
||||
/**
|
||||
* PatientCardProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the PatientCard component
|
||||
*
|
||||
* Props:
|
||||
* - patient: Patient data object containing all patient information
|
||||
* - onPress: Callback function triggered when the card is pressed
|
||||
*/
|
||||
interface PatientCardProps {
|
||||
patient: Patient;
|
||||
onPress: (patient: Patient) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* PatientCard Component
|
||||
*
|
||||
* Purpose: Display comprehensive patient information in a compact, touchable card format
|
||||
*
|
||||
* Features:
|
||||
* 1. Patient identification (name, age, gender, MRN)
|
||||
* 2. Priority and status badges with color coding
|
||||
* 3. Location information (bed, room, attending physician)
|
||||
* 4. Real-time vital signs display
|
||||
* 5. Allergy warnings (if any)
|
||||
* 6. Current diagnosis and last update time
|
||||
*
|
||||
* Data Display:
|
||||
* - Header: Patient name, demographics, and status badges
|
||||
* - Location: Bed/room assignment and attending physician
|
||||
* - Vital Signs: BP, HR, Temperature, Oxygen saturation
|
||||
* - Allergies: Warning display for known allergies
|
||||
* - Footer: Current diagnosis and timestamp
|
||||
*
|
||||
* Interaction:
|
||||
* - Touchable card that navigates to detailed patient view
|
||||
* - Visual feedback with activeOpacity for better UX
|
||||
*/
|
||||
export const PatientCard: React.FC<PatientCardProps> = ({
|
||||
patient,
|
||||
onPress,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// DATA PROCESSING
|
||||
// ============================================================================
|
||||
|
||||
// Calculate patient age from date of birth
|
||||
const age = calculateAge(patient.dateOfBirth);
|
||||
|
||||
// Get color coding for priority and status badges
|
||||
const priorityColor = getPriorityColor(patient.priority); // Color based on priority level
|
||||
const statusColor = getStatusColor(patient.status); // Color based on patient status
|
||||
|
||||
/**
|
||||
* formatVitalSigns Function
|
||||
*
|
||||
* Purpose: Format vital signs data for display in the card
|
||||
*
|
||||
* Returns:
|
||||
* - bp: Blood pressure in systolic/diastolic format
|
||||
* - hr: Heart rate value
|
||||
* - temp: Temperature in Celsius
|
||||
* - o2: Oxygen saturation percentage
|
||||
*/
|
||||
const formatVitalSigns = () => {
|
||||
const { vitalSigns } = patient;
|
||||
return {
|
||||
bp: `${vitalSigns.bloodPressure.systolic}/${vitalSigns.bloodPressure.diastolic}`, // Format: 120/80
|
||||
hr: vitalSigns.heartRate.value, // Heart rate: 75
|
||||
temp: vitalSigns.temperature.value, // Temperature: 37.2
|
||||
o2: vitalSigns.oxygenSaturation.value, // Oxygen: 98
|
||||
};
|
||||
};
|
||||
|
||||
// Format vital signs for display
|
||||
const vitals = formatVitalSigns();
|
||||
|
||||
// ============================================================================
|
||||
// RENDER SECTION
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.container}
|
||||
onPress={() => onPress(patient)} // Navigate to patient details
|
||||
activeOpacity={0.7} // Visual feedback on touch
|
||||
>
|
||||
{/* ========================================================================
|
||||
* HEADER SECTION - Patient identification and status badges
|
||||
* ======================================================================== */}
|
||||
<View style={styles.header}>
|
||||
{/* Patient information section */}
|
||||
<View style={styles.patientInfo}>
|
||||
{/* Patient full name */}
|
||||
<Text style={styles.patientName}>
|
||||
{patient.firstName} {patient.lastName}
|
||||
</Text>
|
||||
{/* Patient demographics and MRN */}
|
||||
<Text style={styles.patientDetails}>
|
||||
{age} years • {patient.gender} • MRN: {patient.mrn}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Status badges section */}
|
||||
<View style={styles.badges}>
|
||||
{/* Priority badge with color coding */}
|
||||
<View style={[styles.badge, { backgroundColor: priorityColor + '20' }]}>
|
||||
<Text style={[styles.badgeText, { color: priorityColor }]}>
|
||||
{patient.priority}
|
||||
</Text>
|
||||
</View>
|
||||
{/* Status badge with color coding */}
|
||||
<View style={[styles.badge, { backgroundColor: statusColor + '20' }]}>
|
||||
<Text style={[styles.badgeText, { color: statusColor }]}>
|
||||
{patient.status}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ========================================================================
|
||||
* LOCATION SECTION - Bed assignment and attending physician
|
||||
* ======================================================================== */}
|
||||
<View style={styles.locationInfo}>
|
||||
{/* Bed and room location */}
|
||||
<Text style={styles.locationText}>
|
||||
Bed {patient.bedNumber} • Room {patient.roomNumber}
|
||||
</Text>
|
||||
{/* Attending physician */}
|
||||
<Text style={styles.attendingText}>
|
||||
Dr. {patient.attendingPhysician}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* ========================================================================
|
||||
* VITAL SIGNS SECTION - Real-time patient vitals
|
||||
* ======================================================================== */}
|
||||
<View style={styles.vitalSigns}>
|
||||
{/* Blood Pressure */}
|
||||
<View style={styles.vitalItem}>
|
||||
<Text style={styles.vitalLabel}>BP</Text>
|
||||
<Text style={styles.vitalValue}>{vitals.bp}</Text>
|
||||
</View>
|
||||
{/* Heart Rate */}
|
||||
<View style={styles.vitalItem}>
|
||||
<Text style={styles.vitalLabel}>HR</Text>
|
||||
<Text style={styles.vitalValue}>{vitals.hr}</Text>
|
||||
</View>
|
||||
{/* Temperature */}
|
||||
<View style={styles.vitalItem}>
|
||||
<Text style={styles.vitalLabel}>Temp</Text>
|
||||
<Text style={styles.vitalValue}>{vitals.temp}°C</Text>
|
||||
</View>
|
||||
{/* Oxygen Saturation */}
|
||||
<View style={styles.vitalItem}>
|
||||
<Text style={styles.vitalLabel}>O₂</Text>
|
||||
<Text style={styles.vitalValue}>{vitals.o2}%</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ========================================================================
|
||||
* ALLERGIES SECTION - Warning display for known allergies
|
||||
* ======================================================================== */}
|
||||
{patient.allergies.length > 0 && (
|
||||
<View style={styles.allergiesContainer}>
|
||||
<Text style={styles.allergiesLabel}>Allergies:</Text>
|
||||
<Text style={styles.allergiesText}>
|
||||
{patient.allergies.map(a => a.name).join(', ')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ========================================================================
|
||||
* FOOTER SECTION - Diagnosis and last update time
|
||||
* ======================================================================== */}
|
||||
<View style={styles.footer}>
|
||||
{/* Current diagnosis */}
|
||||
<Text style={styles.diagnosisText}>
|
||||
{patient.currentDiagnosis}
|
||||
</Text>
|
||||
{/* Last update timestamp */}
|
||||
<Text style={styles.timeText}>
|
||||
Updated {new Date(patient.lastUpdated).toLocaleTimeString()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main card container with shadow and rounded corners
|
||||
container: {
|
||||
backgroundColor: theme.colors.cardBackground,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.md,
|
||||
marginHorizontal: theme.spacing.md,
|
||||
marginVertical: theme.spacing.sm,
|
||||
...theme.shadows.small, // Add subtle shadow for elevation
|
||||
},
|
||||
|
||||
// Header section with patient info and badges
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Patient information container
|
||||
patientInfo: {
|
||||
flex: 1, // Take available space
|
||||
},
|
||||
|
||||
// Patient name styling
|
||||
patientName: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Patient details styling (age, gender, MRN)
|
||||
patientDetails: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Badges container for priority and status
|
||||
badges: {
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing.xs, // Space between badges
|
||||
},
|
||||
|
||||
// Individual badge styling
|
||||
badge: {
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
},
|
||||
|
||||
// Badge text styling
|
||||
badgeText: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
},
|
||||
|
||||
// Location information container
|
||||
locationInfo: {
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Bed and room location text
|
||||
locationText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
color: theme.colors.textPrimary,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
},
|
||||
|
||||
// Attending physician text
|
||||
attendingText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Vital signs container with background
|
||||
vitalSigns: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: theme.colors.backgroundAccent, // Light background for vitals
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.sm,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Individual vital sign item
|
||||
vitalItem: {
|
||||
alignItems: 'center', // Center align label and value
|
||||
},
|
||||
|
||||
// Vital sign label (BP, HR, Temp, O₂)
|
||||
vitalLabel: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
color: theme.colors.textSecondary,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Vital sign value styling
|
||||
vitalValue: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
color: theme.colors.textPrimary,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
},
|
||||
|
||||
// Allergies container
|
||||
allergiesContainer: {
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Allergies label with warning color
|
||||
allergiesLabel: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.warning, // Warning color for allergies
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Allergies text listing
|
||||
allergiesText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Footer container with diagnosis and timestamp
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Current diagnosis text
|
||||
diagnosisText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
color: theme.colors.textPrimary,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
flex: 1, // Take available space
|
||||
},
|
||||
|
||||
// Last update timestamp
|
||||
timeText: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: PatientCard.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
139
app/modules/Dashboard/components/QuickActions.tsx
Normal file
139
app/modules/Dashboard/components/QuickActions.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* File: QuickActions.tsx
|
||||
* Description: Quick actions component providing common emergency actions
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
|
||||
interface QuickAction {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface QuickActionsProps {
|
||||
onQuickAction: (action: QuickAction) => void;
|
||||
}
|
||||
|
||||
const quickActions: QuickAction[] = [
|
||||
{
|
||||
id: 'emergency',
|
||||
title: 'Emergency',
|
||||
icon: '🚨',
|
||||
color: theme.colors.critical,
|
||||
},
|
||||
{
|
||||
id: 'scan',
|
||||
title: 'Order Scan',
|
||||
icon: '📷',
|
||||
color: theme.colors.primary,
|
||||
},
|
||||
{
|
||||
id: 'medication',
|
||||
title: 'Medication',
|
||||
icon: '💊',
|
||||
color: theme.colors.warning,
|
||||
},
|
||||
{
|
||||
id: 'lab',
|
||||
title: 'Lab Work',
|
||||
icon: '🧪',
|
||||
color: theme.colors.info,
|
||||
},
|
||||
{
|
||||
id: 'consult',
|
||||
title: 'Consult',
|
||||
icon: '👨⚕️',
|
||||
color: theme.colors.success,
|
||||
},
|
||||
{
|
||||
id: 'transfer',
|
||||
title: 'Transfer',
|
||||
icon: '🚑',
|
||||
color: theme.colors.secondary,
|
||||
},
|
||||
];
|
||||
|
||||
export const QuickActions: React.FC<QuickActionsProps> = ({
|
||||
onQuickAction,
|
||||
}) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Quick Actions</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContainer}
|
||||
>
|
||||
{quickActions.map((action) => (
|
||||
<TouchableOpacity
|
||||
key={action.id}
|
||||
style={styles.actionButton}
|
||||
onPress={() => onQuickAction(action)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={[styles.iconContainer, { backgroundColor: action.color + '20' }]}>
|
||||
<Text style={styles.icon}>{action.icon}</Text>
|
||||
</View>
|
||||
<Text style={styles.actionTitle}>{action.title}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
scrollContainer: {
|
||||
paddingRight: theme.spacing.md,
|
||||
},
|
||||
actionButton: {
|
||||
alignItems: 'center',
|
||||
marginRight: theme.spacing.lg,
|
||||
minWidth: 80,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
...theme.shadows.small,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 24,
|
||||
},
|
||||
actionTitle: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textPrimary,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: QuickActions.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
91
app/modules/Dashboard/index.ts
Normal file
91
app/modules/Dashboard/index.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Main exports for Dashboard module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Export screens
|
||||
export { default as ERDashboardScreen } from './screens/ERDashboardScreen';
|
||||
|
||||
// Export navigation
|
||||
export {
|
||||
DashboardStackNavigator,
|
||||
DashboardStackParamList,
|
||||
DashboardNavigationProp,
|
||||
DashboardScreenProps,
|
||||
ERDashboardScreenProps,
|
||||
PatientDetailsScreenProps,
|
||||
AlertDetailsScreenProps,
|
||||
DepartmentStatsScreenProps,
|
||||
QuickActionsScreenProps,
|
||||
navigateToERDashboard,
|
||||
navigateToPatientDetails,
|
||||
navigateToAlertDetails,
|
||||
navigateToDepartmentStats,
|
||||
navigateToQuickActions,
|
||||
goBack,
|
||||
resetToERDashboard,
|
||||
replaceWithERDashboard,
|
||||
navigateToERDashboardAndClearStack,
|
||||
navigateToPatientDetailsAndClearStack,
|
||||
navigateToAlertDetailsAndClearStack,
|
||||
} from './navigation';
|
||||
|
||||
// Export components
|
||||
export { default as PatientCard } from './components/PatientCard';
|
||||
export { default as CriticalAlerts } from './components/CriticalAlerts';
|
||||
export { default as DashboardHeader } from './components/DashboardHeader';
|
||||
export { default as QuickActions } from './components/QuickActions';
|
||||
export { default as DepartmentStats } from './components/DepartmentStats';
|
||||
|
||||
// Export Redux
|
||||
export {
|
||||
fetchDashboardData,
|
||||
refreshDashboardData,
|
||||
clearError,
|
||||
setFilter,
|
||||
setSort,
|
||||
updateConnectionStatus,
|
||||
updateLastUpdated,
|
||||
updateDashboardData,
|
||||
} from './redux/dashboardSlice';
|
||||
|
||||
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
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
91
app/modules/Dashboard/navigation/DashboardStackNavigator.tsx
Normal file
91
app/modules/Dashboard/navigation/DashboardStackNavigator.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* File: DashboardStackNavigator.tsx
|
||||
* Description: Stack navigator for dashboard screens within the Dashboard module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
||||
// Import dashboard screens
|
||||
import { ERDashboardScreen } from '../screens/ERDashboardScreen';
|
||||
|
||||
// Import navigation types
|
||||
import { DashboardStackParamList } from './navigationTypes';
|
||||
import { theme } from '../../../theme';
|
||||
|
||||
// Create stack navigator for Dashboard module
|
||||
const Stack = createStackNavigator<DashboardStackParamList>();
|
||||
|
||||
/**
|
||||
* DashboardStackNavigator - Manages navigation between dashboard screens
|
||||
*
|
||||
* This navigator handles the flow between:
|
||||
* - ERDashboardScreen: Main ER dashboard with patient overview
|
||||
* - Future screens: Patient details, alerts, reports, etc.
|
||||
*
|
||||
* Features:
|
||||
* - Clean header styling
|
||||
* - Smooth transitions between screens
|
||||
* - Type-safe navigation parameters
|
||||
* - Healthcare-focused design
|
||||
*/
|
||||
const DashboardStackNavigator: React.FC = () => {
|
||||
return (
|
||||
<Stack.Navigator
|
||||
initialRouteName="ERDashboard"
|
||||
screenOptions={{
|
||||
// Header styling for dashboard screens
|
||||
headerStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
elevation: 0, // Remove shadow on Android
|
||||
shadowOpacity: 0, // Remove shadow on iOS
|
||||
},
|
||||
headerTitleStyle: {
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
fontSize: 18,
|
||||
color: '#212121',
|
||||
},
|
||||
headerTintColor: '#2196F3', // Back button and title color
|
||||
headerBackTitleVisible: false, // Hide back title on iOS
|
||||
cardStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
// Smooth transitions
|
||||
transitionSpec: {
|
||||
open: {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 300,
|
||||
},
|
||||
},
|
||||
close: {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* ER Dashboard Screen - Main dashboard entry point */}
|
||||
<Stack.Screen
|
||||
name="ERDashboard"
|
||||
component={ERDashboardScreen}
|
||||
options={{
|
||||
title: 'ER Dashboard',
|
||||
headerShown: false, // Hide header for main dashboard
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardStackNavigator;
|
||||
|
||||
/*
|
||||
* End of File: DashboardStackNavigator.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
47
app/modules/Dashboard/navigation/index.ts
Normal file
47
app/modules/Dashboard/navigation/index.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Barrel exports for Dashboard module navigation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Export main navigator
|
||||
export { default as DashboardStackNavigator } from './DashboardStackNavigator';
|
||||
|
||||
// Export navigation types
|
||||
export type {
|
||||
DashboardStackParamList,
|
||||
DashboardNavigationProp,
|
||||
DashboardScreenProps,
|
||||
ERDashboardScreenProps,
|
||||
PatientDetailsScreenProps,
|
||||
AlertDetailsScreenProps,
|
||||
DepartmentStatsScreenProps,
|
||||
QuickActionsScreenProps,
|
||||
ERDashboardScreenParams,
|
||||
PatientDetailsScreenParams,
|
||||
AlertDetailsScreenParams,
|
||||
DepartmentStatsScreenParams,
|
||||
QuickActionsScreenParams,
|
||||
} from './navigationTypes';
|
||||
|
||||
// Export navigation utilities
|
||||
export {
|
||||
navigateToERDashboard,
|
||||
navigateToPatientDetails,
|
||||
navigateToAlertDetails,
|
||||
navigateToDepartmentStats,
|
||||
navigateToQuickActions,
|
||||
goBack,
|
||||
resetToERDashboard,
|
||||
replaceWithERDashboard,
|
||||
navigateToERDashboardAndClearStack,
|
||||
navigateToPatientDetailsAndClearStack,
|
||||
navigateToAlertDetailsAndClearStack,
|
||||
} from './navigationUtils';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
171
app/modules/Dashboard/navigation/navigationTypes.ts
Normal file
171
app/modules/Dashboard/navigation/navigationTypes.ts
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* File: navigationTypes.ts
|
||||
* Description: TypeScript types for Dashboard module navigation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { Patient, Alert as AlertType, ERDashboard } from '../../../shared/types';
|
||||
|
||||
/**
|
||||
* DashboardStackParamList - Defines the parameter list for Dashboard stack navigator
|
||||
*
|
||||
* This interface defines all the screens available in the Dashboard module
|
||||
* and their associated navigation parameters.
|
||||
*/
|
||||
export type DashboardStackParamList = {
|
||||
// ER Dashboard screen - Main dashboard with patient overview
|
||||
ERDashboard: ERDashboardScreenParams;
|
||||
|
||||
// Patient Details screen - Detailed patient information
|
||||
PatientDetails: PatientDetailsScreenParams;
|
||||
|
||||
// Alert Details screen - Detailed alert information
|
||||
AlertDetails: AlertDetailsScreenParams;
|
||||
|
||||
// Department Stats screen - Department-specific statistics
|
||||
DepartmentStats: DepartmentStatsScreenParams;
|
||||
|
||||
// Quick Actions screen - Quick action menu
|
||||
QuickActions: QuickActionsScreenParams;
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardNavigationProp - Type for navigation prop in Dashboard screens
|
||||
*
|
||||
* This type provides type-safe navigation methods for screens
|
||||
* within the Dashboard module.
|
||||
*/
|
||||
export type DashboardNavigationProp = StackNavigationProp<DashboardStackParamList>;
|
||||
|
||||
/**
|
||||
* DashboardScreenProps - Base props interface for Dashboard screens
|
||||
*
|
||||
* This interface provides the common props that all Dashboard screens
|
||||
* will receive, including navigation and route.
|
||||
*/
|
||||
export interface DashboardScreenProps<T extends keyof DashboardStackParamList> {
|
||||
navigation: DashboardNavigationProp;
|
||||
route: {
|
||||
key: string;
|
||||
name: T;
|
||||
params: DashboardStackParamList[T];
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SCREEN PARAMETER TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* ERDashboardScreenParams
|
||||
*
|
||||
* Purpose: Parameters passed to the ER dashboard screen
|
||||
*
|
||||
* Parameters:
|
||||
* - filter: Optional filter to apply to dashboard data
|
||||
* - refresh: Optional flag to force refresh
|
||||
*/
|
||||
export interface ERDashboardScreenParams {
|
||||
filter?: 'all' | 'critical' | 'active' | 'pending';
|
||||
refresh?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* PatientDetailsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the patient details screen
|
||||
*
|
||||
* Parameters:
|
||||
* - patientId: Required patient ID to display details
|
||||
* - patient: Optional patient data to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface PatientDetailsScreenParams {
|
||||
patientId: string;
|
||||
patient?: Patient;
|
||||
fromScreen?: keyof DashboardStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertDetailsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the alert details screen
|
||||
*
|
||||
* Parameters:
|
||||
* - alertId: Required alert ID to display details
|
||||
* - alert: Optional alert data to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface AlertDetailsScreenParams {
|
||||
alertId: string;
|
||||
alert?: AlertType;
|
||||
fromScreen?: keyof DashboardStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* DepartmentStatsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the department stats screen
|
||||
*
|
||||
* Parameters:
|
||||
* - department: Required department name
|
||||
* - dateRange: Optional date range for statistics
|
||||
*/
|
||||
export interface DepartmentStatsScreenParams {
|
||||
department: string;
|
||||
dateRange?: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* QuickActionsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the quick actions screen
|
||||
*
|
||||
* Parameters:
|
||||
* - actionType: Optional action type to pre-select
|
||||
* - patientId: Optional patient ID for patient-specific actions
|
||||
*/
|
||||
export interface QuickActionsScreenParams {
|
||||
actionType?: 'emergency' | 'scan' | 'report' | 'consultation';
|
||||
patientId?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SCREEN-SPECIFIC PROPS TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* ERDashboardScreenProps - Props for ERDashboardScreen component
|
||||
*/
|
||||
export type ERDashboardScreenProps = DashboardScreenProps<'ERDashboard'>;
|
||||
|
||||
/**
|
||||
* PatientDetailsScreenProps - Props for PatientDetailsScreen component
|
||||
*/
|
||||
export type PatientDetailsScreenProps = DashboardScreenProps<'PatientDetails'>;
|
||||
|
||||
/**
|
||||
* AlertDetailsScreenProps - Props for AlertDetailsScreen component
|
||||
*/
|
||||
export type AlertDetailsScreenProps = DashboardScreenProps<'AlertDetails'>;
|
||||
|
||||
/**
|
||||
* DepartmentStatsScreenProps - Props for DepartmentStatsScreen component
|
||||
*/
|
||||
export type DepartmentStatsScreenProps = DashboardScreenProps<'DepartmentStats'>;
|
||||
|
||||
/**
|
||||
* QuickActionsScreenProps - Props for QuickActionsScreen component
|
||||
*/
|
||||
export type QuickActionsScreenProps = DashboardScreenProps<'QuickActions'>;
|
||||
|
||||
/*
|
||||
* End of File: navigationTypes.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
209
app/modules/Dashboard/navigation/navigationUtils.ts
Normal file
209
app/modules/Dashboard/navigation/navigationUtils.ts
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* File: navigationUtils.ts
|
||||
* Description: Navigation utilities for Dashboard module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { DashboardNavigationProp } from './navigationTypes';
|
||||
import { Patient, Alert as AlertType } from '../../../shared/types';
|
||||
|
||||
/**
|
||||
* DashboardNavigationUtils - Utility functions for Dashboard module navigation
|
||||
*
|
||||
* This module provides helper functions for common navigation patterns
|
||||
* within the Dashboard module, ensuring consistent navigation behavior.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Navigate to ER Dashboard screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the dashboard
|
||||
*/
|
||||
export const navigateToERDashboard = (
|
||||
navigation: DashboardNavigationProp,
|
||||
params?: {
|
||||
filter?: 'all' | 'critical' | 'active' | 'pending';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.navigate('ERDashboard', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Patient Details screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param patientId - Required patient ID
|
||||
* @param patient - Optional patient data
|
||||
* @param fromScreen - Optional source screen
|
||||
*/
|
||||
export const navigateToPatientDetails = (
|
||||
navigation: DashboardNavigationProp,
|
||||
patientId: string,
|
||||
patient?: Patient,
|
||||
fromScreen?: keyof import('./navigationTypes').DashboardStackParamList
|
||||
): void => {
|
||||
navigation.navigate('PatientDetails', {
|
||||
patientId,
|
||||
patient,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Alert Details screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param alertId - Required alert ID
|
||||
* @param alert - Optional alert data
|
||||
* @param fromScreen - Optional source screen
|
||||
*/
|
||||
export const navigateToAlertDetails = (
|
||||
navigation: DashboardNavigationProp,
|
||||
alertId: string,
|
||||
alert?: AlertType,
|
||||
fromScreen?: keyof import('./navigationTypes').DashboardStackParamList
|
||||
): void => {
|
||||
navigation.navigate('AlertDetails', {
|
||||
alertId,
|
||||
alert,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Department Stats screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param department - Required department name
|
||||
* @param dateRange - Optional date range for statistics
|
||||
*/
|
||||
export const navigateToDepartmentStats = (
|
||||
navigation: DashboardNavigationProp,
|
||||
department: string,
|
||||
dateRange?: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
}
|
||||
): void => {
|
||||
navigation.navigate('DepartmentStats', {
|
||||
department,
|
||||
dateRange,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Quick Actions screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param actionType - Optional action type to pre-select
|
||||
* @param patientId - Optional patient ID for patient-specific actions
|
||||
*/
|
||||
export const navigateToQuickActions = (
|
||||
navigation: DashboardNavigationProp,
|
||||
actionType?: 'emergency' | 'scan' | 'report' | 'consultation',
|
||||
patientId?: string
|
||||
): void => {
|
||||
navigation.navigate('QuickActions', {
|
||||
actionType,
|
||||
patientId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Go back to previous screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const goBack = (navigation: DashboardNavigationProp): void => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset navigation stack to ER Dashboard screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the dashboard
|
||||
*/
|
||||
export const resetToERDashboard = (
|
||||
navigation: DashboardNavigationProp,
|
||||
params?: {
|
||||
filter?: 'all' | 'critical' | 'active' | 'pending';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'ERDashboard', params }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace current screen with ER Dashboard screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the dashboard
|
||||
*/
|
||||
export const replaceWithERDashboard = (
|
||||
navigation: DashboardNavigationProp,
|
||||
params?: {
|
||||
filter?: 'all' | 'critical' | 'active' | 'pending';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.replace('ERDashboard', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to ER Dashboard and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the dashboard
|
||||
*/
|
||||
export const navigateToERDashboardAndClearStack = (
|
||||
navigation: DashboardNavigationProp,
|
||||
params?: {
|
||||
filter?: 'all' | 'critical' | 'active' | 'pending';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'ERDashboard', params }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Patient Details and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param patientId - Required patient ID
|
||||
* @param patient - Optional patient data
|
||||
*/
|
||||
export const navigateToPatientDetailsAndClearStack = (
|
||||
navigation: DashboardNavigationProp,
|
||||
patientId: string,
|
||||
patient?: Patient
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'PatientDetails', params: { patientId, patient } }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Alert Details and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param alertId - Required alert ID
|
||||
* @param alert - Optional alert data
|
||||
*/
|
||||
export const navigateToAlertDetailsAndClearStack = (
|
||||
navigation: DashboardNavigationProp,
|
||||
alertId: string,
|
||||
alert?: AlertType
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'AlertDetails', params: { alertId, alert } }],
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: navigationUtils.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
341
app/modules/Dashboard/redux/alertsSlice.ts
Normal file
341
app/modules/Dashboard/redux/alertsSlice.ts
Normal file
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
280
app/modules/Dashboard/redux/dashboardSlice.ts
Normal file
280
app/modules/Dashboard/redux/dashboardSlice.ts
Normal file
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* File: dashboardSlice.ts
|
||||
* Description: Dashboard state management slice
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { ERDashboard, DashboardState } from '../../../shared/types';
|
||||
|
||||
// ============================================================================
|
||||
// ASYNC THUNKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch Dashboard Data Async Thunk
|
||||
*
|
||||
* Purpose: Fetch dashboard data from API
|
||||
*
|
||||
* @returns Promise with dashboard data or error
|
||||
*/
|
||||
export const fetchDashboardData = createAsyncThunk(
|
||||
'dashboard/fetchDashboardData',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
// Simulate API call with timeout
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
// Mock dashboard data
|
||||
const mockDashboard: ERDashboard = {
|
||||
totalPatients: 24,
|
||||
criticalPatients: 3,
|
||||
pendingScans: 8,
|
||||
recentReports: 12,
|
||||
bedOccupancy: 85,
|
||||
departmentStats: {
|
||||
emergency: 8,
|
||||
trauma: 4,
|
||||
cardiac: 3,
|
||||
neurology: 2,
|
||||
pediatrics: 5,
|
||||
icu: 2,
|
||||
},
|
||||
shiftInfo: {
|
||||
currentShift: 'DAY',
|
||||
startTime: new Date(),
|
||||
endTime: new Date(),
|
||||
attendingPhysician: 'Dr. Smith',
|
||||
residents: ['Dr. Johnson', 'Dr. Williams'],
|
||||
nurses: ['Nurse Brown', 'Nurse Davis'],
|
||||
},
|
||||
lastUpdated: new Date(),
|
||||
};
|
||||
|
||||
return mockDashboard;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to fetch dashboard data.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Refresh Dashboard Data Async Thunk
|
||||
*
|
||||
* Purpose: Refresh dashboard data
|
||||
*
|
||||
* @returns Promise with updated dashboard data or error
|
||||
*/
|
||||
export const refreshDashboardData = createAsyncThunk(
|
||||
'dashboard/refreshDashboardData',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Mock refreshed dashboard data
|
||||
const mockDashboard: ERDashboard = {
|
||||
totalPatients: 26,
|
||||
criticalPatients: 2,
|
||||
pendingScans: 6,
|
||||
recentReports: 15,
|
||||
bedOccupancy: 87,
|
||||
departmentStats: {
|
||||
emergency: 9,
|
||||
trauma: 3,
|
||||
cardiac: 4,
|
||||
neurology: 2,
|
||||
pediatrics: 6,
|
||||
icu: 2,
|
||||
},
|
||||
shiftInfo: {
|
||||
currentShift: 'DAY',
|
||||
startTime: new Date(),
|
||||
endTime: new Date(),
|
||||
attendingPhysician: 'Dr. Smith',
|
||||
residents: ['Dr. Johnson', 'Dr. Williams'],
|
||||
nurses: ['Nurse Brown', 'Nurse Davis'],
|
||||
},
|
||||
lastUpdated: new Date(),
|
||||
};
|
||||
|
||||
return mockDashboard;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to refresh dashboard data.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// INITIAL STATE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initial Dashboard State
|
||||
*
|
||||
* Purpose: Define the initial state for dashboard
|
||||
*
|
||||
* Features:
|
||||
* - Dashboard data and statistics
|
||||
* - Loading states for async operations
|
||||
* - Error handling and messages
|
||||
* - Real-time updates tracking
|
||||
*/
|
||||
const initialState: DashboardState = {
|
||||
// Dashboard data
|
||||
dashboard: null,
|
||||
|
||||
// Loading states
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
|
||||
// Error handling
|
||||
error: null,
|
||||
|
||||
// Real-time updates
|
||||
lastUpdated: null,
|
||||
isConnected: true,
|
||||
|
||||
// Filters and preferences
|
||||
selectedFilter: 'all',
|
||||
sortBy: 'priority',
|
||||
sortOrder: 'desc',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// DASHBOARD SLICE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Dashboard Slice
|
||||
*
|
||||
* Purpose: Redux slice for dashboard state management
|
||||
*
|
||||
* Features:
|
||||
* - Dashboard data management
|
||||
* - Real-time updates
|
||||
* - Filtering and sorting
|
||||
* - Error handling
|
||||
* - Loading states
|
||||
*/
|
||||
const dashboardSlice = createSlice({
|
||||
name: 'dashboard',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* Clear Error Action
|
||||
*
|
||||
* Purpose: Clear dashboard errors
|
||||
*/
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Filter Action
|
||||
*
|
||||
* Purpose: Set dashboard filter
|
||||
*/
|
||||
setFilter: (state, action: PayloadAction<'all' | 'critical' | 'active' | 'pending'>) => {
|
||||
state.selectedFilter = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Sort Action
|
||||
*
|
||||
* Purpose: Set dashboard sort options
|
||||
*/
|
||||
setSort: (state, action: PayloadAction<{ by: string; order: 'asc' | 'desc' }>) => {
|
||||
state.sortBy = action.payload.by;
|
||||
state.sortOrder = action.payload.order;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Connection Status Action
|
||||
*
|
||||
* Purpose: Update real-time connection status
|
||||
*/
|
||||
updateConnectionStatus: (state, action: PayloadAction<boolean>) => {
|
||||
state.isConnected = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Last Updated Action
|
||||
*
|
||||
* Purpose: Update last updated timestamp
|
||||
*/
|
||||
updateLastUpdated: (state, action: PayloadAction<Date>) => {
|
||||
state.lastUpdated = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Dashboard Data Action
|
||||
*
|
||||
* Purpose: Update dashboard data manually
|
||||
*/
|
||||
updateDashboardData: (state, action: PayloadAction<Partial<ERDashboard>>) => {
|
||||
if (state.dashboard) {
|
||||
state.dashboard = { ...state.dashboard, ...action.payload };
|
||||
state.lastUpdated = new Date();
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Fetch Dashboard Data
|
||||
builder
|
||||
.addCase(fetchDashboardData.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchDashboardData.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.dashboard = action.payload;
|
||||
state.lastUpdated = action.payload.lastUpdated;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchDashboardData.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Refresh Dashboard Data
|
||||
builder
|
||||
.addCase(refreshDashboardData.pending, (state) => {
|
||||
state.isRefreshing = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(refreshDashboardData.fulfilled, (state, action) => {
|
||||
state.isRefreshing = false;
|
||||
state.dashboard = action.payload;
|
||||
state.lastUpdated = action.payload.lastUpdated;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(refreshDashboardData.rejected, (state, action) => {
|
||||
state.isRefreshing = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export const {
|
||||
clearError,
|
||||
setFilter,
|
||||
setSort,
|
||||
updateConnectionStatus,
|
||||
updateLastUpdated,
|
||||
updateDashboardData,
|
||||
} = dashboardSlice.actions;
|
||||
|
||||
export default dashboardSlice.reducer;
|
||||
|
||||
/*
|
||||
* End of File: dashboardSlice.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
330
app/modules/Dashboard/redux/uiSlice.ts
Normal file
330
app/modules/Dashboard/redux/uiSlice.ts
Normal file
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
752
app/modules/Dashboard/screens/ERDashboardScreen.tsx
Normal file
752
app/modules/Dashboard/screens/ERDashboardScreen.tsx
Normal file
@ -0,0 +1,752 @@
|
||||
/*
|
||||
* File: ERDashboardScreen.tsx
|
||||
* Description: Main ER dashboard screen displaying patient list, critical alerts, and department statistics
|
||||
* 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 { QuickActions } from '../components/QuickActions';
|
||||
import { DepartmentStats } from '../components/DepartmentStats';
|
||||
|
||||
/**
|
||||
* ERDashboardScreenProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the ERDashboardScreen component
|
||||
*
|
||||
* Props:
|
||||
* - navigation: React Navigation object for screen navigation
|
||||
*/
|
||||
interface ERDashboardScreenProps {
|
||||
navigation: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* ERDashboardScreen Component
|
||||
*
|
||||
* Purpose: Main dashboard screen for Emergency Department physicians
|
||||
*
|
||||
* Dashboard Features:
|
||||
* 1. Real-time patient overview with filtering capabilities
|
||||
* 2. Critical alerts display for immediate attention
|
||||
* 3. Quick action buttons for common tasks
|
||||
* 4. Department statistics and bed occupancy
|
||||
* 5. Pull-to-refresh functionality for live updates
|
||||
* 6. Internal data generation for demonstration
|
||||
*
|
||||
* Patient Filtering:
|
||||
* - All: Shows all patients in the system
|
||||
* - Critical: Shows only patients with critical priority
|
||||
* - Active: Shows only patients with active status
|
||||
*
|
||||
* Data Flow:
|
||||
* 1. Generate mock data internally for demonstration
|
||||
* 2. Filter patients based on selected filter
|
||||
* 3. Display critical alerts at the top
|
||||
* 4. Show patient cards in scrollable list
|
||||
* 5. Handle refresh and navigation interactions
|
||||
*/
|
||||
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 patients are displayed
|
||||
const [selectedFilter, setSelectedFilter] = useState<'all' | 'critical' | 'active'>('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 for demonstration
|
||||
*
|
||||
* Returns: ERDashboard object with realistic ER statistics
|
||||
*/
|
||||
const generateMockDashboard = (): ERDashboard => ({
|
||||
totalPatients: 24, // Total number of patients in ER
|
||||
criticalPatients: 3, // Number of patients requiring immediate attention
|
||||
pendingScans: 8, // Number of scans waiting for review
|
||||
recentReports: 12, // Number of reports generated recently
|
||||
bedOccupancy: 85, // Percentage of beds currently occupied
|
||||
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
|
||||
});
|
||||
|
||||
/**
|
||||
* generateMockPatients Function
|
||||
*
|
||||
* Purpose: Generate mock patient data for demonstration
|
||||
*
|
||||
* Returns: Array of Patient objects with realistic medical data
|
||||
*/
|
||||
const generateMockPatients = (): Patient[] => [
|
||||
{
|
||||
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(),
|
||||
},
|
||||
{
|
||||
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: 'MEDIUM' as const,
|
||||
department: 'Cardiac',
|
||||
attendingPhysician: 'Dr. Williams',
|
||||
allergies: [
|
||||
{
|
||||
id: '2',
|
||||
name: 'Aspirin',
|
||||
severity: 'MODERATE' as const,
|
||||
reaction: 'Stomach upset'
|
||||
},
|
||||
],
|
||||
medications: [
|
||||
{
|
||||
id: '2',
|
||||
name: 'Nitroglycerin',
|
||||
dosage: '0.4mg',
|
||||
frequency: 'As needed',
|
||||
route: 'Sublingual',
|
||||
startDate: new Date(),
|
||||
status: 'ACTIVE' as const,
|
||||
prescribedBy: 'Dr. Williams',
|
||||
},
|
||||
],
|
||||
vitalSigns: {
|
||||
bloodPressure: { systolic: 160, diastolic: 100, timestamp: new Date() },
|
||||
heartRate: { value: 110, timestamp: new Date() },
|
||||
temperature: { value: 36.9, timestamp: new Date() },
|
||||
respiratoryRate: { value: 20, timestamp: new Date() },
|
||||
oxygenSaturation: { value: 95, timestamp: new Date() },
|
||||
},
|
||||
medicalHistory: [],
|
||||
currentDiagnosis: 'Hypertension, chest pain',
|
||||
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: 'Neurology',
|
||||
attendingPhysician: 'Dr. Davis',
|
||||
allergies: [],
|
||||
medications: [
|
||||
{
|
||||
id: '3',
|
||||
name: 'Mannitol',
|
||||
dosage: '100ml',
|
||||
frequency: 'Every 6 hours',
|
||||
route: 'IV',
|
||||
startDate: new Date(),
|
||||
status: 'ACTIVE' as const,
|
||||
prescribedBy: 'Dr. Davis',
|
||||
},
|
||||
],
|
||||
vitalSigns: {
|
||||
bloodPressure: { systolic: 180, diastolic: 110, timestamp: new Date() },
|
||||
heartRate: { value: 60, timestamp: new Date() },
|
||||
temperature: { value: 38.5, timestamp: new Date() },
|
||||
respiratoryRate: { value: 12, timestamp: new Date() },
|
||||
oxygenSaturation: { value: 92, timestamp: new Date() },
|
||||
},
|
||||
medicalHistory: [],
|
||||
currentDiagnosis: 'Suspected intracranial hemorrhage',
|
||||
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: 'Pediatrics',
|
||||
attendingPhysician: 'Dr. Brown',
|
||||
allergies: [
|
||||
{
|
||||
id: '3',
|
||||
name: 'Latex',
|
||||
severity: 'SEVERE' as const,
|
||||
reaction: 'Contact dermatitis'
|
||||
},
|
||||
],
|
||||
medications: [],
|
||||
vitalSigns: {
|
||||
bloodPressure: { systolic: 110, diastolic: 70, timestamp: new Date() },
|
||||
heartRate: { value: 85, timestamp: new Date() },
|
||||
temperature: { value: 37.8, timestamp: new Date() },
|
||||
respiratoryRate: { value: 22, timestamp: new Date() },
|
||||
oxygenSaturation: { value: 97, timestamp: new Date() },
|
||||
},
|
||||
medicalHistory: [],
|
||||
currentDiagnosis: 'Fever of unknown origin',
|
||||
lastUpdated: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* generateMockAlerts Function
|
||||
*
|
||||
* Purpose: Generate mock alert data for demonstration
|
||||
*
|
||||
* Returns: Array of Alert objects with realistic medical alerts
|
||||
*/
|
||||
const generateMockAlerts = (): AlertType[] => [
|
||||
{
|
||||
id: '1',
|
||||
type: 'CRITICAL_FINDING' as const, // Type of alert
|
||||
priority: 'CRITICAL' as const, // Priority level
|
||||
title: 'Critical Finding Detected',
|
||||
message: 'AI has detected a potential brain bleed in CT scan. Immediate review required.',
|
||||
patientId: '4', // Associated patient
|
||||
patientName: 'Sarah Wilson',
|
||||
bedNumber: 'D4',
|
||||
timestamp: new Date(), // When alert was generated
|
||||
isRead: false, // Whether alert has been read
|
||||
isAcknowledged: false, // Whether alert has been acknowledged
|
||||
actionRequired: true, // Whether action is required
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'VITAL_SIGNS_ALERT' as const,
|
||||
priority: 'HIGH' as const,
|
||||
title: 'Vital Sign Alert',
|
||||
message: 'Blood pressure elevated: 180/110. Patient requires immediate attention.',
|
||||
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: 'Medication Due',
|
||||
message: 'Morphine due for patient John Doe in 15 minutes.',
|
||||
patientId: '1',
|
||||
patientName: 'John Doe',
|
||||
bedNumber: 'A1',
|
||||
timestamp: new Date(Date.now() - 600000), // 10 minutes ago
|
||||
isRead: true,
|
||||
isAcknowledged: true,
|
||||
actionRequired: false,
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useEffect for initial data loading
|
||||
*
|
||||
* Purpose: Load initial mock data when component mounts
|
||||
*
|
||||
* Flow:
|
||||
* 1. Set loading state to true
|
||||
* 2. Generate mock data
|
||||
* 3. Set data in state
|
||||
* 4. Set loading state to false
|
||||
*/
|
||||
useEffect(() => {
|
||||
const loadInitialData = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate API call delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 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
|
||||
*
|
||||
* Flow:
|
||||
* 1. Set refreshing state to true (show loading indicator)
|
||||
* 2. Simulate API call with delay
|
||||
* 3. Update data with fresh mock data
|
||||
* 4. Set refreshing state to false (hide loading indicator)
|
||||
*/
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true); // Show refresh indicator
|
||||
|
||||
// Simulate API call with 1-second delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Update data with fresh mock data
|
||||
setDashboard(generateMockDashboard());
|
||||
setPatients(generateMockPatients());
|
||||
setAlerts(generateMockAlerts());
|
||||
|
||||
setRefreshing(false); // Hide refresh indicator
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePatientPress Function
|
||||
*
|
||||
* Purpose: Handle patient card press navigation
|
||||
*
|
||||
* @param patient - Patient object that was pressed
|
||||
*/
|
||||
const handlePatientPress = (patient: Patient) => {
|
||||
// TODO: Navigate to patient details screen
|
||||
console.log('Patient pressed:', patient.firstName, patient.lastName);
|
||||
Alert.alert('Patient Details', `Navigate to ${patient.firstName} ${patient.lastName}'s details`);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleAlertPress Function
|
||||
*
|
||||
* Purpose: Handle alert press interaction
|
||||
*
|
||||
* @param alert - Alert object that was pressed
|
||||
*/
|
||||
const handleAlertPress = (alert: AlertType) => {
|
||||
// TODO: Navigate to alert details or patient details
|
||||
console.log('Alert pressed:', alert.title);
|
||||
Alert.alert('Alert Details', alert.message);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// DATA PROCESSING
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* filteredPatients - Computed property
|
||||
*
|
||||
* Purpose: Filter patients based on selected filter criteria
|
||||
*
|
||||
* Filter Options:
|
||||
* - 'all': Show all patients regardless of status or priority
|
||||
* - 'critical': Show only patients with CRITICAL priority
|
||||
* - 'active': Show only patients with ACTIVE status
|
||||
*/
|
||||
const filteredPatients = patients.filter(patient => {
|
||||
switch (selectedFilter) {
|
||||
case 'critical':
|
||||
return patient.priority === 'CRITICAL'; // Only critical priority patients
|
||||
case 'active':
|
||||
return patient.status === 'ACTIVE'; // Only active status patients
|
||||
default:
|
||||
return true; // All patients
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* criticalAlerts - Computed property
|
||||
*
|
||||
* Purpose: Extract critical alerts from all alerts for immediate display
|
||||
*
|
||||
* Filter: Only alerts with CRITICAL priority that require immediate attention
|
||||
*/
|
||||
const criticalAlerts = alerts.filter(alert => alert.priority === 'CRITICAL');
|
||||
|
||||
/**
|
||||
* pendingScans - Computed property
|
||||
*
|
||||
* Purpose: Identify patients with pending scans or high priority needs
|
||||
*
|
||||
* Filter: Patients with PENDING status or HIGH priority
|
||||
*/
|
||||
const pendingScans = patients.filter(patient =>
|
||||
patient.status === 'PENDING' || patient.priority === 'HIGH'
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// 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)} // Navigate to patient details
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* renderHeader Function
|
||||
*
|
||||
* Purpose: Render the dashboard header section with all dashboard components
|
||||
*
|
||||
* Components included:
|
||||
* - DashboardHeader: Shows shift info and key statistics
|
||||
* - CriticalAlerts: Displays urgent alerts requiring attention
|
||||
* - QuickActions: Provides quick access to common functions
|
||||
* - DepartmentStats: Shows department-wise patient distribution
|
||||
* - Filter buttons: Allow filtering of patient list
|
||||
*/
|
||||
const renderHeader = () => (
|
||||
<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} // Handle alert interaction
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Quick action buttons for common tasks */}
|
||||
<QuickActions
|
||||
onQuickAction={(action) => {
|
||||
// TODO: Implement quick action handlers
|
||||
console.log('Quick action:', action);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Department statistics showing patient distribution */}
|
||||
{dashboard && <DepartmentStats stats={dashboard.departmentStats} />}
|
||||
|
||||
{/* Patient filter section with filter buttons */}
|
||||
<View style={styles.filterContainer}>
|
||||
<Text style={styles.sectionTitle}>Patients</Text>
|
||||
<View style={styles.filterButtons}>
|
||||
{/* All patients 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 patients 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>
|
||||
|
||||
{/* Active patients filter button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.filterButton,
|
||||
selectedFilter === 'active' && styles.filterButtonActive
|
||||
]}
|
||||
onPress={() => setSelectedFilter('active')}
|
||||
>
|
||||
<Text style={[
|
||||
styles.filterButtonText,
|
||||
selectedFilter === 'active' && styles.filterButtonTextActive
|
||||
]}>
|
||||
Active ({patients.filter(p => p.status === 'ACTIVE').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 Dashboard...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* FlatList for efficient rendering of patient cards */}
|
||||
<FlatList
|
||||
data={filteredPatients} // Filtered patient data
|
||||
renderItem={renderPatientCard} // Render function for each patient
|
||||
keyExtractor={(item) => item.id} // Unique key for each patient
|
||||
ListHeaderComponent={renderHeader} // Header with dashboard components
|
||||
contentContainerStyle={styles.listContainer} // Container styling
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing} // Show refresh indicator
|
||||
onRefresh={handleRefresh} // Handle refresh action
|
||||
colors={[theme.colors.primary]} // Refresh indicator color
|
||||
tintColor={theme.colors.primary} // iOS refresh indicator color
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false} // Hide scroll indicator for cleaner look
|
||||
/>
|
||||
</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, // Add bottom padding for tab bar
|
||||
},
|
||||
|
||||
// Header section containing dashboard components
|
||||
header: {
|
||||
paddingHorizontal: theme.spacing.md, // Horizontal padding for content
|
||||
},
|
||||
|
||||
// Container for patient filter section
|
||||
filterContainer: {
|
||||
marginTop: theme.spacing.lg, // Top margin for separation
|
||||
marginBottom: theme.spacing.md, // Bottom margin for list spacing
|
||||
},
|
||||
|
||||
// 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', // Horizontal layout
|
||||
gap: theme.spacing.sm, // Space between buttons
|
||||
},
|
||||
|
||||
// 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, // Primary color background
|
||||
borderColor: theme.colors.primary, // Primary color border
|
||||
},
|
||||
|
||||
// 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, // White text on primary background
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: ERDashboardScreen.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
475
app/modules/PatientCare/redux/patientCareSlice.ts
Normal file
475
app/modules/PatientCare/redux/patientCareSlice.ts
Normal file
@ -0,0 +1,475 @@
|
||||
/*
|
||||
* File: patientCareSlice.ts
|
||||
* Description: Patient care state management slice
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { Patient, PatientCareState } from '../../../shared/types';
|
||||
|
||||
// ============================================================================
|
||||
// ASYNC THUNKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch Patients Async Thunk
|
||||
*
|
||||
* Purpose: Fetch patients list from API
|
||||
*
|
||||
* @returns Promise with patients data or error
|
||||
*/
|
||||
export const fetchPatients = createAsyncThunk(
|
||||
'patientCare/fetchPatients',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
// Mock patients data
|
||||
const mockPatients: Patient[] = [
|
||||
{
|
||||
id: '1',
|
||||
mrn: 'MRN001',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
dateOfBirth: new Date('1985-03-15'),
|
||||
gender: 'MALE',
|
||||
age: 38,
|
||||
bedNumber: 'A1',
|
||||
roomNumber: '101',
|
||||
admissionDate: new Date('2024-01-15'),
|
||||
status: 'ACTIVE',
|
||||
priority: 'CRITICAL',
|
||||
department: 'Emergency',
|
||||
attendingPhysician: 'Dr. Smith',
|
||||
allergies: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Penicillin',
|
||||
severity: 'SEVERE',
|
||||
reaction: 'Anaphylaxis',
|
||||
},
|
||||
],
|
||||
medications: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Morphine',
|
||||
dosage: '2mg',
|
||||
frequency: 'Every 4 hours',
|
||||
route: 'IV',
|
||||
startDate: new Date(),
|
||||
status: 'ACTIVE',
|
||||
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',
|
||||
lastUpdated: new Date(),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
mrn: 'MRN002',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
dateOfBirth: new Date('1990-07-22'),
|
||||
gender: 'FEMALE',
|
||||
age: 33,
|
||||
bedNumber: 'B2',
|
||||
roomNumber: '102',
|
||||
admissionDate: new Date('2024-01-15'),
|
||||
status: 'ACTIVE',
|
||||
priority: 'HIGH',
|
||||
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',
|
||||
lastUpdated: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
return mockPatients;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to fetch patients.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Fetch Patient Details Async Thunk
|
||||
*
|
||||
* Purpose: Fetch detailed patient information
|
||||
*
|
||||
* @param patientId - ID of the patient to fetch
|
||||
* @returns Promise with patient details or error
|
||||
*/
|
||||
export const fetchPatientDetails = createAsyncThunk(
|
||||
'patientCare/fetchPatientDetails',
|
||||
async (patientId: string, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Mock patient details (same as above but with more detailed info)
|
||||
const mockPatient: Patient = {
|
||||
id: patientId,
|
||||
mrn: `MRN${patientId.padStart(3, '0')}`,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
dateOfBirth: new Date('1985-03-15'),
|
||||
gender: 'MALE',
|
||||
age: 38,
|
||||
bedNumber: 'A1',
|
||||
roomNumber: '101',
|
||||
admissionDate: new Date('2024-01-15'),
|
||||
status: 'ACTIVE',
|
||||
priority: 'CRITICAL',
|
||||
department: 'Emergency',
|
||||
attendingPhysician: 'Dr. Smith',
|
||||
allergies: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Penicillin',
|
||||
severity: 'SEVERE',
|
||||
reaction: 'Anaphylaxis',
|
||||
},
|
||||
],
|
||||
medications: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Morphine',
|
||||
dosage: '2mg',
|
||||
frequency: 'Every 4 hours',
|
||||
route: 'IV',
|
||||
startDate: new Date(),
|
||||
status: 'ACTIVE',
|
||||
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',
|
||||
lastUpdated: new Date(),
|
||||
};
|
||||
|
||||
return mockPatient;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to fetch patient details.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Update Patient Async Thunk
|
||||
*
|
||||
* Purpose: Update patient information
|
||||
*
|
||||
* @param patientData - Updated patient data
|
||||
* @returns Promise with updated patient or error
|
||||
*/
|
||||
export const updatePatient = createAsyncThunk(
|
||||
'patientCare/updatePatient',
|
||||
async (patientData: Partial<Patient> & { id: string }, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
return patientData;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to update patient.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// INITIAL STATE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initial Patient Care State
|
||||
*
|
||||
* Purpose: Define the initial state for patient care
|
||||
*
|
||||
* Features:
|
||||
* - Patients list and management
|
||||
* - Current patient details
|
||||
* - Loading states for async operations
|
||||
* - Error handling and messages
|
||||
* - Search and filtering
|
||||
*/
|
||||
const initialState: PatientCareState = {
|
||||
// Patients data
|
||||
patients: [],
|
||||
currentPatient: null,
|
||||
|
||||
// Loading states
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
isLoadingPatientDetails: false,
|
||||
|
||||
// Error handling
|
||||
error: null,
|
||||
|
||||
// Search and filtering
|
||||
searchQuery: '',
|
||||
selectedFilter: 'all',
|
||||
sortBy: 'priority',
|
||||
sortOrder: 'desc',
|
||||
|
||||
// Pagination
|
||||
currentPage: 1,
|
||||
itemsPerPage: 20,
|
||||
totalItems: 0,
|
||||
|
||||
// Cache
|
||||
lastUpdated: null,
|
||||
cacheExpiry: null,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PATIENT CARE SLICE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Patient Care Slice
|
||||
*
|
||||
* Purpose: Redux slice for patient care state management
|
||||
*
|
||||
* Features:
|
||||
* - Patient data management
|
||||
* - Search and filtering
|
||||
* - Pagination
|
||||
* - Caching
|
||||
* - Error handling
|
||||
* - Loading states
|
||||
*/
|
||||
const patientCareSlice = createSlice({
|
||||
name: 'patientCare',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* Clear Error Action
|
||||
*
|
||||
* Purpose: Clear patient care errors
|
||||
*/
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Search Query Action
|
||||
*
|
||||
* Purpose: Set search query for patients
|
||||
*/
|
||||
setSearchQuery: (state, action: PayloadAction<string>) => {
|
||||
state.searchQuery = action.payload;
|
||||
state.currentPage = 1; // Reset to first page when searching
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Filter Action
|
||||
*
|
||||
* Purpose: Set patient filter
|
||||
*/
|
||||
setFilter: (state, action: PayloadAction<'all' | 'active' | 'discharged' | 'critical'>) => {
|
||||
state.selectedFilter = action.payload;
|
||||
state.currentPage = 1; // Reset to first page when filtering
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Sort Action
|
||||
*
|
||||
* Purpose: Set patient sort options
|
||||
*/
|
||||
setSort: (state, action: PayloadAction<{ by: string; 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 Patient Action
|
||||
*
|
||||
* Purpose: Set the currently selected patient
|
||||
*/
|
||||
setCurrentPatient: (state, action: PayloadAction<Patient | null>) => {
|
||||
state.currentPatient = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Patient in List Action
|
||||
*
|
||||
* Purpose: Update a patient in the patients list
|
||||
*/
|
||||
updatePatientInList: (state, action: PayloadAction<Patient>) => {
|
||||
const index = state.patients.findIndex(patient => patient.id === action.payload.id);
|
||||
if (index !== -1) {
|
||||
state.patients[index] = action.payload;
|
||||
}
|
||||
|
||||
// Update current patient if it's the same patient
|
||||
if (state.currentPatient && state.currentPatient.id === action.payload.id) {
|
||||
state.currentPatient = action.payload;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add Patient Action
|
||||
*
|
||||
* Purpose: Add a new patient to the list
|
||||
*/
|
||||
addPatient: (state, action: PayloadAction<Patient>) => {
|
||||
state.patients.unshift(action.payload);
|
||||
state.totalItems += 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove Patient Action
|
||||
*
|
||||
* Purpose: Remove a patient from the list
|
||||
*/
|
||||
removePatient: (state, action: PayloadAction<string>) => {
|
||||
const index = state.patients.findIndex(patient => patient.id === action.payload);
|
||||
if (index !== -1) {
|
||||
state.patients.splice(index, 1);
|
||||
state.totalItems -= 1;
|
||||
}
|
||||
|
||||
// Clear current patient if it's the same patient
|
||||
if (state.currentPatient && state.currentPatient.id === action.payload) {
|
||||
state.currentPatient = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear Cache Action
|
||||
*
|
||||
* Purpose: Clear patient data cache
|
||||
*/
|
||||
clearCache: (state) => {
|
||||
state.patients = [];
|
||||
state.currentPatient = null;
|
||||
state.lastUpdated = null;
|
||||
state.cacheExpiry = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Fetch Patients
|
||||
builder
|
||||
.addCase(fetchPatients.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchPatients.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.patients = action.payload;
|
||||
state.totalItems = action.payload.length;
|
||||
state.lastUpdated = new Date();
|
||||
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchPatients.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Fetch Patient Details
|
||||
builder
|
||||
.addCase(fetchPatientDetails.pending, (state) => {
|
||||
state.isLoadingPatientDetails = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchPatientDetails.fulfilled, (state, action) => {
|
||||
state.isLoadingPatientDetails = false;
|
||||
state.currentPatient = action.payload;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchPatientDetails.rejected, (state, action) => {
|
||||
state.isLoadingPatientDetails = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Update Patient
|
||||
builder
|
||||
.addCase(updatePatient.fulfilled, (state, action) => {
|
||||
// Update patient in list
|
||||
const index = state.patients.findIndex(patient => patient.id === action.payload.id);
|
||||
if (index !== -1) {
|
||||
state.patients[index] = { ...state.patients[index], ...action.payload };
|
||||
}
|
||||
|
||||
// Update current patient if it's the same patient
|
||||
if (state.currentPatient && state.currentPatient.id === action.payload.id) {
|
||||
state.currentPatient = { ...state.currentPatient, ...action.payload };
|
||||
}
|
||||
})
|
||||
.addCase(updatePatient.rejected, (state, action) => {
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export const {
|
||||
clearError,
|
||||
setSearchQuery,
|
||||
setFilter,
|
||||
setSort,
|
||||
setCurrentPage,
|
||||
setItemsPerPage,
|
||||
setCurrentPatient,
|
||||
updatePatientInList,
|
||||
addPatient,
|
||||
removePatient,
|
||||
clearCache,
|
||||
} = patientCareSlice.actions;
|
||||
|
||||
export default patientCareSlice.reducer;
|
||||
|
||||
/*
|
||||
* End of File: patientCareSlice.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
310
app/modules/Settings/components/ProfileCard.tsx
Normal file
310
app/modules/Settings/components/ProfileCard.tsx
Normal file
@ -0,0 +1,310 @@
|
||||
/*
|
||||
* File: ProfileCard.tsx
|
||||
* Description: Profile card component displaying user information
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { UserProfile } from '../../../shared/types';
|
||||
|
||||
/**
|
||||
* ProfileCardProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the ProfileCard component
|
||||
*
|
||||
* Props:
|
||||
* - profile: User profile data to display
|
||||
* - onPress: Callback function when card is pressed
|
||||
*/
|
||||
interface ProfileCardProps {
|
||||
profile: UserProfile;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileCard Component
|
||||
*
|
||||
* Purpose: Displays user profile information in a card format
|
||||
*
|
||||
* Features:
|
||||
* - Profile picture display
|
||||
* - User name and credentials
|
||||
* - Department and role information
|
||||
* - Contact information
|
||||
* - Interactive card design
|
||||
*/
|
||||
export const ProfileCard: React.FC<ProfileCardProps> = ({ profile, onPress }) => {
|
||||
/**
|
||||
* formatCredentials Function
|
||||
*
|
||||
* Purpose: Format credentials array into a readable string
|
||||
*
|
||||
* @returns Formatted credentials string
|
||||
*/
|
||||
const formatCredentials = (): string => {
|
||||
return profile.credentials.join(', ');
|
||||
};
|
||||
|
||||
/**
|
||||
* formatSpecialties Function
|
||||
*
|
||||
* Purpose: Format specialties array into a readable string
|
||||
*
|
||||
* @returns Formatted specialties string
|
||||
*/
|
||||
const formatSpecialties = (): string => {
|
||||
return profile.specialties.slice(0, 2).join(', ');
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={styles.container} onPress={onPress} activeOpacity={0.7}>
|
||||
{/* Profile picture and basic info */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.imageContainer}>
|
||||
{profile.profilePicture ? (
|
||||
<Image
|
||||
source={require('../../../assets/images/default-avatar.png')}
|
||||
style={styles.profileImage}
|
||||
resizeMode="cover"
|
||||
onError={(error) => {
|
||||
console.log('Profile image loading error:', error.nativeEvent.error);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.fallbackAvatar}>
|
||||
<Text style={styles.fallbackText}>
|
||||
{profile.firstName.charAt(0)}{profile.lastName.charAt(0)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.basicInfo}>
|
||||
<Text style={styles.name}>
|
||||
{profile.firstName} {profile.lastName}
|
||||
</Text>
|
||||
<Text style={styles.credentials}>{formatCredentials()}</Text>
|
||||
<Text style={styles.specialties}>{formatSpecialties()}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.editIcon}>
|
||||
<Text style={styles.editText}>Edit</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Department and role information */}
|
||||
<View style={styles.infoSection}>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>Department:</Text>
|
||||
<Text style={styles.infoValue}>{profile.department}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>Role:</Text>
|
||||
<Text style={styles.infoValue}>
|
||||
{profile.role.replace('_', ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase())}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>Experience:</Text>
|
||||
<Text style={styles.infoValue}>{profile.yearsOfExperience} years</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Contact information */}
|
||||
<View style={styles.contactSection}>
|
||||
<View style={styles.contactRow}>
|
||||
<Text style={styles.contactLabel}>Email:</Text>
|
||||
<Text style={styles.contactValue} numberOfLines={1}>
|
||||
{profile.email}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.contactRow}>
|
||||
<Text style={styles.contactLabel}>Phone:</Text>
|
||||
<Text style={styles.contactValue}>{profile.phoneNumber}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container for the profile card
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.lg,
|
||||
marginVertical: theme.spacing.md,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
borderColor: theme.colors.border,
|
||||
borderWidth: 1,
|
||||
},
|
||||
|
||||
// Header section with profile picture and basic info
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Profile image container
|
||||
imageContainer: {
|
||||
marginRight: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Profile image styling
|
||||
profileImage: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderWidth: 2,
|
||||
borderColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Basic information section
|
||||
basicInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// User name styling
|
||||
name: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Credentials styling
|
||||
credentials: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.primary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Specialties styling
|
||||
specialties: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Edit icon/text styling
|
||||
editIcon: {
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
},
|
||||
|
||||
// Edit text styling
|
||||
editText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Information section styling
|
||||
infoSection: {
|
||||
marginBottom: theme.spacing.md,
|
||||
paddingTop: theme.spacing.md,
|
||||
borderTopColor: theme.colors.border,
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
|
||||
// Information row styling
|
||||
infoRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Information label styling
|
||||
infoLabel: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Information value styling
|
||||
infoValue: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
textAlign: 'right',
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Contact section styling
|
||||
contactSection: {
|
||||
paddingTop: theme.spacing.md,
|
||||
borderTopColor: theme.colors.border,
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
|
||||
// Contact row styling
|
||||
contactRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Contact label styling
|
||||
contactLabel: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Contact value styling
|
||||
contactValue: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
textAlign: 'right',
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Fallback avatar styling
|
||||
fallbackAvatar: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: theme.colors.primary,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Fallback text styling
|
||||
fallbackText: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: ProfileCard.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
68
app/modules/Settings/components/SettingsHeader.tsx
Normal file
68
app/modules/Settings/components/SettingsHeader.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* File: SettingsHeader.tsx
|
||||
* Description: Header component for the settings screen
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
|
||||
/**
|
||||
* SettingsHeaderProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the SettingsHeader component
|
||||
*
|
||||
* Props:
|
||||
* - title: Title text to display in the header
|
||||
*/
|
||||
interface SettingsHeaderProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsHeader Component
|
||||
*
|
||||
* Purpose: Displays the header for the settings screen
|
||||
*
|
||||
* Features:
|
||||
* - Clean, minimal header design
|
||||
* - Consistent with app theme
|
||||
* - Proper spacing and typography
|
||||
*/
|
||||
export const SettingsHeader: React.FC<SettingsHeaderProps> = ({ title }) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container for the header
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.lg,
|
||||
borderBottomColor: theme.colors.border,
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
|
||||
// Title text styling
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displayMedium,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: SettingsHeader.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
299
app/modules/Settings/components/SettingsItemComponent.tsx
Normal file
299
app/modules/Settings/components/SettingsItemComponent.tsx
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* File: SettingsItemComponent.tsx
|
||||
* Description: Settings item component displaying individual settings options
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Switch } from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { SettingsItem } from '../../../shared/types';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
/**
|
||||
* SettingsItemComponentProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the SettingsItemComponent
|
||||
*
|
||||
* Props:
|
||||
* - item: Settings item data to display
|
||||
* - isLast: Boolean indicating if this is the last item in the section
|
||||
*/
|
||||
interface SettingsItemComponentProps {
|
||||
item: SettingsItem;
|
||||
isLast: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsItemComponent Component
|
||||
*
|
||||
* Purpose: Displays individual settings items with different interaction types
|
||||
*
|
||||
* Features:
|
||||
* - Navigation items with chevron
|
||||
* - Toggle items with switch
|
||||
* - Action items with custom styling
|
||||
* - Icon support (placeholder)
|
||||
* - Consistent styling and spacing
|
||||
*/
|
||||
export const SettingsItemComponent: React.FC<SettingsItemComponentProps> = ({ item, isLast }) => {
|
||||
/**
|
||||
* renderIcon Function
|
||||
*
|
||||
* Purpose: Render icon for the settings item
|
||||
*
|
||||
* @returns Icon component or placeholder
|
||||
*/
|
||||
const renderIcon = () => {
|
||||
// TODO: Implement actual icon rendering
|
||||
return (
|
||||
<View style={styles.iconPlaceholder}>
|
||||
<Icon name={item.icon} size={24} color={theme.colors.textPrimary} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* renderValue Function
|
||||
*
|
||||
* Purpose: Render the value/action component based on item type
|
||||
*
|
||||
* @returns Value component (switch, chevron, or custom)
|
||||
*/
|
||||
const renderValue = () => {
|
||||
switch (item.type) {
|
||||
case 'TOGGLE':
|
||||
return (
|
||||
<Switch
|
||||
value={item.value || false}
|
||||
onValueChange={item.onPress}
|
||||
trackColor={{ false: theme.colors.border, true: theme.colors.primary }}
|
||||
thumbColor={theme.colors.background}
|
||||
ios_backgroundColor={theme.colors.border}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'NAVIGATION':
|
||||
return (
|
||||
<Text style={styles.chevron}>›</Text>
|
||||
);
|
||||
|
||||
case 'ACTION':
|
||||
return (
|
||||
<Text style={styles.actionText}>Sign Out</Text>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getItemStyle Function
|
||||
*
|
||||
* Purpose: Get appropriate styling based on item type and position
|
||||
*
|
||||
* @returns Style object for the item container
|
||||
*/
|
||||
const getItemStyle = () => {
|
||||
const baseStyle = [styles.container];
|
||||
|
||||
// Add border bottom if not the last item
|
||||
if (!isLast) {
|
||||
baseStyle.push(styles.withBorder);
|
||||
}
|
||||
|
||||
// Add special styling for action items
|
||||
if (item.type === 'ACTION') {
|
||||
baseStyle.push(styles.actionItem);
|
||||
}
|
||||
|
||||
// Add disabled styling if item is disabled
|
||||
if (item.disabled) {
|
||||
baseStyle.push(styles.disabledItem);
|
||||
}
|
||||
|
||||
return baseStyle;
|
||||
};
|
||||
|
||||
/**
|
||||
* getTextStyle Function
|
||||
*
|
||||
* Purpose: Get appropriate text styling based on item state
|
||||
*
|
||||
* @returns Style object for text
|
||||
*/
|
||||
const getTextStyle = () => {
|
||||
if (item.disabled) {
|
||||
return [styles.title, styles.disabledText];
|
||||
}
|
||||
if (item.type === 'ACTION') {
|
||||
return [styles.title, styles.actionTitle];
|
||||
}
|
||||
return styles.title;
|
||||
};
|
||||
|
||||
/**
|
||||
* getSubtitleStyle Function
|
||||
*
|
||||
* Purpose: Get appropriate subtitle styling based on item state
|
||||
*
|
||||
* @returns Style object for subtitle
|
||||
*/
|
||||
const getSubtitleStyle = () => {
|
||||
if (item.disabled) {
|
||||
return [styles.subtitle, styles.disabledText];
|
||||
}
|
||||
return styles.subtitle;
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={getItemStyle()}
|
||||
onPress={item.onPress}
|
||||
disabled={item.disabled}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* Icon */}
|
||||
{renderIcon()}
|
||||
|
||||
{/* Content */}
|
||||
<View style={styles.content}>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={getTextStyle()}>{item.title}</Text>
|
||||
{item.subtitle && (
|
||||
<Text style={getSubtitleStyle()}>{item.subtitle}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Badge */}
|
||||
{item.badge && (
|
||||
<View style={styles.badge}>
|
||||
<Text style={styles.badgeText}>{item.badge}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Value/Action */}
|
||||
{renderValue()}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container for the settings item
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
paddingVertical: theme.spacing.md,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
},
|
||||
|
||||
// Container with bottom border
|
||||
withBorder: {
|
||||
borderBottomColor: theme.colors.border,
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
|
||||
// Action item styling
|
||||
actionItem: {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
|
||||
// Disabled item styling
|
||||
disabledItem: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
// Icon placeholder styling
|
||||
iconPlaceholder: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginRight: theme.spacing.md,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Icon text styling
|
||||
iconText: {
|
||||
fontSize: 16,
|
||||
},
|
||||
|
||||
// Content container
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// Text container
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Title text styling
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
// Action title styling
|
||||
actionTitle: {
|
||||
color: theme.colors.error,
|
||||
},
|
||||
|
||||
// Subtitle text styling
|
||||
subtitle: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Disabled text styling
|
||||
disabledText: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
|
||||
// Badge styling
|
||||
badge: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
marginLeft: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Badge text styling
|
||||
badgeText: {
|
||||
fontSize: theme.typography.fontSize.caption,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
|
||||
// Chevron styling for navigation items
|
||||
chevron: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
color: theme.colors.textMuted,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
},
|
||||
|
||||
// Action text styling
|
||||
actionText: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.error,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: SettingsItemComponent.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
90
app/modules/Settings/components/SettingsSectionComponent.tsx
Normal file
90
app/modules/Settings/components/SettingsSectionComponent.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* File: SettingsSectionComponent.tsx
|
||||
* Description: Settings section component displaying grouped settings items
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import { SettingsSectionData } from '../../../shared/types';
|
||||
import { SettingsItemComponent } from './SettingsItemComponent';
|
||||
|
||||
/**
|
||||
* SettingsSectionComponentProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the SettingsSectionComponent
|
||||
*
|
||||
* Props:
|
||||
* - section: Settings section data to display
|
||||
*/
|
||||
interface SettingsSectionComponentProps {
|
||||
section: SettingsSectionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsSectionComponent Component
|
||||
*
|
||||
* Purpose: Displays a settings section with grouped items
|
||||
*
|
||||
* Features:
|
||||
* - Section title display
|
||||
* - Grouped settings items
|
||||
* - Consistent styling and spacing
|
||||
* - Clean visual separation between sections
|
||||
*/
|
||||
export const SettingsSectionComponent: React.FC<SettingsSectionComponentProps> = ({ section }) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Section title */}
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
|
||||
{/* Settings items */}
|
||||
<View style={styles.itemsContainer}>
|
||||
{section.items.map((item, index) => (
|
||||
<SettingsItemComponent
|
||||
key={item.id}
|
||||
item={item}
|
||||
isLast={index === section.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container for the settings section
|
||||
container: {
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Section title styling
|
||||
sectionTitle: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.sm,
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
},
|
||||
|
||||
// Container for settings items
|
||||
itemsContainer: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
borderColor: theme.colors.border,
|
||||
borderWidth: 1,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: SettingsSectionComponent.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
80
app/modules/Settings/index.ts
Normal file
80
app/modules/Settings/index.ts
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Settings module exports
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// SCREENS
|
||||
// ============================================================================
|
||||
|
||||
export { default as SettingsScreen } from './screens/SettingsScreen';
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION
|
||||
// ============================================================================
|
||||
|
||||
export {
|
||||
SettingsStackNavigator,
|
||||
SettingsStackParamList,
|
||||
SettingsNavigationProp,
|
||||
SettingsScreenProps,
|
||||
ProfileEditScreenProps,
|
||||
SecuritySettingsScreenProps,
|
||||
NotificationSettingsScreenProps,
|
||||
ClinicalPreferencesScreenProps,
|
||||
PrivacySettingsScreenProps,
|
||||
AccessibilitySettingsScreenProps,
|
||||
AboutScreenProps,
|
||||
HelpSupportScreenProps,
|
||||
navigateToSettings,
|
||||
navigateToProfileEdit,
|
||||
navigateToSecuritySettings,
|
||||
navigateToNotificationSettings,
|
||||
navigateToClinicalPreferences,
|
||||
navigateToPrivacySettings,
|
||||
navigateToAccessibilitySettings,
|
||||
navigateToAbout,
|
||||
navigateToHelpSupport,
|
||||
goBack,
|
||||
resetToSettings,
|
||||
replaceWithSettings,
|
||||
navigateToSettingsAndClearStack,
|
||||
navigateToProfileEditAndClearStack,
|
||||
navigateToSecuritySettingsAndClearStack,
|
||||
} from './navigation';
|
||||
|
||||
// ============================================================================
|
||||
// COMPONENTS
|
||||
// ============================================================================
|
||||
|
||||
export { default as SettingsHeader } from './components/SettingsHeader';
|
||||
export { default as ProfileCard } from './components/ProfileCard';
|
||||
export { default as SettingsSectionComponent } from './components/SettingsSectionComponent';
|
||||
export { default as SettingsItemComponent } from './components/SettingsItemComponent';
|
||||
|
||||
// ============================================================================
|
||||
// REDUX
|
||||
// ============================================================================
|
||||
|
||||
export {
|
||||
fetchUserProfile,
|
||||
updateUserProfile,
|
||||
fetchUserPreferences,
|
||||
updateUserPreferences,
|
||||
clearError,
|
||||
setProfileValidation,
|
||||
setPreferencesValidation,
|
||||
updateProfileField,
|
||||
updatePreferenceField,
|
||||
resetProfile,
|
||||
resetPreferences,
|
||||
clearSettingsCache,
|
||||
} from './redux/settingsSlice';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
91
app/modules/Settings/navigation/SettingsStackNavigator.tsx
Normal file
91
app/modules/Settings/navigation/SettingsStackNavigator.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* File: SettingsStackNavigator.tsx
|
||||
* Description: Stack navigator for settings screens within the Settings module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
|
||||
// Import settings screens
|
||||
import { SettingsScreen } from '../screens/SettingsScreen';
|
||||
|
||||
// Import navigation types
|
||||
import { SettingsStackParamList } from './navigationTypes';
|
||||
import { theme } from '../../../theme';
|
||||
|
||||
// Create stack navigator for Settings module
|
||||
const Stack = createStackNavigator<SettingsStackParamList>();
|
||||
|
||||
/**
|
||||
* SettingsStackNavigator - Manages navigation between settings screens
|
||||
*
|
||||
* This navigator handles the flow between:
|
||||
* - SettingsScreen: Main settings screen with profile and preferences
|
||||
* - Future screens: Profile edit, security settings, notifications, etc.
|
||||
*
|
||||
* Features:
|
||||
* - Clean header styling
|
||||
* - Smooth transitions between screens
|
||||
* - Type-safe navigation parameters
|
||||
* - Settings-focused design
|
||||
*/
|
||||
const SettingsStackNavigator: React.FC = () => {
|
||||
return (
|
||||
<Stack.Navigator
|
||||
initialRouteName="Settings"
|
||||
screenOptions={{
|
||||
// Header styling for settings screens
|
||||
headerStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
elevation: 0, // Remove shadow on Android
|
||||
shadowOpacity: 0, // Remove shadow on iOS
|
||||
},
|
||||
headerTitleStyle: {
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
fontSize: 18,
|
||||
color: '#212121',
|
||||
},
|
||||
headerTintColor: '#2196F3', // Back button and title color
|
||||
headerBackTitleVisible: false, // Hide back title on iOS
|
||||
cardStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
// Smooth transitions
|
||||
transitionSpec: {
|
||||
open: {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 300,
|
||||
},
|
||||
},
|
||||
close: {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Settings Screen - Main settings entry point */}
|
||||
<Stack.Screen
|
||||
name="Settings"
|
||||
component={SettingsScreen}
|
||||
options={{
|
||||
title: 'Settings',
|
||||
headerShown: false, // Hide header for main settings screen
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsStackNavigator;
|
||||
|
||||
/*
|
||||
* End of File: SettingsStackNavigator.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
59
app/modules/Settings/navigation/index.ts
Normal file
59
app/modules/Settings/navigation/index.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Barrel exports for Settings module navigation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// Export main navigator
|
||||
export { default as SettingsStackNavigator } from './SettingsStackNavigator';
|
||||
|
||||
// Export navigation types
|
||||
export type {
|
||||
SettingsStackParamList,
|
||||
SettingsNavigationProp,
|
||||
SettingsScreenProps,
|
||||
SettingsScreenProps as MainSettingsScreenProps,
|
||||
ProfileEditScreenProps,
|
||||
SecuritySettingsScreenProps,
|
||||
NotificationSettingsScreenProps,
|
||||
ClinicalPreferencesScreenProps,
|
||||
PrivacySettingsScreenProps,
|
||||
AccessibilitySettingsScreenProps,
|
||||
AboutScreenProps,
|
||||
HelpSupportScreenProps,
|
||||
SettingsScreenParams,
|
||||
ProfileEditScreenParams,
|
||||
SecuritySettingsScreenParams,
|
||||
NotificationSettingsScreenParams,
|
||||
ClinicalPreferencesScreenParams,
|
||||
PrivacySettingsScreenParams,
|
||||
AccessibilitySettingsScreenParams,
|
||||
AboutScreenParams,
|
||||
HelpSupportScreenParams,
|
||||
} from './navigationTypes';
|
||||
|
||||
// Export navigation utilities
|
||||
export {
|
||||
navigateToSettings,
|
||||
navigateToProfileEdit,
|
||||
navigateToSecuritySettings,
|
||||
navigateToNotificationSettings,
|
||||
navigateToClinicalPreferences,
|
||||
navigateToPrivacySettings,
|
||||
navigateToAccessibilitySettings,
|
||||
navigateToAbout,
|
||||
navigateToHelpSupport,
|
||||
goBack,
|
||||
resetToSettings,
|
||||
replaceWithSettings,
|
||||
navigateToSettingsAndClearStack,
|
||||
navigateToProfileEditAndClearStack,
|
||||
navigateToSecuritySettingsAndClearStack,
|
||||
} from './navigationUtils';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
252
app/modules/Settings/navigation/navigationTypes.ts
Normal file
252
app/modules/Settings/navigation/navigationTypes.ts
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* File: navigationTypes.ts
|
||||
* Description: TypeScript types for Settings module navigation
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { UserProfile, UserPreferences } from '../../../shared/types';
|
||||
|
||||
/**
|
||||
* SettingsStackParamList - Defines the parameter list for Settings stack navigator
|
||||
*
|
||||
* This interface defines all the screens available in the Settings module
|
||||
* and their associated navigation parameters.
|
||||
*/
|
||||
export type SettingsStackParamList = {
|
||||
// Settings screen - Main settings with profile and preferences
|
||||
Settings: SettingsScreenParams;
|
||||
|
||||
// Profile Edit screen - Edit user profile information
|
||||
ProfileEdit: ProfileEditScreenParams;
|
||||
|
||||
// Security Settings screen - Security and privacy settings
|
||||
SecuritySettings: SecuritySettingsScreenParams;
|
||||
|
||||
// Notification Settings screen - Notification preferences
|
||||
NotificationSettings: NotificationSettingsScreenParams;
|
||||
|
||||
// Clinical Preferences screen - Clinical workflow preferences
|
||||
ClinicalPreferences: ClinicalPreferencesScreenParams;
|
||||
|
||||
// Privacy Settings screen - Privacy and data settings
|
||||
PrivacySettings: PrivacySettingsScreenParams;
|
||||
|
||||
// Accessibility Settings screen - Accessibility preferences
|
||||
AccessibilitySettings: AccessibilitySettingsScreenParams;
|
||||
|
||||
// About screen - App information and version
|
||||
About: AboutScreenParams;
|
||||
|
||||
// Help & Support screen - Help documentation and support
|
||||
HelpSupport: HelpSupportScreenParams;
|
||||
};
|
||||
|
||||
/**
|
||||
* SettingsNavigationProp - Type for navigation prop in Settings screens
|
||||
*
|
||||
* This type provides type-safe navigation methods for screens
|
||||
* within the Settings module.
|
||||
*/
|
||||
export type SettingsNavigationProp = StackNavigationProp<SettingsStackParamList>;
|
||||
|
||||
/**
|
||||
* SettingsScreenProps - Base props interface for Settings screens
|
||||
*
|
||||
* This interface provides the common props that all Settings screens
|
||||
* will receive, including navigation and route.
|
||||
*/
|
||||
export interface SettingsScreenProps<T extends keyof SettingsStackParamList> {
|
||||
navigation: SettingsNavigationProp;
|
||||
route: {
|
||||
key: string;
|
||||
name: T;
|
||||
params: SettingsStackParamList[T];
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SCREEN PARAMETER TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SettingsScreenParams
|
||||
*
|
||||
* Purpose: Parameters passed to the main settings screen
|
||||
*
|
||||
* Parameters:
|
||||
* - section: Optional section to navigate to within settings
|
||||
* - refresh: Optional flag to force refresh
|
||||
*/
|
||||
export interface SettingsScreenParams {
|
||||
section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help';
|
||||
refresh?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileEditScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the profile edit screen
|
||||
*
|
||||
* Parameters:
|
||||
* - profile: Optional profile data to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface ProfileEditScreenParams {
|
||||
profile?: UserProfile;
|
||||
fromScreen?: keyof SettingsStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* SecuritySettingsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the security settings screen
|
||||
*
|
||||
* Parameters:
|
||||
* - showBiometricSetup: Optional flag to show biometric setup
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface SecuritySettingsScreenParams {
|
||||
showBiometricSetup?: boolean;
|
||||
fromScreen?: keyof SettingsStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationSettingsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the notification settings screen
|
||||
*
|
||||
* Parameters:
|
||||
* - preferences: Optional notification preferences to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface NotificationSettingsScreenParams {
|
||||
preferences?: UserPreferences['notificationPreferences'];
|
||||
fromScreen?: keyof SettingsStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClinicalPreferencesScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the clinical preferences screen
|
||||
*
|
||||
* Parameters:
|
||||
* - preferences: Optional clinical preferences to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface ClinicalPreferencesScreenParams {
|
||||
preferences?: UserPreferences['clinicalPreferences'];
|
||||
fromScreen?: keyof SettingsStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* PrivacySettingsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the privacy settings screen
|
||||
*
|
||||
* Parameters:
|
||||
* - preferences: Optional privacy preferences to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface PrivacySettingsScreenParams {
|
||||
preferences?: UserPreferences['privacyPreferences'];
|
||||
fromScreen?: keyof SettingsStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* AccessibilitySettingsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the accessibility settings screen
|
||||
*
|
||||
* Parameters:
|
||||
* - preferences: Optional accessibility preferences to pre-populate
|
||||
* - fromScreen: Optional source screen for back navigation
|
||||
*/
|
||||
export interface AccessibilitySettingsScreenParams {
|
||||
preferences?: UserPreferences['accessibilityPreferences'];
|
||||
fromScreen?: keyof SettingsStackParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* AboutScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the about screen
|
||||
*
|
||||
* Parameters:
|
||||
* - showChangelog: Optional flag to show changelog
|
||||
* - showLicenses: Optional flag to show licenses
|
||||
*/
|
||||
export interface AboutScreenParams {
|
||||
showChangelog?: boolean;
|
||||
showLicenses?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* HelpSupportScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the help and support screen
|
||||
*
|
||||
* Parameters:
|
||||
* - topic: Optional help topic to navigate to
|
||||
* - showContact: Optional flag to show contact form
|
||||
*/
|
||||
export interface HelpSupportScreenParams {
|
||||
topic?: 'faq' | 'troubleshooting' | 'contact' | 'feedback';
|
||||
showContact?: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SCREEN-SPECIFIC PROPS TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SettingsScreenProps - Props for SettingsScreen component
|
||||
*/
|
||||
export type SettingsScreenProps = SettingsScreenProps<'Settings'>;
|
||||
|
||||
/**
|
||||
* ProfileEditScreenProps - Props for ProfileEditScreen component
|
||||
*/
|
||||
export type ProfileEditScreenProps = SettingsScreenProps<'ProfileEdit'>;
|
||||
|
||||
/**
|
||||
* SecuritySettingsScreenProps - Props for SecuritySettingsScreen component
|
||||
*/
|
||||
export type SecuritySettingsScreenProps = SettingsScreenProps<'SecuritySettings'>;
|
||||
|
||||
/**
|
||||
* NotificationSettingsScreenProps - Props for NotificationSettingsScreen component
|
||||
*/
|
||||
export type NotificationSettingsScreenProps = SettingsScreenProps<'NotificationSettings'>;
|
||||
|
||||
/**
|
||||
* ClinicalPreferencesScreenProps - Props for ClinicalPreferencesScreen component
|
||||
*/
|
||||
export type ClinicalPreferencesScreenProps = SettingsScreenProps<'ClinicalPreferences'>;
|
||||
|
||||
/**
|
||||
* PrivacySettingsScreenProps - Props for PrivacySettingsScreen component
|
||||
*/
|
||||
export type PrivacySettingsScreenProps = SettingsScreenProps<'PrivacySettings'>;
|
||||
|
||||
/**
|
||||
* AccessibilitySettingsScreenProps - Props for AccessibilitySettingsScreen component
|
||||
*/
|
||||
export type AccessibilitySettingsScreenProps = SettingsScreenProps<'AccessibilitySettings'>;
|
||||
|
||||
/**
|
||||
* AboutScreenProps - Props for AboutScreen component
|
||||
*/
|
||||
export type AboutScreenProps = SettingsScreenProps<'About'>;
|
||||
|
||||
/**
|
||||
* HelpSupportScreenProps - Props for HelpSupportScreen component
|
||||
*/
|
||||
export type HelpSupportScreenProps = SettingsScreenProps<'HelpSupport'>;
|
||||
|
||||
/*
|
||||
* End of File: navigationTypes.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
264
app/modules/Settings/navigation/navigationUtils.ts
Normal file
264
app/modules/Settings/navigation/navigationUtils.ts
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* File: navigationUtils.ts
|
||||
* Description: Navigation utilities for Settings module
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { SettingsNavigationProp } from './navigationTypes';
|
||||
import { UserProfile, UserPreferences } from '../../../shared/types';
|
||||
|
||||
/**
|
||||
* SettingsNavigationUtils - Utility functions for Settings module navigation
|
||||
*
|
||||
* This module provides helper functions for common navigation patterns
|
||||
* within the Settings module, ensuring consistent navigation behavior.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Navigate to Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the settings screen
|
||||
*/
|
||||
export const navigateToSettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
params?: {
|
||||
section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.navigate('Settings', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Profile Edit screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param profile - Optional profile data to pre-populate
|
||||
* @param fromScreen - Optional source screen for back navigation
|
||||
*/
|
||||
export const navigateToProfileEdit = (
|
||||
navigation: SettingsNavigationProp,
|
||||
profile?: UserProfile,
|
||||
fromScreen?: keyof import('./navigationTypes').SettingsStackParamList
|
||||
): void => {
|
||||
navigation.navigate('ProfileEdit', {
|
||||
profile,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Security Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param showBiometricSetup - Optional flag to show biometric setup
|
||||
* @param fromScreen - Optional source screen for back navigation
|
||||
*/
|
||||
export const navigateToSecuritySettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
showBiometricSetup?: boolean,
|
||||
fromScreen?: keyof import('./navigationTypes').SettingsStackParamList
|
||||
): void => {
|
||||
navigation.navigate('SecuritySettings', {
|
||||
showBiometricSetup,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Notification Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param preferences - Optional notification preferences to pre-populate
|
||||
* @param fromScreen - Optional source screen for back navigation
|
||||
*/
|
||||
export const navigateToNotificationSettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
preferences?: UserPreferences['notificationPreferences'],
|
||||
fromScreen?: keyof import('./navigationTypes').SettingsStackParamList
|
||||
): void => {
|
||||
navigation.navigate('NotificationSettings', {
|
||||
preferences,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Clinical Preferences screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param preferences - Optional clinical preferences to pre-populate
|
||||
* @param fromScreen - Optional source screen for back navigation
|
||||
*/
|
||||
export const navigateToClinicalPreferences = (
|
||||
navigation: SettingsNavigationProp,
|
||||
preferences?: UserPreferences['clinicalPreferences'],
|
||||
fromScreen?: keyof import('./navigationTypes').SettingsStackParamList
|
||||
): void => {
|
||||
navigation.navigate('ClinicalPreferences', {
|
||||
preferences,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Privacy Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param preferences - Optional privacy preferences to pre-populate
|
||||
* @param fromScreen - Optional source screen for back navigation
|
||||
*/
|
||||
export const navigateToPrivacySettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
preferences?: UserPreferences['privacyPreferences'],
|
||||
fromScreen?: keyof import('./navigationTypes').SettingsStackParamList
|
||||
): void => {
|
||||
navigation.navigate('PrivacySettings', {
|
||||
preferences,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Accessibility Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param preferences - Optional accessibility preferences to pre-populate
|
||||
* @param fromScreen - Optional source screen for back navigation
|
||||
*/
|
||||
export const navigateToAccessibilitySettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
preferences?: UserPreferences['accessibilityPreferences'],
|
||||
fromScreen?: keyof import('./navigationTypes').SettingsStackParamList
|
||||
): void => {
|
||||
navigation.navigate('AccessibilitySettings', {
|
||||
preferences,
|
||||
fromScreen,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to About screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param showChangelog - Optional flag to show changelog
|
||||
* @param showLicenses - Optional flag to show licenses
|
||||
*/
|
||||
export const navigateToAbout = (
|
||||
navigation: SettingsNavigationProp,
|
||||
showChangelog?: boolean,
|
||||
showLicenses?: boolean
|
||||
): void => {
|
||||
navigation.navigate('About', {
|
||||
showChangelog,
|
||||
showLicenses,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Help & Support screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param topic - Optional help topic to navigate to
|
||||
* @param showContact - Optional flag to show contact form
|
||||
*/
|
||||
export const navigateToHelpSupport = (
|
||||
navigation: SettingsNavigationProp,
|
||||
topic?: 'faq' | 'troubleshooting' | 'contact' | 'feedback',
|
||||
showContact?: boolean
|
||||
): void => {
|
||||
navigation.navigate('HelpSupport', {
|
||||
topic,
|
||||
showContact,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Go back to previous screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
*/
|
||||
export const goBack = (navigation: SettingsNavigationProp): void => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset navigation stack to Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the settings screen
|
||||
*/
|
||||
export const resetToSettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
params?: {
|
||||
section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'Settings', params }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace current screen with Settings screen
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the settings screen
|
||||
*/
|
||||
export const replaceWithSettings = (
|
||||
navigation: SettingsNavigationProp,
|
||||
params?: {
|
||||
section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.replace('Settings', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Settings and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param params - Optional parameters for the settings screen
|
||||
*/
|
||||
export const navigateToSettingsAndClearStack = (
|
||||
navigation: SettingsNavigationProp,
|
||||
params?: {
|
||||
section?: 'profile' | 'security' | 'notifications' | 'clinical' | 'privacy' | 'accessibility' | 'about' | 'help';
|
||||
refresh?: boolean;
|
||||
}
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'Settings', params }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Profile Edit and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param profile - Optional profile data to pre-populate
|
||||
*/
|
||||
export const navigateToProfileEditAndClearStack = (
|
||||
navigation: SettingsNavigationProp,
|
||||
profile?: UserProfile
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'ProfileEdit', params: { profile } }],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to Security Settings and clear back stack
|
||||
* @param navigation - Navigation prop from React Navigation
|
||||
* @param showBiometricSetup - Optional flag to show biometric setup
|
||||
*/
|
||||
export const navigateToSecuritySettingsAndClearStack = (
|
||||
navigation: SettingsNavigationProp,
|
||||
showBiometricSetup?: boolean
|
||||
): void => {
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'SecuritySettings', params: { showBiometricSetup } }],
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: navigationUtils.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
394
app/modules/Settings/redux/settingsSlice.ts
Normal file
394
app/modules/Settings/redux/settingsSlice.ts
Normal file
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* File: settingsSlice.ts
|
||||
* Description: Settings state management slice
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { UserProfile, UserPreferences, SettingsState } from '../../../shared/types';
|
||||
|
||||
// ============================================================================
|
||||
// ASYNC THUNKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch User Profile Async Thunk
|
||||
*
|
||||
* Purpose: Fetch user profile from API
|
||||
*
|
||||
* @returns Promise with user profile data or error
|
||||
*/
|
||||
export const fetchUserProfile = createAsyncThunk(
|
||||
'settings/fetchUserProfile',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Mock user profile data
|
||||
const mockProfile: UserProfile = {
|
||||
id: '1',
|
||||
email: 'john.smith@hospital.com',
|
||||
firstName: 'Dr. John',
|
||||
lastName: 'Smith',
|
||||
role: 'ATTENDING_PHYSICIAN',
|
||||
department: 'Emergency Medicine',
|
||||
credentials: ['MD', 'FACEP'],
|
||||
specialties: ['Emergency Medicine', 'Trauma'],
|
||||
profilePicture: null,
|
||||
phoneNumber: '+1-555-0123',
|
||||
yearsOfExperience: 15,
|
||||
isActive: true,
|
||||
lastLogin: new Date().toISOString(),
|
||||
permissions: ['READ_PATIENTS', 'WRITE_PATIENTS', 'VIEW_ALERTS'],
|
||||
};
|
||||
|
||||
return mockProfile;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to fetch user profile.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Update User Profile Async Thunk
|
||||
*
|
||||
* Purpose: Update user profile information
|
||||
*
|
||||
* @param profileData - Updated profile data
|
||||
* @returns Promise with updated profile or error
|
||||
*/
|
||||
export const updateUserProfile = createAsyncThunk(
|
||||
'settings/updateUserProfile',
|
||||
async (profileData: Partial<UserProfile> & { id: string }, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
return profileData;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to update user profile.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Fetch User Preferences Async Thunk
|
||||
*
|
||||
* Purpose: Fetch user preferences from API
|
||||
*
|
||||
* @returns Promise with user preferences data or error
|
||||
*/
|
||||
export const fetchUserPreferences = createAsyncThunk(
|
||||
'settings/fetchUserPreferences',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Mock user preferences data
|
||||
const mockPreferences: UserPreferences = {
|
||||
notifications: {
|
||||
criticalAlerts: true,
|
||||
patientUpdates: true,
|
||||
shiftChanges: false,
|
||||
systemMaintenance: false,
|
||||
soundEnabled: true,
|
||||
vibrationEnabled: true,
|
||||
},
|
||||
clinical: {
|
||||
autoRefreshInterval: 30,
|
||||
showVitalSigns: true,
|
||||
showAllergies: true,
|
||||
showMedications: true,
|
||||
defaultView: 'list',
|
||||
},
|
||||
privacy: {
|
||||
biometricEnabled: true,
|
||||
sessionTimeout: 30,
|
||||
dataRetention: 90,
|
||||
auditLogging: true,
|
||||
},
|
||||
accessibility: {
|
||||
fontSize: 'medium',
|
||||
highContrast: false,
|
||||
screenReader: false,
|
||||
reducedMotion: false,
|
||||
},
|
||||
};
|
||||
|
||||
return mockPreferences;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to fetch user preferences.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Update User Preferences Async Thunk
|
||||
*
|
||||
* Purpose: Update user preferences
|
||||
*
|
||||
* @param preferences - Updated preferences data
|
||||
* @returns Promise with updated preferences or error
|
||||
*/
|
||||
export const updateUserPreferences = createAsyncThunk(
|
||||
'settings/updateUserPreferences',
|
||||
async (preferences: Partial<UserPreferences>, { rejectWithValue }) => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
return preferences;
|
||||
} catch (error) {
|
||||
return rejectWithValue('Failed to update user preferences.');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// INITIAL STATE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initial Settings State
|
||||
*
|
||||
* Purpose: Define the initial state for settings
|
||||
*
|
||||
* Features:
|
||||
* - User profile management
|
||||
* - User preferences management
|
||||
* - Loading states for async operations
|
||||
* - Error handling and messages
|
||||
* - Settings validation
|
||||
*/
|
||||
const initialState: SettingsState = {
|
||||
// User profile
|
||||
userProfile: null,
|
||||
|
||||
// User preferences
|
||||
userPreferences: null,
|
||||
|
||||
// Loading states
|
||||
isLoading: false,
|
||||
isUpdatingProfile: false,
|
||||
isUpdatingPreferences: false,
|
||||
|
||||
// Error handling
|
||||
error: null,
|
||||
|
||||
// Settings validation
|
||||
isProfileValid: true,
|
||||
isPreferencesValid: true,
|
||||
|
||||
// Last updated timestamps
|
||||
profileLastUpdated: null,
|
||||
preferencesLastUpdated: null,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SETTINGS SLICE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Settings Slice
|
||||
*
|
||||
* Purpose: Redux slice for settings state management
|
||||
*
|
||||
* Features:
|
||||
* - User profile management
|
||||
* - User preferences management
|
||||
* - Settings validation
|
||||
* - Error handling
|
||||
* - Loading states
|
||||
*/
|
||||
const settingsSlice = createSlice({
|
||||
name: 'settings',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* Clear Error Action
|
||||
*
|
||||
* Purpose: Clear settings errors
|
||||
*/
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Profile Validation Action
|
||||
*
|
||||
* Purpose: Set profile validation status
|
||||
*/
|
||||
setProfileValidation: (state, action: PayloadAction<boolean>) => {
|
||||
state.isProfileValid = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Preferences Validation Action
|
||||
*
|
||||
* Purpose: Set preferences validation status
|
||||
*/
|
||||
setPreferencesValidation: (state, action: PayloadAction<boolean>) => {
|
||||
state.isPreferencesValid = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Profile Field Action
|
||||
*
|
||||
* Purpose: Update a specific profile field
|
||||
*/
|
||||
updateProfileField: (state, action: PayloadAction<{ field: keyof UserProfile; value: any }>) => {
|
||||
if (state.userProfile) {
|
||||
(state.userProfile as any)[action.payload.field] = action.payload.value;
|
||||
state.profileLastUpdated = new Date();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Preference Field Action
|
||||
*
|
||||
* Purpose: Update a specific preference field
|
||||
*/
|
||||
updatePreferenceField: (state, action: PayloadAction<{ path: string; value: any }>) => {
|
||||
if (state.userPreferences) {
|
||||
const path = action.payload.path.split('.');
|
||||
let current: any = state.userPreferences;
|
||||
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
current = current[path[i]];
|
||||
}
|
||||
|
||||
current[path[path.length - 1]] = action.payload.value;
|
||||
state.preferencesLastUpdated = new Date();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset Profile Action
|
||||
*
|
||||
* Purpose: Reset profile to last saved state
|
||||
*/
|
||||
resetProfile: (state) => {
|
||||
// TODO: Implement reset logic
|
||||
state.isProfileValid = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset Preferences Action
|
||||
*
|
||||
* Purpose: Reset preferences to last saved state
|
||||
*/
|
||||
resetPreferences: (state) => {
|
||||
// TODO: Implement reset logic
|
||||
state.isPreferencesValid = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear Settings Cache Action
|
||||
*
|
||||
* Purpose: Clear settings data cache
|
||||
*/
|
||||
clearSettingsCache: (state) => {
|
||||
state.userProfile = null;
|
||||
state.userPreferences = null;
|
||||
state.profileLastUpdated = null;
|
||||
state.preferencesLastUpdated = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// Fetch User Profile
|
||||
builder
|
||||
.addCase(fetchUserProfile.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchUserProfile.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.userProfile = action.payload;
|
||||
state.profileLastUpdated = new Date();
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchUserProfile.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Update User Profile
|
||||
builder
|
||||
.addCase(updateUserProfile.pending, (state) => {
|
||||
state.isUpdatingProfile = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updateUserProfile.fulfilled, (state, action) => {
|
||||
state.isUpdatingProfile = false;
|
||||
if (state.userProfile) {
|
||||
state.userProfile = { ...state.userProfile, ...action.payload };
|
||||
state.profileLastUpdated = new Date();
|
||||
}
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updateUserProfile.rejected, (state, action) => {
|
||||
state.isUpdatingProfile = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Fetch User Preferences
|
||||
builder
|
||||
.addCase(fetchUserPreferences.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchUserPreferences.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.userPreferences = action.payload;
|
||||
state.preferencesLastUpdated = new Date();
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchUserPreferences.rejected, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
|
||||
// Update User Preferences
|
||||
builder
|
||||
.addCase(updateUserPreferences.pending, (state) => {
|
||||
state.isUpdatingPreferences = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updateUserPreferences.fulfilled, (state, action) => {
|
||||
state.isUpdatingPreferences = false;
|
||||
if (state.userPreferences) {
|
||||
state.userPreferences = { ...state.userPreferences, ...action.payload };
|
||||
state.preferencesLastUpdated = new Date();
|
||||
}
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updateUserPreferences.rejected, (state, action) => {
|
||||
state.isUpdatingPreferences = false;
|
||||
state.error = action.payload as string;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
|
||||
export const {
|
||||
clearError,
|
||||
setProfileValidation,
|
||||
setPreferencesValidation,
|
||||
updateProfileField,
|
||||
updatePreferenceField,
|
||||
resetProfile,
|
||||
resetPreferences,
|
||||
clearSettingsCache,
|
||||
} = settingsSlice.actions;
|
||||
|
||||
export default settingsSlice.reducer;
|
||||
|
||||
/*
|
||||
* End of File: settingsSlice.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
610
app/modules/Settings/screens/SettingsScreen.tsx
Normal file
610
app/modules/Settings/screens/SettingsScreen.tsx
Normal file
@ -0,0 +1,610 @@
|
||||
/*
|
||||
* File: SettingsScreen.tsx
|
||||
* Description: Main settings screen with profile management and app preferences
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
RefreshControl,
|
||||
Image,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../../theme/theme';
|
||||
import {
|
||||
SettingsSection,
|
||||
SettingsSectionData,
|
||||
SettingsItem
|
||||
} from '../../../shared/types';
|
||||
import { SettingsHeader } from '../components/SettingsHeader';
|
||||
import { SettingsSectionComponent } from '../components/SettingsSectionComponent';
|
||||
import { ProfileCard } from '../components/ProfileCard';
|
||||
import { CustomModal } from '../../../shared/components';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
||||
import { logoutUser } from '../../Auth/redux/authActions';
|
||||
import {
|
||||
selectUser,
|
||||
selectUserDisplayName,
|
||||
selectUserEmail,
|
||||
selectUserFirstName,
|
||||
selectUserLastName,
|
||||
selectUserProfilePhoto,
|
||||
selectNotificationPreferences,
|
||||
selectDashboardSettings
|
||||
} from '../../Auth/redux/authSelectors';
|
||||
|
||||
/**
|
||||
* SettingsScreenProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the SettingsScreen component
|
||||
*
|
||||
* Props:
|
||||
* - navigation: React Navigation object for screen navigation
|
||||
*/
|
||||
interface SettingsScreenProps {
|
||||
navigation: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsScreen Component
|
||||
*
|
||||
* Purpose: Main settings screen for user profile management and app preferences
|
||||
*
|
||||
* Features:
|
||||
* 1. User profile overview and quick access
|
||||
* 2. Comprehensive settings sections
|
||||
* 3. Navigation to detailed settings screens
|
||||
* 4. Pull-to-refresh functionality
|
||||
* 5. Mock data generation for demonstration
|
||||
*
|
||||
* Settings Sections:
|
||||
* - Profile: Personal and professional information
|
||||
* - Notifications: Alert and notification preferences
|
||||
* - Clinical: Clinical workflow preferences
|
||||
* - Privacy: Security and privacy settings
|
||||
* - Accessibility: Accessibility features
|
||||
* - About: App information and help
|
||||
* - Logout: Sign out functionality
|
||||
*/
|
||||
export const SettingsScreen: React.FC<SettingsScreenProps> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
// Settings sections state
|
||||
const [settingsSections, setSettingsSections] = useState<SettingsSectionData[]>([]);
|
||||
|
||||
// UI state
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// Modal state
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [modalConfig, setModalConfig] = useState({
|
||||
title: '',
|
||||
message: '',
|
||||
type: 'info' as 'success' | 'error' | 'warning' | 'info' | 'confirm',
|
||||
onConfirm: () => {},
|
||||
showCancel: false,
|
||||
icon: '',
|
||||
});
|
||||
|
||||
// Redux dispatch and selectors
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// User data from Redux
|
||||
const user = useAppSelector(selectUser);
|
||||
const userDisplayName = useAppSelector(selectUserDisplayName);
|
||||
const userEmail = useAppSelector(selectUserEmail);
|
||||
const userFirstName = useAppSelector(selectUserFirstName);
|
||||
const userLastName = useAppSelector(selectUserLastName);
|
||||
const userProfilePhoto = useAppSelector(selectUserProfilePhoto);
|
||||
const notificationPreferences = useAppSelector(selectNotificationPreferences);
|
||||
const dashboardSettings = useAppSelector(selectDashboardSettings);
|
||||
console.log('user details i got', user);
|
||||
|
||||
// ============================================================================
|
||||
// SETTINGS SECTIONS GENERATION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* generateSettingsSections Function
|
||||
*
|
||||
* Purpose: Generate settings sections with items for the settings screen
|
||||
*
|
||||
* Returns: Array of SettingsSectionData with navigation and action items
|
||||
*/
|
||||
const generateSettingsSections = (): SettingsSectionData[] => [
|
||||
{
|
||||
id: 'PROFILE',
|
||||
title: 'Profile & Account',
|
||||
items: [
|
||||
{
|
||||
id: 'edit-profile',
|
||||
title: 'Edit Profile',
|
||||
subtitle: 'Update personal and professional information',
|
||||
icon: 'user',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('PROFILE'),
|
||||
},
|
||||
{
|
||||
id: 'change-password',
|
||||
title: 'Change Password',
|
||||
subtitle: 'Update your account password',
|
||||
icon: 'lock',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('CHANGE_PASSWORD'),
|
||||
},
|
||||
{
|
||||
id: 'security-settings',
|
||||
title: 'Security Settings',
|
||||
subtitle: 'Two-factor authentication and biometrics',
|
||||
icon: 'shield',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('SECURITY'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'NOTIFICATIONS',
|
||||
title: 'Notifications',
|
||||
items: [
|
||||
{
|
||||
id: 'notification-preferences',
|
||||
title: 'Notification Preferences',
|
||||
subtitle: 'Manage alert and notification settings',
|
||||
icon: 'bell',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('NOTIFICATIONS'),
|
||||
},
|
||||
{
|
||||
id: 'quiet-hours',
|
||||
title: 'Quiet Hours',
|
||||
subtitle: 'Set do not disturb periods',
|
||||
icon: 'moon',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('QUIET_HOURS'),
|
||||
},
|
||||
{
|
||||
id: 'push-notifications',
|
||||
title: 'Push Notifications',
|
||||
subtitle: 'Enable or disable push notifications',
|
||||
icon: 'phone',
|
||||
type: 'TOGGLE',
|
||||
value: notificationPreferences?.system_notifications.push,
|
||||
onPress: () => handleToggleSetting('pushNotifications'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'PRIVACY',
|
||||
title: 'Privacy & Security',
|
||||
items: [
|
||||
{
|
||||
id: 'privacy-settings',
|
||||
title: 'Privacy Settings',
|
||||
subtitle: 'Manage data sharing and privacy controls',
|
||||
icon: 'settings',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('PRIVACY'),
|
||||
},
|
||||
{
|
||||
id: 'biometric-auth',
|
||||
title: 'Biometric Authentication',
|
||||
subtitle: 'Use fingerprint or face ID',
|
||||
icon: 'lock',
|
||||
type: 'TOGGLE',
|
||||
value: false, // TODO: Add biometric auth preference to user data
|
||||
onPress: () => handleToggleSetting('biometricAuth'),
|
||||
},
|
||||
{
|
||||
id: 'session-timeout',
|
||||
title: 'Session Timeout',
|
||||
subtitle: 'Auto-logout after inactivity',
|
||||
icon: 'clock',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('SESSION_TIMEOUT'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: 'ABOUT',
|
||||
title: 'About & Support',
|
||||
items: [
|
||||
{
|
||||
id: 'app-info',
|
||||
title: 'App Information',
|
||||
subtitle: 'Version, build number, and details',
|
||||
icon: 'smartphone',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('APP_INFO'),
|
||||
},
|
||||
{
|
||||
id: 'help-support',
|
||||
title: 'Help & Support',
|
||||
subtitle: 'Contact support and view documentation',
|
||||
icon: 'help',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('HELP'),
|
||||
},
|
||||
{
|
||||
id: 'feedback',
|
||||
title: 'Send Feedback',
|
||||
subtitle: 'Report bugs or suggest improvements',
|
||||
icon: 'rss',
|
||||
type: 'NAVIGATION',
|
||||
onPress: () => handleNavigation('FEEDBACK'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'LOGOUT',
|
||||
title: 'Account',
|
||||
items: [
|
||||
{
|
||||
id: 'logout',
|
||||
title: 'Sign Out',
|
||||
subtitle: 'Sign out of your account',
|
||||
icon: 'log-out',
|
||||
type: 'ACTION',
|
||||
onPress: handleLogout,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// EFFECTS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* useEffect for initial settings sections generation
|
||||
*
|
||||
* Purpose: Generate settings sections when component mounts or user data changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
setSettingsSections(generateSettingsSections());
|
||||
}, [user, notificationPreferences, dashboardSettings]);
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* handleRefresh Function
|
||||
*
|
||||
* Purpose: Handle pull-to-refresh functionality to update settings data
|
||||
*
|
||||
* Flow:
|
||||
* 1. Set refreshing state to true (show loading indicator)
|
||||
* 2. Simulate API call with delay
|
||||
* 3. Regenerate settings sections with current user data
|
||||
* 4. Set refreshing state to false (hide loading indicator)
|
||||
*/
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
|
||||
// Simulate API call with 1-second delay
|
||||
await new Promise<void>(resolve => setTimeout(() => resolve(), 1000));
|
||||
|
||||
// Regenerate settings sections with current user data
|
||||
setSettingsSections(generateSettingsSections());
|
||||
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleNavigation Function
|
||||
*
|
||||
* Purpose: Handle navigation to different settings screens
|
||||
*
|
||||
* @param screen - Screen to navigate to
|
||||
*/
|
||||
const handleNavigation = (screen: string) => {
|
||||
// TODO: Implement navigation to specific settings screens
|
||||
console.log('Navigate to:', screen);
|
||||
setModalConfig({
|
||||
title: 'Navigation',
|
||||
message: `Navigate to ${screen} screen`,
|
||||
type: 'info',
|
||||
onConfirm: () => {},
|
||||
showCancel: false,
|
||||
icon: 'info',
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleToggleSetting Function
|
||||
*
|
||||
* Purpose: Handle toggle settings changes
|
||||
*
|
||||
* @param setting - Setting to toggle
|
||||
*/
|
||||
const handleToggleSetting = (setting: string) => {
|
||||
// TODO: Implement setting toggle logic
|
||||
console.log('Toggle setting:', setting);
|
||||
setModalConfig({
|
||||
title: 'Setting Toggle',
|
||||
message: `Toggle ${setting} setting`,
|
||||
type: 'info',
|
||||
icon: 'info',
|
||||
onConfirm: () => {},
|
||||
showCancel: false,
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleLogout Function
|
||||
*
|
||||
* Purpose: Handle user logout with Redux integration
|
||||
*
|
||||
* Flow:
|
||||
* 1. Show confirmation dialog
|
||||
* 2. Dispatch logout action to Redux
|
||||
* 3. Clear authentication state
|
||||
* 4. Show success message
|
||||
* 5. Automatically navigate to login screen via Redux state change
|
||||
*/
|
||||
const handleLogout = () => {
|
||||
setModalConfig({
|
||||
title: 'Sign Out',
|
||||
message: 'Are you sure you want to sign out?',
|
||||
type: 'confirm',
|
||||
icon: 'log-out',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
// Dispatch logout thunk to Redux
|
||||
await dispatch(logoutUser());
|
||||
|
||||
// Log the logout action
|
||||
console.log('User logged out successfully');
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
setModalConfig({
|
||||
title: 'Error',
|
||||
message: 'Failed to sign out. Please try again.',
|
||||
type: 'error',
|
||||
icon: 'info',
|
||||
onConfirm: () => {},
|
||||
showCancel: false,
|
||||
});
|
||||
setModalVisible(true);
|
||||
}
|
||||
},
|
||||
showCancel: true,
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* handleProfilePress Function
|
||||
*
|
||||
* Purpose: Handle profile card press navigation
|
||||
*/
|
||||
const handleProfilePress = () => {
|
||||
handleNavigation('PROFILE');
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MAIN RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Settings header with title */}
|
||||
<SettingsHeader title="Settings" />
|
||||
|
||||
{/* Scrollable settings content */}
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
colors={[theme.colors.primary]}
|
||||
tintColor={theme.colors.primary}
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Profile card section */}
|
||||
{user && (
|
||||
<View style={styles.profileCard}>
|
||||
<TouchableOpacity onPress={handleProfilePress} activeOpacity={0.7}>
|
||||
<View style={styles.profileHeader}>
|
||||
<View style={styles.profileImageContainer}>
|
||||
{user.profile_photo_url ? (
|
||||
<Image
|
||||
source={{ uri: user.profile_photo_url }}
|
||||
style={styles.profileImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.fallbackAvatar}>
|
||||
<Text style={styles.fallbackText}>
|
||||
{user.first_name.charAt(0)}{user.last_name.charAt(0)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.profileInfo}>
|
||||
<Text style={styles.profileName}>
|
||||
{user.display_name || `${user.first_name} ${user.last_name}`}
|
||||
</Text>
|
||||
<Text style={styles.profileEmail}>{user.email}</Text>
|
||||
<Text style={styles.profileRole}>{user.dashboard_role}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.editIcon}>
|
||||
<Text style={styles.editText}>Edit</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Settings sections */}
|
||||
{settingsSections.map((section) => (
|
||||
<SettingsSectionComponent
|
||||
key={section.id}
|
||||
section={section}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Bottom spacing for tab bar */}
|
||||
<View style={styles.bottomSpacing} />
|
||||
</ScrollView>
|
||||
|
||||
{/* Custom Modal */}
|
||||
<CustomModal
|
||||
visible={modalVisible}
|
||||
title={modalConfig.title}
|
||||
message={modalConfig.message}
|
||||
type={modalConfig.type}
|
||||
onConfirm={modalConfig.onConfirm}
|
||||
showCancel={modalConfig.showCancel}
|
||||
icon={modalConfig.icon}
|
||||
confirmText={modalConfig.type === 'confirm' ? 'Sign Out' : 'OK'}
|
||||
cancelText="Cancel"
|
||||
onClose={() => setModalVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES SECTION
|
||||
// ============================================================================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Main container for the settings 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,
|
||||
},
|
||||
|
||||
// Scroll view styling
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Scroll content styling
|
||||
scrollContent: {
|
||||
paddingHorizontal: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Bottom spacing for tab bar
|
||||
bottomSpacing: {
|
||||
height: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Profile card styles
|
||||
profileCard: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
padding: theme.spacing.md,
|
||||
marginBottom: theme.spacing.md,
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
profileHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
profileImageContainer: {
|
||||
marginRight: theme.spacing.md,
|
||||
},
|
||||
|
||||
profileImage: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
},
|
||||
|
||||
fallbackAvatar: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: theme.colors.primary,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
fallbackText: {
|
||||
color: theme.colors.background,
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
},
|
||||
|
||||
profileInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
profileName: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
profileEmail: {
|
||||
fontSize: theme.typography.fontSize.bodyMedium,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
marginBottom: theme.spacing.xs,
|
||||
},
|
||||
|
||||
profileRole: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.primary,
|
||||
},
|
||||
|
||||
editIcon: {
|
||||
paddingHorizontal: theme.spacing.sm,
|
||||
paddingVertical: theme.spacing.xs,
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderRadius: theme.borderRadius.small,
|
||||
},
|
||||
|
||||
editText: {
|
||||
fontSize: theme.typography.fontSize.bodySmall,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.primary,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: SettingsScreen.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
148
app/navigation/MainTabNavigator.tsx
Normal file
148
app/navigation/MainTabNavigator.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* File: MainTabNavigator.tsx
|
||||
* Description: Bottom tab navigator for the main app interface
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { theme } from '../theme/theme';
|
||||
import { DashboardStackNavigator } from '../modules/Dashboard/navigation';
|
||||
import { SettingsStackNavigator } from '../modules/Settings/navigation';
|
||||
import { MainTabParamList } from './navigationTypes';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
// Create the bottom tab navigator
|
||||
const Tab = createBottomTabNavigator<MainTabParamList>();
|
||||
|
||||
/**
|
||||
* MainTabNavigator Component
|
||||
*
|
||||
* Purpose: Creates the bottom tab navigation for the main app interface
|
||||
*
|
||||
* Tab Structure:
|
||||
* - Dashboard: Main ER dashboard with patient overview and statistics
|
||||
* - Patients: Detailed patient list and management interface
|
||||
* - Alerts: Critical notifications and alert management
|
||||
* - Reports: Medical reports and documentation access
|
||||
* - Settings: User preferences and app configuration
|
||||
*
|
||||
* Features:
|
||||
* - Consistent styling with app theme
|
||||
* - Tab-specific icons and labels
|
||||
* - Proper parameter passing to screens
|
||||
* - Accessibility support
|
||||
*/
|
||||
export const MainTabNavigator: React.FC = () => {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={{
|
||||
// Tab bar styling for active and inactive states
|
||||
tabBarActiveTintColor: theme.colors.primary, // Blue color for active tab
|
||||
tabBarInactiveTintColor: theme.colors.textMuted, // Gray color for inactive tab
|
||||
|
||||
// Tab bar container styling
|
||||
tabBarStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderTopColor: theme.colors.border,
|
||||
borderTopWidth: 1,
|
||||
paddingBottom: 8,
|
||||
paddingTop: 8,
|
||||
height: 60, // Fixed height for tab bar
|
||||
},
|
||||
|
||||
// Header styling for each screen
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderBottomColor: theme.colors.border,
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: theme.colors.textPrimary,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Dashboard Tab - Main ER overview */}
|
||||
<Tab.Screen
|
||||
name="Dashboard"
|
||||
component={DashboardStackNavigator}
|
||||
options={{
|
||||
title: 'ER Dashboard',
|
||||
tabBarLabel: 'Dashboard',
|
||||
// TODO: Add tab bar icon
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<MaterialIcons name="dashboard" size={size} color={color} />
|
||||
),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Patients Tab - Patient list and management */}
|
||||
<Tab.Screen
|
||||
name="Patients"
|
||||
component={DashboardStackNavigator} // TODO: Replace with actual PatientsScreen
|
||||
options={{
|
||||
title: 'Patient List',
|
||||
tabBarLabel: 'Patients',
|
||||
// TODO: Add tab bar icon
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<MaterialIcons name="people" size={size} color={color} />
|
||||
),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Alerts Tab - Critical notifications */}
|
||||
<Tab.Screen
|
||||
name="Alerts"
|
||||
component={DashboardStackNavigator} // TODO: Replace with actual AlertsScreen
|
||||
options={{
|
||||
title: 'Alerts',
|
||||
tabBarLabel: 'Alerts',
|
||||
// TODO: Add tab bar icon
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<MaterialIcons name="notifications" size={size} color={color} />
|
||||
),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Reports Tab - Medical documentation */}
|
||||
<Tab.Screen
|
||||
name="Reports"
|
||||
component={DashboardStackNavigator} // TODO: Replace with actual ReportsScreen
|
||||
options={{
|
||||
title: 'Reports',
|
||||
tabBarLabel: 'Reports',
|
||||
// TODO: Add tab bar icon
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<MaterialIcons name="description" size={size} color={color} />
|
||||
),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Settings Tab - User preferences */}
|
||||
<Tab.Screen
|
||||
name="Settings"
|
||||
component={SettingsStackNavigator}
|
||||
options={{
|
||||
title: 'Settings',
|
||||
tabBarLabel: 'Settings',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<MaterialIcons name="settings" size={size} color={color} />
|
||||
),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: MainTabNavigator.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
80
app/navigation/RootStackNavigator.tsx
Normal file
80
app/navigation/RootStackNavigator.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* File: RootStackNavigator.tsx
|
||||
* Description: Root stack navigator managing authentication and main app flow
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { AuthStackNavigator } from '../modules/Auth/navigation';
|
||||
import { MainTabNavigator } from './MainTabNavigator';
|
||||
import { RootStackParamList } from './navigationTypes';
|
||||
import { useAppSelector } from '../store/hooks';
|
||||
import { selectIsAuthenticated, selectIsOnboarded } from '../modules/Auth/redux/authSelectors';
|
||||
import ResetPasswordScreen from '../modules/Auth/screens/ResetPasswordScreen';
|
||||
|
||||
// Create the stack navigator
|
||||
const Stack = createStackNavigator<RootStackParamList>();
|
||||
|
||||
/**
|
||||
* RootStackNavigatorProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the RootStackNavigator component
|
||||
*
|
||||
* Props:
|
||||
* - isAuthenticated: Boolean indicating if user is logged in
|
||||
*/
|
||||
interface RootStackNavigatorProps {
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* RootStackNavigator Component
|
||||
*
|
||||
* Purpose: Manages the main navigation flow between authentication, onboarding, and main app
|
||||
*
|
||||
* Navigation Flow:
|
||||
* 1. App starts → Check authentication status
|
||||
* 2. If not authenticated → Show AuthStackNavigator (LoginScreen)
|
||||
* 3. If authenticated but not onboarded → Show ResetPasswordScreen
|
||||
* 4. If authenticated and onboarded → Show MainTabNavigator (dashboard)
|
||||
*
|
||||
* Features:
|
||||
* - Conditional rendering based on authentication and onboarding status
|
||||
* - Seamless transition between auth, onboarding, and main app
|
||||
* - Proper prop passing to child components
|
||||
* - Hidden headers for custom styling
|
||||
*/
|
||||
export const RootStackNavigator: React.FC<RootStackNavigatorProps> = ({
|
||||
isAuthenticated,
|
||||
}) => {
|
||||
// Get onboarding status from Redux
|
||||
const isOnboarded = useAppSelector(selectIsOnboarded);
|
||||
|
||||
return (
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerShown: false, // Hide default headers for custom styling
|
||||
}}
|
||||
>
|
||||
{/* Conditional rendering based on authentication and onboarding status */}
|
||||
{!isAuthenticated ? (
|
||||
// Show auth stack if user is not authenticated
|
||||
<Stack.Screen name="Auth" component={AuthStackNavigator} />
|
||||
) : !isOnboarded ? (
|
||||
// Show reset password screen if user is authenticated but not onboarded
|
||||
<Stack.Screen name="ResetPassword" component={ResetPasswordScreen} />
|
||||
) : (
|
||||
// Show main app tabs if user is authenticated and onboarded
|
||||
<Stack.Screen name="Main" component={MainTabNavigator} />
|
||||
)}
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: RootStackNavigator.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
59
app/navigation/index.ts
Normal file
59
app/navigation/index.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Main navigation module exports
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION COMPONENTS
|
||||
// ============================================================================
|
||||
|
||||
// Main navigation components
|
||||
export { MainTabNavigator } from './MainTabNavigator';
|
||||
export { RootStackNavigator } from './RootStackNavigator';
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION TYPES
|
||||
// ============================================================================
|
||||
|
||||
// Type definitions for navigation
|
||||
export type {
|
||||
RootStackParamList,
|
||||
MainTabParamList,
|
||||
DashboardScreenParams,
|
||||
PatientsScreenParams,
|
||||
AlertsScreenParams,
|
||||
ReportsScreenParams,
|
||||
SettingsScreenParams,
|
||||
NavigationProps,
|
||||
TabNavigationProps,
|
||||
NavigationRef,
|
||||
} from './navigationTypes';
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION UTILITIES
|
||||
// ============================================================================
|
||||
|
||||
// Navigation utility functions
|
||||
export {
|
||||
navigationRef,
|
||||
setNavigationRef,
|
||||
navigateToScreen,
|
||||
goBack,
|
||||
resetNavigation,
|
||||
navigateToDashboard,
|
||||
navigateToPatientDetails,
|
||||
navigateToAlerts,
|
||||
navigateToReports,
|
||||
navigateToSettings,
|
||||
navigateToLogin,
|
||||
navigateToMainApp,
|
||||
handleNavigationError,
|
||||
} from './navigationUtils';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
190
app/navigation/navigationTypes.ts
Normal file
190
app/navigation/navigationTypes.ts
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* File: navigationTypes.ts
|
||||
* Description: TypeScript type definitions for navigation parameters and routes
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { NavigatorScreenParams } from '@react-navigation/native';
|
||||
import { ERDashboard, Patient, Alert as AlertType } from '../shared/types';
|
||||
|
||||
// ============================================================================
|
||||
// ROOT NAVIGATION TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* RootStackParamList
|
||||
*
|
||||
* Purpose: Defines the main navigation stack parameters for the entire app
|
||||
*
|
||||
* Routes:
|
||||
* - Auth: Authentication stack navigator
|
||||
* - Main: Main app with bottom tab navigation
|
||||
*/
|
||||
export type RootStackParamList = {
|
||||
Auth: undefined; // Auth stack navigator handles its own screens
|
||||
ResetPassword: undefined; // Reset password screen for onboarding
|
||||
Main: NavigatorScreenParams<MainTabParamList>; // Main app with tab navigation
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MAIN TAB NAVIGATION TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* MainTabParamList
|
||||
*
|
||||
* Purpose: Defines the bottom tab navigation parameters
|
||||
*
|
||||
* Tabs:
|
||||
* - Dashboard: ER dashboard with patient overview
|
||||
* - Patients: Patient list and management
|
||||
* - Alerts: Critical notifications and alerts
|
||||
* - Reports: Medical reports and documentation
|
||||
* - Settings: User preferences and app configuration
|
||||
*/
|
||||
export type MainTabParamList = {
|
||||
Dashboard: DashboardScreenParams; // Dashboard with initial data
|
||||
Patients: PatientsScreenParams; // Patient list screen
|
||||
Alerts: AlertsScreenParams; // Alerts screen
|
||||
Reports: ReportsScreenParams; // Reports screen
|
||||
Settings: SettingsScreenParams; // Settings screen
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SCREEN PARAMETER TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* DashboardScreenParams
|
||||
*
|
||||
* Purpose: Parameters passed to the dashboard screen
|
||||
*
|
||||
* Parameters:
|
||||
* - dashboard: ER dashboard data including statistics and shift info
|
||||
* - patients: Array of patient data for display
|
||||
* - alerts: Array of critical alerts and notifications
|
||||
*/
|
||||
export interface DashboardScreenParams {
|
||||
dashboard: ERDashboard;
|
||||
patients: Patient[];
|
||||
alerts: AlertType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* PatientsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the patients screen
|
||||
*
|
||||
* Parameters:
|
||||
* - filter: Optional filter to apply to patient list
|
||||
* - searchQuery: Optional search term for patient search
|
||||
*/
|
||||
export interface PatientsScreenParams {
|
||||
filter?: 'all' | 'critical' | 'active' | 'pending';
|
||||
searchQuery?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the alerts screen
|
||||
*
|
||||
* Parameters:
|
||||
* - priority: Optional priority filter for alerts
|
||||
* - unreadOnly: Optional flag to show only unread alerts
|
||||
*/
|
||||
export interface AlertsScreenParams {
|
||||
priority?: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
|
||||
unreadOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ReportsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the reports screen
|
||||
*
|
||||
* Parameters:
|
||||
* - patientId: Optional patient ID to filter reports
|
||||
* - dateRange: Optional date range for report filtering
|
||||
*/
|
||||
export interface ReportsScreenParams {
|
||||
patientId?: string;
|
||||
dateRange?: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsScreenParams
|
||||
*
|
||||
* Purpose: Parameters for the settings screen
|
||||
*
|
||||
* Parameters:
|
||||
* - section: Optional section to navigate to within settings
|
||||
*/
|
||||
export interface SettingsScreenParams {
|
||||
section?: 'profile' | 'notifications' | 'security' | 'about';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION PROPS TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* NavigationProps
|
||||
*
|
||||
* Purpose: Common navigation props used across components
|
||||
*
|
||||
* Properties:
|
||||
* - navigation: Navigation object for screen navigation
|
||||
* - route: Current route information and parameters
|
||||
*/
|
||||
export interface NavigationProps<T extends keyof RootStackParamList> {
|
||||
navigation: any; // Will be properly typed when navigation is set up
|
||||
route: {
|
||||
params: RootStackParamList[T];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TabNavigationProps
|
||||
*
|
||||
* Purpose: Navigation props for tab screens
|
||||
*
|
||||
* Properties:
|
||||
* - navigation: Tab navigation object
|
||||
* - route: Current tab route information
|
||||
*/
|
||||
export interface TabNavigationProps<T extends keyof MainTabParamList> {
|
||||
navigation: any; // Will be properly typed when navigation is set up
|
||||
route: {
|
||||
params: MainTabParamList[T];
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION UTILITY TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* NavigationRef
|
||||
*
|
||||
* Purpose: Reference to the navigation object for programmatic navigation
|
||||
*
|
||||
* Usage:
|
||||
* - Used for navigation from outside React components
|
||||
* - Enables navigation from services, utilities, or event handlers
|
||||
*/
|
||||
export type NavigationRef = {
|
||||
navigate: (name: keyof RootStackParamList, params?: any) => void;
|
||||
goBack: () => void;
|
||||
reset: (state: any) => void;
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: navigationTypes.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
263
app/navigation/navigationUtils.ts
Normal file
263
app/navigation/navigationUtils.ts
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* File: navigationUtils.ts
|
||||
* Description: Navigation utility functions for common navigation operations
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { NavigationRef } from './navigationTypes';
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION REFERENCE
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* navigationRef
|
||||
*
|
||||
* Purpose: Global navigation reference for programmatic navigation
|
||||
*
|
||||
* Usage:
|
||||
* - Set this reference in the main App component
|
||||
* - Use for navigation from outside React components
|
||||
* - Enables navigation from services, utilities, or event handlers
|
||||
*
|
||||
* Example:
|
||||
* navigationRef.current?.navigate('Main');
|
||||
*/
|
||||
export let navigationRef: React.RefObject<NavigationRef> | null = null;
|
||||
|
||||
/**
|
||||
* setNavigationRef
|
||||
*
|
||||
* Purpose: Set the global navigation reference
|
||||
*
|
||||
* @param ref - Navigation reference object
|
||||
*
|
||||
* Usage:
|
||||
* Called from the main App component to set the navigation reference
|
||||
*/
|
||||
export const setNavigationRef = (ref: React.RefObject<NavigationRef | null>) => {
|
||||
navigationRef = ref as React.RefObject<NavigationRef>;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// NAVIGATION HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* navigateToScreen
|
||||
*
|
||||
* Purpose: Navigate to a specific screen with optional parameters
|
||||
*
|
||||
* @param screenName - Name of the screen to navigate to
|
||||
* @param params - Optional parameters to pass to the screen
|
||||
*
|
||||
* Example:
|
||||
* navigateToScreen('Main', { tab: 'Dashboard' });
|
||||
*/
|
||||
export const navigateToScreen = (screenName: string, params?: any) => {
|
||||
if (navigationRef?.current) {
|
||||
navigationRef.current.navigate(screenName as any, params);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* goBack
|
||||
*
|
||||
* Purpose: Navigate back to the previous screen
|
||||
*
|
||||
* Example:
|
||||
* goBack();
|
||||
*/
|
||||
export const goBack = () => {
|
||||
if (navigationRef?.current) {
|
||||
navigationRef.current.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* resetNavigation
|
||||
*
|
||||
* Purpose: Reset the navigation state to a specific screen
|
||||
*
|
||||
* @param screenName - Name of the screen to reset to
|
||||
* @param params - Optional parameters for the screen
|
||||
*
|
||||
* Example:
|
||||
* resetNavigation('Login'); // Reset to login screen
|
||||
*/
|
||||
export const resetNavigation = (screenName: string, params?: any) => {
|
||||
if (navigationRef?.current) {
|
||||
navigationRef.current.reset({
|
||||
index: 0,
|
||||
routes: [{ name: screenName, params }],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SCREEN-SPECIFIC NAVIGATION FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* navigateToDashboard
|
||||
*
|
||||
* Purpose: Navigate to the dashboard with specific data
|
||||
*
|
||||
* @param dashboard - Dashboard data to pass
|
||||
* @param patients - Patient data to pass
|
||||
* @param alerts - Alert data to pass
|
||||
*
|
||||
* Example:
|
||||
* navigateToDashboard(dashboardData, patientList, alertList);
|
||||
*/
|
||||
export const navigateToDashboard = (
|
||||
dashboard: any,
|
||||
patients: any[],
|
||||
alerts: any[]
|
||||
) => {
|
||||
navigateToScreen('Main', {
|
||||
screen: 'Dashboard',
|
||||
params: {
|
||||
dashboard,
|
||||
patients,
|
||||
alerts,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* navigateToPatientDetails
|
||||
*
|
||||
* Purpose: Navigate to patient details screen
|
||||
*
|
||||
* @param patientId - ID of the patient to view
|
||||
*
|
||||
* Example:
|
||||
* navigateToPatientDetails('patient123');
|
||||
*/
|
||||
export const navigateToPatientDetails = (patientId: string) => {
|
||||
navigateToScreen('Main', {
|
||||
screen: 'Patients',
|
||||
params: {
|
||||
patientId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* navigateToAlerts
|
||||
*
|
||||
* Purpose: Navigate to alerts screen with optional filters
|
||||
*
|
||||
* @param priority - Optional priority filter
|
||||
* @param unreadOnly - Optional flag for unread alerts only
|
||||
*
|
||||
* Example:
|
||||
* navigateToAlerts('CRITICAL', true);
|
||||
*/
|
||||
export const navigateToAlerts = (priority?: string, unreadOnly?: boolean) => {
|
||||
navigateToScreen('Main', {
|
||||
screen: 'Alerts',
|
||||
params: {
|
||||
priority,
|
||||
unreadOnly,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* navigateToReports
|
||||
*
|
||||
* Purpose: Navigate to reports screen with optional filters
|
||||
*
|
||||
* @param patientId - Optional patient ID filter
|
||||
* @param dateRange - Optional date range filter
|
||||
*
|
||||
* Example:
|
||||
* navigateToReports('patient123', { start: new Date(), end: new Date() });
|
||||
*/
|
||||
export const navigateToReports = (patientId?: string, dateRange?: any) => {
|
||||
navigateToScreen('Main', {
|
||||
screen: 'Reports',
|
||||
params: {
|
||||
patientId,
|
||||
dateRange,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* navigateToSettings
|
||||
*
|
||||
* Purpose: Navigate to settings screen with optional section
|
||||
*
|
||||
* @param section - Optional section to navigate to within settings
|
||||
*
|
||||
* Example:
|
||||
* navigateToSettings('profile');
|
||||
*/
|
||||
export const navigateToSettings = (section?: string) => {
|
||||
navigateToScreen('Main', {
|
||||
screen: 'Settings',
|
||||
params: {
|
||||
section,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// AUTHENTICATION NAVIGATION FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* navigateToLogin
|
||||
*
|
||||
* Purpose: Navigate to login screen
|
||||
*
|
||||
* Example:
|
||||
* navigateToLogin();
|
||||
*/
|
||||
export const navigateToLogin = () => {
|
||||
resetNavigation('Login');
|
||||
};
|
||||
|
||||
/**
|
||||
* navigateToMainApp
|
||||
*
|
||||
* Purpose: Navigate to main app after successful authentication
|
||||
*
|
||||
* Example:
|
||||
* navigateToMainApp();
|
||||
*/
|
||||
export const navigateToMainApp = () => {
|
||||
resetNavigation('Main');
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ERROR HANDLING
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* handleNavigationError
|
||||
*
|
||||
* Purpose: Handle navigation errors gracefully
|
||||
*
|
||||
* @param error - Navigation error object
|
||||
* @param fallbackScreen - Fallback screen to navigate to
|
||||
*
|
||||
* Example:
|
||||
* handleNavigationError(error, 'Login');
|
||||
*/
|
||||
export const handleNavigationError = (error: any, fallbackScreen: string) => {
|
||||
console.error('Navigation error:', error);
|
||||
|
||||
// Navigate to fallback screen
|
||||
resetNavigation(fallbackScreen);
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: navigationUtils.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
348
app/shared/components/CustomModal.tsx
Normal file
348
app/shared/components/CustomModal.tsx
Normal file
@ -0,0 +1,348 @@
|
||||
/*
|
||||
* File: CustomModal.tsx
|
||||
* Description: Custom modal component with matching UI design
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Modal,
|
||||
TouchableWithoutFeedback,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
import { theme } from '../../theme/theme';
|
||||
import Icon from 'react-native-vector-icons/Feather';
|
||||
|
||||
// ============================================================================
|
||||
// INTERFACES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* CustomModalProps Interface
|
||||
*
|
||||
* Purpose: Defines the props required by the CustomModal component
|
||||
*
|
||||
* Props:
|
||||
* - visible: Whether the modal is visible
|
||||
* - title: Modal title
|
||||
* - message: Modal message/content
|
||||
* - type: Modal type (success, error, warning, info, confirm)
|
||||
* - onConfirm: Callback for confirm action
|
||||
* - onCancel: Callback for cancel action
|
||||
* - confirmText: Text for confirm button
|
||||
* - cancelText: Text for cancel button
|
||||
* - onClose: Callback for closing modal
|
||||
* - showCancel: Whether to show cancel button
|
||||
* - icon: Custom icon name
|
||||
*/
|
||||
interface CustomModalProps {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
type?: 'success' | 'error' | 'warning' | 'info' | 'confirm';
|
||||
onConfirm?: () => void;
|
||||
onCancel?: () => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
onClose?: () => void;
|
||||
showCancel?: boolean;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CUSTOM MODAL COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* CustomModal Component
|
||||
*
|
||||
* Purpose: Displays a custom modal with consistent UI design
|
||||
*
|
||||
* Features:
|
||||
* - Multiple modal types (success, error, warning, info, confirm)
|
||||
* - Customizable buttons and text
|
||||
* - Consistent theme styling
|
||||
* - Backdrop tap to close
|
||||
* - Icon support
|
||||
* - Responsive design
|
||||
*/
|
||||
export const CustomModal: React.FC<CustomModalProps> = ({
|
||||
visible,
|
||||
title,
|
||||
message,
|
||||
type = 'info',
|
||||
onConfirm,
|
||||
onCancel,
|
||||
confirmText = 'OK',
|
||||
cancelText = 'Cancel',
|
||||
onClose,
|
||||
showCancel = false,
|
||||
icon,
|
||||
}) => {
|
||||
// ============================================================================
|
||||
// MODAL CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get modal configuration based on type
|
||||
*/
|
||||
const getModalConfig = () => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return {
|
||||
icon: icon || 'check-circle',
|
||||
iconColor: theme.colors.success,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderColor: theme.colors.success,
|
||||
};
|
||||
case 'error':
|
||||
return {
|
||||
icon: icon || 'alert-circle',
|
||||
iconColor: theme.colors.error,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderColor: theme.colors.error,
|
||||
};
|
||||
case 'warning':
|
||||
return {
|
||||
icon: icon || 'alert-triangle',
|
||||
iconColor: theme.colors.warning,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderColor: theme.colors.warning,
|
||||
};
|
||||
case 'confirm':
|
||||
return {
|
||||
icon: icon || 'help-circle',
|
||||
iconColor: theme.colors.primary,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderColor: theme.colors.primary,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
icon: icon || 'info',
|
||||
iconColor: theme.colors.info,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderColor: theme.colors.info,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const config = getModalConfig();
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle confirm action
|
||||
*/
|
||||
const handleConfirm = () => {
|
||||
if (onConfirm) {
|
||||
onConfirm();
|
||||
}
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle cancel action
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle backdrop press
|
||||
*/
|
||||
const handleBackdropPress = () => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={handleBackdropPress}>
|
||||
<View style={styles.backdrop}>
|
||||
<TouchableWithoutFeedback>
|
||||
<View style={[
|
||||
styles.modalContainer,
|
||||
{
|
||||
backgroundColor: config.backgroundColor,
|
||||
borderColor: config.borderColor,
|
||||
}
|
||||
]}>
|
||||
{/* Icon */}
|
||||
<View style={[styles.iconContainer, { backgroundColor: config.iconColor + '20' }]}>
|
||||
<Icon name={config.icon} size={32} color={config.iconColor} />
|
||||
</View>
|
||||
|
||||
{/* Title */}
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
|
||||
{/* Message */}
|
||||
<Text style={styles.message}>{message}</Text>
|
||||
|
||||
{/* Buttons */}
|
||||
<View style={styles.buttonContainer}>
|
||||
{showCancel && (
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.cancelButton]}
|
||||
onPress={handleCancel}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>{cancelText}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
styles.confirmButton,
|
||||
{ backgroundColor: config.iconColor },
|
||||
showCancel && styles.confirmButtonWithCancel,
|
||||
]}
|
||||
onPress={handleConfirm}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.confirmButtonText}>{confirmText}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STYLES
|
||||
// ============================================================================
|
||||
|
||||
const { width: screenWidth } = Dimensions.get('window');
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Backdrop
|
||||
backdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Modal container
|
||||
modalContainer: {
|
||||
width: screenWidth - theme.spacing.lg * 2,
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.large,
|
||||
padding: theme.spacing.xl,
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
...theme.shadows.large,
|
||||
},
|
||||
|
||||
// Icon container
|
||||
iconContainer: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing.lg,
|
||||
},
|
||||
|
||||
// Title
|
||||
title: {
|
||||
fontSize: theme.typography.fontSize.displaySmall,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.textPrimary,
|
||||
textAlign: 'center',
|
||||
marginBottom: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Message
|
||||
message: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.regular,
|
||||
color: theme.colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
lineHeight: theme.typography.fontSize.bodyLarge * 1.4,
|
||||
marginBottom: theme.spacing.xl,
|
||||
},
|
||||
|
||||
// Button container
|
||||
buttonContainer: {
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
gap: theme.spacing.md,
|
||||
},
|
||||
|
||||
// Button base
|
||||
button: {
|
||||
flex: 1,
|
||||
paddingVertical: theme.spacing.md,
|
||||
paddingHorizontal: theme.spacing.lg,
|
||||
borderRadius: theme.borderRadius.medium,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
...theme.shadows.primary,
|
||||
},
|
||||
|
||||
// Cancel button
|
||||
cancelButton: {
|
||||
backgroundColor: theme.colors.backgroundAlt,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
},
|
||||
|
||||
// Cancel button text
|
||||
cancelButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.medium,
|
||||
color: theme.colors.textSecondary,
|
||||
},
|
||||
|
||||
// Confirm button
|
||||
confirmButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
},
|
||||
|
||||
// Confirm button with cancel
|
||||
confirmButtonWithCancel: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
// Confirm button text
|
||||
confirmButtonText: {
|
||||
fontSize: theme.typography.fontSize.bodyLarge,
|
||||
fontFamily: theme.typography.fontFamily.bold,
|
||||
color: theme.colors.background,
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* End of File: CustomModal.tsx
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
14
app/shared/components/index.ts
Normal file
14
app/shared/components/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Shared components exports
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export { CustomModal } from './CustomModal';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
115
app/shared/types/alerts.ts
Normal file
115
app/shared/types/alerts.ts
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* File: alerts.ts
|
||||
* Description: Alert and notification type definitions
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export interface Alert {
|
||||
id: string;
|
||||
type: AlertType;
|
||||
priority: AlertPriority;
|
||||
title: string;
|
||||
message: string;
|
||||
patientId?: string;
|
||||
patientName?: string;
|
||||
bedNumber?: string;
|
||||
timestamp: Date;
|
||||
isRead: boolean;
|
||||
isAcknowledged: boolean;
|
||||
actionRequired: boolean;
|
||||
expiresAt?: Date;
|
||||
metadata?: AlertMetadata;
|
||||
}
|
||||
|
||||
export type AlertType =
|
||||
| 'CRITICAL_FINDING'
|
||||
| 'VITAL_SIGNS_ALERT'
|
||||
| 'SCAN_COMPLETED'
|
||||
| 'MEDICATION_ALERT'
|
||||
| 'BED_ASSIGNMENT'
|
||||
| 'SHIFT_CHANGE'
|
||||
| 'SYSTEM_ALERT'
|
||||
| 'EMERGENCY_CODE';
|
||||
|
||||
export interface AlertMetadata {
|
||||
scanId?: string;
|
||||
vitalSigns?: {
|
||||
type: string;
|
||||
value: number;
|
||||
normalRange: string;
|
||||
};
|
||||
medication?: {
|
||||
name: string;
|
||||
dosage: string;
|
||||
reason: string;
|
||||
};
|
||||
location?: {
|
||||
floor: string;
|
||||
room: string;
|
||||
bed: string;
|
||||
};
|
||||
aiFindings?: {
|
||||
confidence: number;
|
||||
summary: string;
|
||||
recommendations: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface CriticalFinding {
|
||||
id: string;
|
||||
patientId: string;
|
||||
patientName: string;
|
||||
bedNumber: string;
|
||||
findingType: CriticalFindingType;
|
||||
severity: 'LIFE_THREATENING' | 'URGENT' | 'SERIOUS';
|
||||
description: string;
|
||||
aiSummary: string;
|
||||
recommendations: string[];
|
||||
timestamp: Date;
|
||||
acknowledgedBy?: string;
|
||||
acknowledgedAt?: Date;
|
||||
actionTaken?: string;
|
||||
followUpRequired: boolean;
|
||||
}
|
||||
|
||||
export type CriticalFindingType =
|
||||
| 'BRAIN_BLEED'
|
||||
| 'PULMONARY_EMBOLISM'
|
||||
| 'AORTIC_DISSECTION'
|
||||
| 'APPENDICITIS'
|
||||
| 'FRACTURE'
|
||||
| 'INTERNAL_BLEEDING'
|
||||
| 'CARDIAC_ARREST'
|
||||
| 'STROKE'
|
||||
| 'SEPSIS'
|
||||
| 'OTHER';
|
||||
|
||||
export interface AlertSettings {
|
||||
pushNotifications: boolean;
|
||||
soundAlerts: boolean;
|
||||
vibrationAlerts: boolean;
|
||||
criticalAlertsOnly: boolean;
|
||||
quietHours: {
|
||||
enabled: boolean;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
alertTypes: {
|
||||
[key in AlertType]: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AlertResponse {
|
||||
alertId: string;
|
||||
response: 'ACKNOWLEDGED' | 'DISMISSED' | 'ESCALATED';
|
||||
notes?: string;
|
||||
timestamp: Date;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: alerts.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
93
app/shared/types/auth.ts
Normal file
93
app/shared/types/auth.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* File: auth.ts
|
||||
* Description: Authentication and user-related type definitions
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export interface NotificationPreferences {
|
||||
critical_alerts: {
|
||||
email: boolean;
|
||||
in_app: boolean;
|
||||
push: boolean;
|
||||
sms: boolean;
|
||||
};
|
||||
system_notifications: {
|
||||
push: boolean;
|
||||
email: boolean;
|
||||
in_app: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DashboardSettings {
|
||||
theme: string;
|
||||
language: string;
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
user_id: string;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
display_name: string;
|
||||
hospital_id: string;
|
||||
dashboard_role: string;
|
||||
profile_photo_url: string | null;
|
||||
theme_color: string | null;
|
||||
accent_color: string | null;
|
||||
notification_preferences: NotificationPreferences;
|
||||
dashboard_settings: DashboardSettings;
|
||||
onboarded: boolean;
|
||||
onboarding_completed: boolean;
|
||||
onboarding_step: number;
|
||||
onboarding_message: string;
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export type UserRole =
|
||||
| 'ER_PHYSICIAN'
|
||||
| 'RESIDENT'
|
||||
| 'MEDICAL_STUDENT'
|
||||
| 'EMERGENCY_ACCESS'
|
||||
| 'TEMPORARY_ACCESS';
|
||||
|
||||
export interface AuthState {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
sessionExpiry: Date | null;
|
||||
deviceId: string | null;
|
||||
}
|
||||
|
||||
export interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
rememberDevice?: boolean;
|
||||
}
|
||||
|
||||
export interface SSOLoginData {
|
||||
token: string;
|
||||
hospitalId: string;
|
||||
redirectUrl?: string;
|
||||
}
|
||||
|
||||
export interface EmergencyAccess {
|
||||
accessCode: string;
|
||||
reason: string;
|
||||
duration: number; // minutes
|
||||
}
|
||||
|
||||
export interface SessionData {
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
expiresAt: Date;
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: auth.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
92
app/shared/types/common.ts
Normal file
92
app/shared/types/common.ts
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* File: common.ts
|
||||
* Description: Common type definitions used across the application
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LoadingState {
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface NavigationParams {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ScreenProps {
|
||||
navigation: any;
|
||||
route: {
|
||||
params: NavigationParams;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FormField {
|
||||
name: string;
|
||||
label: string;
|
||||
type: 'text' | 'email' | 'password' | 'number' | 'select' | 'date' | 'textarea';
|
||||
required: boolean;
|
||||
validation?: (value: any) => string | null;
|
||||
options?: { label: string; value: any }[];
|
||||
placeholder?: string;
|
||||
defaultValue?: any;
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
field: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
theme: 'light' | 'dark' | 'auto';
|
||||
language: string;
|
||||
notifications: boolean;
|
||||
biometricAuth: boolean;
|
||||
autoLogout: boolean;
|
||||
offlineMode: boolean;
|
||||
}
|
||||
|
||||
export interface DeviceInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
platform: 'ios' | 'android';
|
||||
version: string;
|
||||
model: string;
|
||||
isTablet: boolean;
|
||||
}
|
||||
|
||||
export interface NetworkStatus {
|
||||
isConnected: boolean;
|
||||
type: 'wifi' | 'cellular' | 'none';
|
||||
strength?: number;
|
||||
}
|
||||
|
||||
export interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
errorInfo?: any;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: common.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
83
app/shared/types/dashboard.ts
Normal file
83
app/shared/types/dashboard.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* File: dashboard.ts
|
||||
* Description: Dashboard and ER-related type definitions
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { Patient, PatientStatus, AlertPriority } from './patient';
|
||||
|
||||
export interface ERDashboard {
|
||||
totalPatients: number;
|
||||
criticalPatients: number;
|
||||
pendingScans: number;
|
||||
recentReports: number;
|
||||
bedOccupancy: number;
|
||||
departmentStats: DepartmentStats;
|
||||
shiftInfo: ShiftInfo;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
export interface DepartmentStats {
|
||||
emergency: number;
|
||||
trauma: number;
|
||||
cardiac: number;
|
||||
neurology: number;
|
||||
pediatrics: number;
|
||||
icu: number;
|
||||
}
|
||||
|
||||
export interface ShiftInfo {
|
||||
currentShift: 'DAY' | 'NIGHT' | 'EVENING';
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
attendingPhysician: string;
|
||||
residents: string[];
|
||||
nurses: string[];
|
||||
}
|
||||
|
||||
export interface PatientList {
|
||||
patients: Patient[];
|
||||
filters: PatientFilters;
|
||||
sortBy: PatientSortBy;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface PatientFilters {
|
||||
status: PatientStatus[];
|
||||
priority: AlertPriority[];
|
||||
department: string[];
|
||||
bedNumber?: string;
|
||||
attendingPhysician?: string;
|
||||
}
|
||||
|
||||
export type PatientSortBy =
|
||||
| 'PRIORITY'
|
||||
| 'NAME'
|
||||
| 'BED_NUMBER'
|
||||
| 'ADMISSION_DATE'
|
||||
| 'LAST_UPDATED';
|
||||
|
||||
export interface QuickAction {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
action: () => void;
|
||||
isEnabled: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardMetrics {
|
||||
responseTime: number; // average response time in minutes
|
||||
patientSatisfaction: number; // percentage
|
||||
criticalAlertsResolved: number;
|
||||
scansReviewed: number;
|
||||
patientsDischarged: number;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: dashboard.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
19
app/shared/types/index.ts
Normal file
19
app/shared/types/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Shared type definitions exports for the Physician App
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export * from './auth';
|
||||
export * from './patient';
|
||||
export * from './dashboard';
|
||||
export * from './alerts';
|
||||
export * from './settings';
|
||||
export * from './common';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
118
app/shared/types/patient.ts
Normal file
118
app/shared/types/patient.ts
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* File: patient.ts
|
||||
* Description: Patient-related type definitions for medical data
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export interface Patient {
|
||||
id: string;
|
||||
mrn: string; // Medical Record Number
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
dateOfBirth: Date;
|
||||
gender: 'MALE' | 'FEMALE' | 'OTHER';
|
||||
age: number;
|
||||
bedNumber: string;
|
||||
roomNumber: string;
|
||||
admissionDate: Date;
|
||||
status: PatientStatus;
|
||||
priority: AlertPriority;
|
||||
department: string;
|
||||
attendingPhysician: string;
|
||||
allergies: Allergy[];
|
||||
medications: Medication[];
|
||||
vitalSigns: VitalSigns;
|
||||
medicalHistory: MedicalHistory[];
|
||||
currentDiagnosis: string;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
export type PatientStatus =
|
||||
| 'ACTIVE'
|
||||
| 'PENDING'
|
||||
| 'DISCHARGED'
|
||||
| 'TRANSFERRED'
|
||||
| 'CRITICAL';
|
||||
|
||||
export type AlertPriority =
|
||||
| 'CRITICAL'
|
||||
| 'HIGH'
|
||||
| 'MEDIUM'
|
||||
| 'LOW';
|
||||
|
||||
export interface VitalSigns {
|
||||
bloodPressure: {
|
||||
systolic: number;
|
||||
diastolic: number;
|
||||
timestamp: Date;
|
||||
};
|
||||
heartRate: {
|
||||
value: number;
|
||||
timestamp: Date;
|
||||
};
|
||||
temperature: {
|
||||
value: number;
|
||||
timestamp: Date;
|
||||
};
|
||||
respiratoryRate: {
|
||||
value: number;
|
||||
timestamp: Date;
|
||||
};
|
||||
oxygenSaturation: {
|
||||
value: number;
|
||||
timestamp: Date;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Allergy {
|
||||
id: string;
|
||||
name: string;
|
||||
severity: 'MILD' | 'MODERATE' | 'SEVERE';
|
||||
reaction: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface Medication {
|
||||
id: string;
|
||||
name: string;
|
||||
dosage: string;
|
||||
frequency: string;
|
||||
route: string;
|
||||
startDate: Date;
|
||||
endDate?: Date;
|
||||
status: 'ACTIVE' | 'DISCONTINUED' | 'COMPLETED';
|
||||
prescribedBy: string;
|
||||
}
|
||||
|
||||
export interface MedicalHistory {
|
||||
id: string;
|
||||
condition: string;
|
||||
diagnosisDate: Date;
|
||||
status: 'ACTIVE' | 'RESOLVED' | 'CHRONIC';
|
||||
notes?: string;
|
||||
treatingPhysician: string;
|
||||
}
|
||||
|
||||
export interface ScanResult {
|
||||
id: string;
|
||||
patientId: string;
|
||||
scanType: 'CT' | 'MRI' | 'XRAY' | 'ULTRASOUND';
|
||||
bodyPart: string;
|
||||
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REVIEWED';
|
||||
orderedBy: string;
|
||||
orderedDate: Date;
|
||||
completedDate?: Date;
|
||||
reviewedBy?: string;
|
||||
reviewedDate?: Date;
|
||||
findings: string;
|
||||
aiSummary?: string;
|
||||
images: string[];
|
||||
priority: AlertPriority;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of File: patient.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
414
app/shared/types/settings.ts
Normal file
414
app/shared/types/settings.ts
Normal file
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* File: settings.ts
|
||||
* Description: Settings and user profile type definitions
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// USER PROFILE TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* UserProfile Interface
|
||||
*
|
||||
* Purpose: Defines the complete user profile structure
|
||||
*
|
||||
* Properties:
|
||||
* - Personal information (name, contact, demographics)
|
||||
* - Professional information (credentials, specialties)
|
||||
* - Hospital information (department, role, permissions)
|
||||
* - Preferences and settings
|
||||
*/
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
mrn: string; // Medical Record Number
|
||||
employeeId: string; // Hospital Employee ID
|
||||
|
||||
// Personal Information
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
middleName?: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
dateOfBirth: Date;
|
||||
gender: 'MALE' | 'FEMALE' | 'OTHER';
|
||||
address: Address;
|
||||
|
||||
// Professional Information
|
||||
credentials: string[]; // MD, PhD, etc.
|
||||
specialties: string[]; // Emergency Medicine, Trauma, etc.
|
||||
boardCertifications: string[]; // Board certifications
|
||||
licenseNumber: string; // Medical license number
|
||||
licenseExpiryDate: Date;
|
||||
yearsOfExperience: number;
|
||||
|
||||
// Hospital Information
|
||||
department: string; // Emergency Department, etc.
|
||||
role: ProfileUserRole; // Attending, Resident, etc.
|
||||
shiftPreference: 'DAY' | 'NIGHT' | 'FLEXIBLE';
|
||||
supervisor?: string; // Direct supervisor
|
||||
permissions: UserPermissions;
|
||||
|
||||
// Profile Settings
|
||||
profilePicture?: string; // URL to profile picture
|
||||
emergencyContact: EmergencyContact;
|
||||
preferences: UserPreferences;
|
||||
|
||||
// Metadata
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
lastLoginAt: Date;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Interface
|
||||
*
|
||||
* Purpose: Defines address structure for user profile
|
||||
*/
|
||||
export interface Address {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* EmergencyContact Interface
|
||||
*
|
||||
* Purpose: Defines emergency contact information
|
||||
*/
|
||||
export interface EmergencyContact {
|
||||
name: string;
|
||||
relationship: string;
|
||||
phoneNumber: string;
|
||||
email?: string;
|
||||
address?: Address;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileUserRole Type
|
||||
*
|
||||
* Purpose: Defines different user roles in the hospital system for profile settings
|
||||
*/
|
||||
export type ProfileUserRole =
|
||||
| 'ATTENDING_PHYSICIAN'
|
||||
| 'RESIDENT_PHYSICIAN'
|
||||
| 'FELLOW'
|
||||
| 'MEDICAL_STUDENT'
|
||||
| 'NURSE_PRACTITIONER'
|
||||
| 'PHYSICIAN_ASSISTANT'
|
||||
| 'ADMINISTRATOR'
|
||||
| 'RESEARCHER';
|
||||
|
||||
/**
|
||||
* UserPermissions Interface
|
||||
*
|
||||
* Purpose: Defines user permissions and access levels
|
||||
*/
|
||||
export interface UserPermissions {
|
||||
canViewPatients: boolean;
|
||||
canEditPatients: boolean;
|
||||
canViewReports: boolean;
|
||||
canEditReports: boolean;
|
||||
canViewAlerts: boolean;
|
||||
canAcknowledgeAlerts: boolean;
|
||||
canManageUsers: boolean;
|
||||
canViewAnalytics: boolean;
|
||||
canExportData: boolean;
|
||||
canAccessAdminPanel: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// USER PREFERENCES TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* UserPreferences Interface
|
||||
*
|
||||
* Purpose: Defines user preferences and settings
|
||||
*/
|
||||
export interface UserPreferences {
|
||||
// Display Preferences
|
||||
theme: 'LIGHT' | 'DARK' | 'AUTO';
|
||||
fontSize: 'SMALL' | 'MEDIUM' | 'LARGE';
|
||||
language: string; // ISO language code
|
||||
timezone: string; // IANA timezone
|
||||
|
||||
// Notification Preferences
|
||||
notifications: NotificationPreferences;
|
||||
|
||||
// Clinical Preferences
|
||||
clinical: ClinicalPreferences;
|
||||
|
||||
// Privacy Preferences
|
||||
privacy: PrivacyPreferences;
|
||||
|
||||
// Accessibility Preferences
|
||||
accessibility: AccessibilityPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationPreferences Interface
|
||||
*
|
||||
* Purpose: Defines notification settings
|
||||
*/
|
||||
export interface NotificationPreferences {
|
||||
pushNotifications: boolean;
|
||||
emailNotifications: boolean;
|
||||
smsNotifications: boolean;
|
||||
|
||||
// Alert Types
|
||||
criticalAlerts: boolean;
|
||||
patientUpdates: boolean;
|
||||
scanResults: boolean;
|
||||
medicationReminders: boolean;
|
||||
shiftChanges: boolean;
|
||||
systemMaintenance: boolean;
|
||||
|
||||
// Timing
|
||||
quietHours: {
|
||||
enabled: boolean;
|
||||
startTime: string; // HH:MM format
|
||||
endTime: string; // HH:MM format
|
||||
};
|
||||
|
||||
// Sound and Vibration
|
||||
soundEnabled: boolean;
|
||||
vibrationEnabled: boolean;
|
||||
customSound?: string; // Custom notification sound
|
||||
}
|
||||
|
||||
/**
|
||||
* ClinicalPreferences Interface
|
||||
*
|
||||
* Purpose: Defines clinical workflow preferences
|
||||
*/
|
||||
export interface ClinicalPreferences {
|
||||
// Default Views
|
||||
defaultDashboardView: 'LIST' | 'GRID' | 'TIMELINE';
|
||||
defaultPatientSort: 'PRIORITY' | 'NAME' | 'ADMISSION_TIME' | 'BED_NUMBER';
|
||||
showPatientPhotos: boolean;
|
||||
|
||||
// Clinical Alerts
|
||||
autoAcknowledgeNonCritical: boolean;
|
||||
alertTimeoutMinutes: number; // Auto-dismiss after X minutes
|
||||
|
||||
// Documentation
|
||||
autoSaveInterval: number; // Seconds
|
||||
defaultReportTemplate: string;
|
||||
enableVoiceNotes: boolean;
|
||||
|
||||
// Patient Care
|
||||
showAllergiesProminently: boolean;
|
||||
showMedicationInteractions: boolean;
|
||||
enableClinicalDecisionSupport: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* PrivacyPreferences Interface
|
||||
*
|
||||
* Purpose: Defines privacy and security settings
|
||||
*/
|
||||
export interface PrivacyPreferences {
|
||||
// Session Management
|
||||
sessionTimeoutMinutes: number;
|
||||
requireBiometricAuth: boolean;
|
||||
autoLockOnBackground: boolean;
|
||||
|
||||
// Data Sharing
|
||||
allowAnalytics: boolean;
|
||||
allowCrashReporting: boolean;
|
||||
allowUsageStatistics: boolean;
|
||||
|
||||
// Privacy Controls
|
||||
showPatientPhotos: boolean;
|
||||
enableScreenCapture: boolean;
|
||||
enableScreenshot: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* AccessibilityPreferences Interface
|
||||
*
|
||||
* Purpose: Defines accessibility settings
|
||||
*/
|
||||
export interface AccessibilityPreferences {
|
||||
// Visual
|
||||
highContrastMode: boolean;
|
||||
reduceMotion: boolean;
|
||||
boldText: boolean;
|
||||
|
||||
// Audio
|
||||
screenReaderEnabled: boolean;
|
||||
audioDescriptions: boolean;
|
||||
|
||||
// Interaction
|
||||
largerTouchTargets: boolean;
|
||||
reduceTransparency: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SETTINGS SCREEN TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* SettingsSection Type
|
||||
*
|
||||
* Purpose: Defines different sections in the settings screen
|
||||
*/
|
||||
export type SettingsSection =
|
||||
| 'PROFILE'
|
||||
| 'NOTIFICATIONS'
|
||||
| 'CLINICAL'
|
||||
| 'PRIVACY'
|
||||
| 'ACCESSIBILITY'
|
||||
| 'ABOUT'
|
||||
| 'HELP'
|
||||
| 'LOGOUT';
|
||||
|
||||
/**
|
||||
* SettingsItem Interface
|
||||
*
|
||||
* Purpose: Defines a single settings item
|
||||
*/
|
||||
export interface SettingsItem {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon: string;
|
||||
type: 'NAVIGATION' | 'TOGGLE' | 'SELECTION' | 'ACTION';
|
||||
value?: any;
|
||||
onPress?: () => void;
|
||||
disabled?: boolean;
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsSection Interface
|
||||
*
|
||||
* Purpose: Defines a settings section with items
|
||||
*/
|
||||
export interface SettingsSectionData {
|
||||
id: SettingsSection;
|
||||
title: string;
|
||||
items: SettingsItem[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PROFILE UPDATE TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* ProfileUpdateData Interface
|
||||
*
|
||||
* Purpose: Defines data structure for profile updates
|
||||
*/
|
||||
export interface ProfileUpdateData {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
middleName?: string;
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
address?: Partial<Address>;
|
||||
emergencyContact?: Partial<EmergencyContact>;
|
||||
specialties?: string[];
|
||||
boardCertifications?: string[];
|
||||
shiftPreference?: 'DAY' | 'NIGHT' | 'FLEXIBLE';
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileUpdateRequest Interface
|
||||
*
|
||||
* Purpose: Defines API request for profile updates
|
||||
*/
|
||||
export interface ProfileUpdateRequest {
|
||||
userId: string;
|
||||
updates: ProfileUpdateData;
|
||||
reason?: string; // Reason for update
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileUpdateResponse Interface
|
||||
*
|
||||
* Purpose: Defines API response for profile updates
|
||||
*/
|
||||
export interface ProfileUpdateResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
updatedProfile?: UserProfile;
|
||||
errors?: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PASSWORD AND SECURITY TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* PasswordChangeRequest Interface
|
||||
*
|
||||
* Purpose: Defines password change request
|
||||
*/
|
||||
export interface PasswordChangeRequest {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SecuritySettings Interface
|
||||
*
|
||||
* Purpose: Defines security-related settings
|
||||
*/
|
||||
export interface SecuritySettings {
|
||||
twoFactorEnabled: boolean;
|
||||
biometricEnabled: boolean;
|
||||
passwordExpiryDays: number;
|
||||
failedLoginAttempts: number;
|
||||
lastPasswordChange: Date;
|
||||
securityQuestions: SecurityQuestion[];
|
||||
}
|
||||
|
||||
/**
|
||||
* SecurityQuestion Interface
|
||||
*
|
||||
* Purpose: Defines security question structure
|
||||
*/
|
||||
export interface SecurityQuestion {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: string; // Hashed answer
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXPORT TYPES
|
||||
// ============================================================================
|
||||
|
||||
export type {
|
||||
UserProfile,
|
||||
Address,
|
||||
EmergencyContact,
|
||||
ProfileUserRole,
|
||||
UserPermissions,
|
||||
UserPreferences,
|
||||
NotificationPreferences,
|
||||
ClinicalPreferences,
|
||||
PrivacyPreferences,
|
||||
AccessibilityPreferences,
|
||||
SettingsSection,
|
||||
SettingsItem,
|
||||
SettingsSectionData,
|
||||
ProfileUpdateData,
|
||||
ProfileUpdateRequest,
|
||||
ProfileUpdateResponse,
|
||||
PasswordChangeRequest,
|
||||
SecuritySettings,
|
||||
SecurityQuestion,
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: settings.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
13
app/shared/utils/api.ts
Normal file
13
app/shared/utils/api.ts
Normal file
@ -0,0 +1,13 @@
|
||||
interface BuildHeadersParams {
|
||||
token?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
export const buildHeaders = ({ token, contentType }: BuildHeadersParams = {}) => {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
if (contentType) headers['Content-Type'] = contentType;
|
||||
|
||||
return { headers };
|
||||
};
|
||||
95
app/shared/utils/constants.ts
Normal file
95
app/shared/utils/constants.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* File: constants.ts
|
||||
* Description: Application constants and configuration values
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
import Config from 'react-native-config';
|
||||
|
||||
// API Configuration
|
||||
export const API_CONFIG = {
|
||||
BASE_URL:Config.BASE_URL,
|
||||
TIMEOUT: 30000,
|
||||
RETRY_ATTEMPTS: 3,
|
||||
RETRY_DELAY: 1000,
|
||||
} as const;
|
||||
|
||||
// WebSocket Configuration
|
||||
export const WEBSOCKET_CONFIG = {
|
||||
URL: 'wss://ws.neoscan-physician.com',
|
||||
RECONNECT_INTERVAL: 5000,
|
||||
MAX_RECONNECT_ATTEMPTS: 10,
|
||||
HEARTBEAT_INTERVAL: 30000,
|
||||
} as const;
|
||||
|
||||
// Session Configuration
|
||||
export const SESSION_CONFIG = {
|
||||
TIMEOUT: 8 * 60 * 60 * 1000, // 8 hours
|
||||
INACTIVITY_TIMEOUT: 30 * 60 * 1000, // 30 minutes
|
||||
DEVICE_REMEMBER_DURATION: 30 * 24 * 60 * 60 * 1000, // 30 days
|
||||
} as const;
|
||||
|
||||
// Alert Configuration
|
||||
export const ALERT_CONFIG = {
|
||||
CRITICAL_TIMEOUT: 2 * 60 * 1000, // 2 minutes
|
||||
WARNING_TIMEOUT: 10 * 60 * 1000, // 10 minutes
|
||||
INFO_TIMEOUT: 30 * 60 * 1000, // 30 minutes
|
||||
MAX_ALERTS: 100,
|
||||
} as const;
|
||||
|
||||
// Cache Configuration
|
||||
export const CACHE_CONFIG = {
|
||||
PATIENT_DATA: 15 * 60 * 1000, // 15 minutes
|
||||
MEDICAL_RECORDS: 5 * 60 * 1000, // 5 minutes
|
||||
USER_SETTINGS: 24 * 60 * 60 * 1000, // 24 hours
|
||||
} as const;
|
||||
|
||||
// UI Configuration
|
||||
export const UI_CONFIG = {
|
||||
ANIMATION_DURATION: 300,
|
||||
DEBOUNCE_DELAY: 300,
|
||||
THROTTLE_DELAY: 100,
|
||||
TOUCH_TARGET_SIZE: 44,
|
||||
} as const;
|
||||
|
||||
// Medical Constants
|
||||
export const MEDICAL_CONSTANTS = {
|
||||
NORMAL_VITAL_SIGNS: {
|
||||
HEART_RATE: { min: 60, max: 100 },
|
||||
BLOOD_PRESSURE: { systolic: { min: 90, max: 140 }, diastolic: { min: 60, max: 90 } },
|
||||
TEMPERATURE: { min: 36.1, max: 37.2 },
|
||||
RESPIRATORY_RATE: { min: 12, max: 20 },
|
||||
OXYGEN_SATURATION: { min: 95, max: 100 },
|
||||
},
|
||||
CRITICAL_VALUES: {
|
||||
HEART_RATE: { min: 40, max: 140 },
|
||||
BLOOD_PRESSURE: { systolic: { min: 70, max: 200 }, diastolic: { min: 40, max: 120 } },
|
||||
TEMPERATURE: { min: 35.0, max: 40.0 },
|
||||
RESPIRATORY_RATE: { min: 8, max: 30 },
|
||||
OXYGEN_SATURATION: { min: 90, max: 100 },
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Error Messages
|
||||
export const ERROR_MESSAGES = {
|
||||
NETWORK_ERROR: 'Network connection error. Please check your internet connection.',
|
||||
AUTHENTICATION_ERROR: 'Authentication failed. Please log in again.',
|
||||
SERVER_ERROR: 'Server error. Please try again later.',
|
||||
VALIDATION_ERROR: 'Please check your input and try again.',
|
||||
UNKNOWN_ERROR: 'An unexpected error occurred. Please try again.',
|
||||
} as const;
|
||||
|
||||
// Success Messages
|
||||
export const SUCCESS_MESSAGES = {
|
||||
LOGIN_SUCCESS: 'Successfully logged in.',
|
||||
LOGOUT_SUCCESS: 'Successfully logged out.',
|
||||
DATA_SAVED: 'Data saved successfully.',
|
||||
ALERT_ACKNOWLEDGED: 'Alert acknowledged.',
|
||||
SETTINGS_UPDATED: 'Settings updated successfully.',
|
||||
} as const;
|
||||
|
||||
/*
|
||||
* End of File: constants.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
524
app/shared/utils/helpers.ts
Normal file
524
app/shared/utils/helpers.ts
Normal file
@ -0,0 +1,524 @@
|
||||
/*
|
||||
* File: helpers.ts
|
||||
* Description: General helper functions for common operations
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import { AlertPriority, PatientStatus } from '../types';
|
||||
|
||||
// ============================================================================
|
||||
// ARRAY HELPERS - Functions for array manipulation and processing
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* chunk Function
|
||||
*
|
||||
* Purpose: Split an array into smaller chunks of specified size
|
||||
*
|
||||
* @param array - The array to be chunked
|
||||
* @param size - The size of each chunk
|
||||
* @returns Array of arrays, each containing up to 'size' elements
|
||||
*
|
||||
* Example:
|
||||
* chunk([1, 2, 3, 4, 5, 6], 2) → [[1, 2], [3, 4], [5, 6]]
|
||||
*
|
||||
* Use case: Pagination, displaying data in groups
|
||||
*/
|
||||
export const chunk = <T>(array: T[], size: number): T[][] => {
|
||||
const chunks: T[][] = [];
|
||||
for (let i = 0; i < array.length; i += size) {
|
||||
chunks.push(array.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
|
||||
/**
|
||||
* groupBy Function
|
||||
*
|
||||
* Purpose: Group array elements by a specified key function
|
||||
*
|
||||
* @param array - The array to be grouped
|
||||
* @param key - Function that returns the grouping key for each element
|
||||
* @returns Object with keys as group names and values as arrays of grouped items
|
||||
*
|
||||
* Example:
|
||||
* groupBy(patients, patient => patient.department)
|
||||
* → { 'Emergency': [patient1, patient2], 'ICU': [patient3] }
|
||||
*
|
||||
* Use case: Organizing patients by department, status, or priority
|
||||
*/
|
||||
export const groupBy = <T, K extends keyof any>(
|
||||
array: T[],
|
||||
key: (item: T) => K
|
||||
): Record<K, T[]> => {
|
||||
return array.reduce((groups, item) => {
|
||||
const group = key(item);
|
||||
if (!groups[group]) {
|
||||
groups[group] = [];
|
||||
}
|
||||
groups[group].push(item);
|
||||
return groups;
|
||||
}, {} as Record<K, T[]>);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// OBJECT HELPERS - Functions for object manipulation and transformation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* pick Function
|
||||
*
|
||||
* Purpose: Create a new object with only specified properties from the original
|
||||
*
|
||||
* @param obj - The source object
|
||||
* @param keys - Array of property names to include
|
||||
* @returns New object containing only the specified properties
|
||||
*
|
||||
* Example:
|
||||
* pick({ name: 'John', age: 30, id: 1 }, ['name', 'age'])
|
||||
* → { name: 'John', age: 30 }
|
||||
*
|
||||
* Use case: Extracting specific patient data fields
|
||||
*/
|
||||
export const pick = <T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> => {
|
||||
const result = {} as Pick<T, K>;
|
||||
keys.forEach(key => {
|
||||
if (key in obj) {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* omit Function
|
||||
*
|
||||
* Purpose: Create a new object excluding specified properties from the original
|
||||
*
|
||||
* @param obj - The source object
|
||||
* @param keys - Array of property names to exclude
|
||||
* @returns New object without the specified properties
|
||||
*
|
||||
* Example:
|
||||
* omit({ name: 'John', age: 30, id: 1 }, ['id'])
|
||||
* → { name: 'John', age: 30 }
|
||||
*
|
||||
* Use case: Removing sensitive data before sending to API
|
||||
*/
|
||||
export const omit = <T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> => {
|
||||
const result = { ...obj };
|
||||
keys.forEach(key => {
|
||||
delete result[key];
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// STRING HELPERS - Functions for string manipulation and formatting
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* capitalize Function
|
||||
*
|
||||
* Purpose: Capitalize the first letter of a string and lowercase the rest
|
||||
*
|
||||
* @param str - The string to capitalize
|
||||
* @returns Capitalized string
|
||||
*
|
||||
* Example:
|
||||
* capitalize('john doe') → 'John doe'
|
||||
*
|
||||
* Use case: Formatting patient names, department names
|
||||
*/
|
||||
export const capitalize = (str: string): string => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* truncate Function
|
||||
*
|
||||
* Purpose: Truncate a string to specified length and add ellipsis
|
||||
*
|
||||
* @param str - The string to truncate
|
||||
* @param length - Maximum length before truncation
|
||||
* @returns Truncated string with ellipsis if needed
|
||||
*
|
||||
* Example:
|
||||
* truncate('Very long diagnosis text', 15) → 'Very long diagn...'
|
||||
*
|
||||
* Use case: Displaying long text in limited space (diagnoses, notes)
|
||||
*/
|
||||
export const truncate = (str: string, length: number): string => {
|
||||
if (str.length <= length) return str;
|
||||
return str.slice(0, length) + '...';
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// NUMBER HELPERS - Functions for number manipulation and validation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* clamp Function
|
||||
*
|
||||
* Purpose: Constrain a number between minimum and maximum values
|
||||
*
|
||||
* @param value - The number to clamp
|
||||
* @param min - Minimum allowed value
|
||||
* @param max - Maximum allowed value
|
||||
* @returns Clamped value within the specified range
|
||||
*
|
||||
* Example:
|
||||
* clamp(150, 0, 100) → 100
|
||||
* clamp(-10, 0, 100) → 0
|
||||
*
|
||||
* Use case: Validating vital signs, age ranges, scores
|
||||
*/
|
||||
export const clamp = (value: number, min: number, max: number): number => {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
};
|
||||
|
||||
/**
|
||||
* roundToDecimal Function
|
||||
*
|
||||
* Purpose: Round a number to specified decimal places
|
||||
*
|
||||
* @param value - The number to round
|
||||
* @param decimals - Number of decimal places
|
||||
* @returns Rounded number
|
||||
*
|
||||
* Example:
|
||||
* roundToDecimal(3.14159, 2) → 3.14
|
||||
*
|
||||
* Use case: Formatting vital signs, medication dosages, scores
|
||||
*/
|
||||
export const roundToDecimal = (value: number, decimals: number): number => {
|
||||
return Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// BOOLEAN HELPERS - Functions for boolean operations and validation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* isTruthy Function
|
||||
*
|
||||
* Purpose: Check if a value is truthy (converts to true)
|
||||
*
|
||||
* @param value - The value to check
|
||||
* @returns Boolean indicating if value is truthy
|
||||
*
|
||||
* Example:
|
||||
* isTruthy('hello') → true
|
||||
* isTruthy(0) → false
|
||||
*
|
||||
* Use case: Validating form inputs, checking optional fields
|
||||
*/
|
||||
export const isTruthy = (value: any): boolean => {
|
||||
return Boolean(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* isFalsy Function
|
||||
*
|
||||
* Purpose: Check if a value is falsy (converts to false)
|
||||
*
|
||||
* @param value - The value to check
|
||||
* @returns Boolean indicating if value is falsy
|
||||
*
|
||||
* Example:
|
||||
* isFalsy('') → true
|
||||
* isFalsy(null) → true
|
||||
*
|
||||
* Use case: Checking for empty or null values
|
||||
*/
|
||||
export const isFalsy = (value: any): boolean => {
|
||||
return !Boolean(value);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// FUNCTION HELPERS - Functions for function manipulation and optimization
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* debounce Function
|
||||
*
|
||||
* Purpose: Create a debounced version of a function that delays execution
|
||||
*
|
||||
* @param func - The function to debounce
|
||||
* @param delay - Delay in milliseconds
|
||||
* @returns Debounced function
|
||||
*
|
||||
* Example:
|
||||
* const debouncedSearch = debounce(searchPatients, 300);
|
||||
* // Only executes search after 300ms of no input
|
||||
*
|
||||
* Use case: Search inputs, API calls, form validation
|
||||
*/
|
||||
export const debounce = <T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
return (...args: Parameters<T>) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func(...args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* throttle Function
|
||||
*
|
||||
* Purpose: Create a throttled version of a function that limits execution frequency
|
||||
*
|
||||
* @param func - The function to throttle
|
||||
* @param delay - Minimum time between executions in milliseconds
|
||||
* @returns Throttled function
|
||||
*
|
||||
* Example:
|
||||
* const throttledScroll = throttle(handleScroll, 100);
|
||||
* // Only executes once every 100ms maximum
|
||||
*
|
||||
* Use case: Scroll events, resize events, real-time updates
|
||||
*/
|
||||
export const throttle = <T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let lastCall = 0;
|
||||
return (...args: Parameters<T>) => {
|
||||
const now = Date.now();
|
||||
if (now - lastCall >= delay) {
|
||||
lastCall = now;
|
||||
func(...args);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MEDICAL HELPERS - Healthcare-specific utility functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* getPriorityColor Function
|
||||
*
|
||||
* Purpose: Get the appropriate color for alert priority levels
|
||||
*
|
||||
* @param priority - The alert priority level
|
||||
* @returns Hex color code for the priority
|
||||
*
|
||||
* Color Mapping:
|
||||
* - CRITICAL: Red (#F44336) - Immediate attention required
|
||||
* - HIGH: Orange (#FF9800) - High priority attention
|
||||
* - MEDIUM: Blue (#2196F3) - Normal priority
|
||||
* - LOW: Green (#4CAF50) - Low priority
|
||||
* - Default: Gray (#9E9E9E) - Unknown priority
|
||||
*
|
||||
* Use case: Color-coding alerts, patient cards, status indicators
|
||||
*/
|
||||
export const getPriorityColor = (priority: AlertPriority): string => {
|
||||
switch (priority) {
|
||||
case 'CRITICAL':
|
||||
return '#F44336'; // Red for critical alerts
|
||||
case 'HIGH':
|
||||
return '#FF9800'; // Orange for high priority
|
||||
case 'MEDIUM':
|
||||
return '#2196F3'; // Blue for medium priority
|
||||
case 'LOW':
|
||||
return '#4CAF50'; // Green for low priority
|
||||
default:
|
||||
return '#9E9E9E'; // Gray for unknown priority
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getStatusColor Function
|
||||
*
|
||||
* Purpose: Get the appropriate color for patient status levels
|
||||
*
|
||||
* @param status - The patient status
|
||||
* @returns Hex color code for the status
|
||||
*
|
||||
* Color Mapping:
|
||||
* - CRITICAL: Red (#F44336) - Critical patient condition
|
||||
* - ACTIVE: Blue (#2196F3) - Currently under care
|
||||
* - PENDING: Orange (#FF9800) - Waiting for treatment
|
||||
* - DISCHARGED: Green (#4CAF50) - Successfully discharged
|
||||
* - TRANSFERRED: Gray (#9E9E9E) - Transferred to another facility
|
||||
*
|
||||
* Use case: Color-coding patient cards, status badges, dashboard indicators
|
||||
*/
|
||||
export const getStatusColor = (status: PatientStatus): string => {
|
||||
switch (status) {
|
||||
case 'CRITICAL':
|
||||
return '#F44336'; // Red for critical patients
|
||||
case 'ACTIVE':
|
||||
return '#2196F3'; // Blue for active patients
|
||||
case 'PENDING':
|
||||
return '#FF9800'; // Orange for pending patients
|
||||
case 'DISCHARGED':
|
||||
return '#4CAF50'; // Green for discharged patients
|
||||
case 'TRANSFERRED':
|
||||
return '#9E9E9E'; // Gray for transferred patients
|
||||
default:
|
||||
return '#9E9E9E'; // Gray for unknown status
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* calculateAge Function
|
||||
*
|
||||
* Purpose: Calculate age from date of birth
|
||||
*
|
||||
* @param dateOfBirth - The patient's date of birth
|
||||
* @returns Age in years
|
||||
*
|
||||
* Logic:
|
||||
* - Calculates year difference
|
||||
* - Adjusts for month and day to get accurate age
|
||||
* - Handles leap years and month length variations
|
||||
*
|
||||
* Example:
|
||||
* calculateAge(new Date('1990-05-15')) → 33 (if current year is 2023)
|
||||
*
|
||||
* Use case: Displaying patient age, age-based calculations, demographics
|
||||
*/
|
||||
export const calculateAge = (dateOfBirth: Date): number => {
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - dateOfBirth.getFullYear();
|
||||
const monthDiff = today.getMonth() - dateOfBirth.getMonth();
|
||||
|
||||
// Adjust age if birthday hasn't occurred this year
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dateOfBirth.getDate())) {
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// VALIDATION HELPERS - Functions for data validation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* isValidEmail Function
|
||||
*
|
||||
* Purpose: Validate email address format
|
||||
*
|
||||
* @param email - The email address to validate
|
||||
* @returns Boolean indicating if email is valid
|
||||
*
|
||||
* Validation Rules:
|
||||
* - Must contain @ symbol
|
||||
* - Must have text before and after @
|
||||
* - Must have domain extension
|
||||
*
|
||||
* Use case: Login forms, user registration, contact information
|
||||
*/
|
||||
export const isValidEmail = (email: string): boolean => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
/**
|
||||
* isValidPhone Function
|
||||
*
|
||||
* Purpose: Validate phone number format
|
||||
*
|
||||
* @param phone - The phone number to validate
|
||||
* @returns Boolean indicating if phone number is valid
|
||||
*
|
||||
* Validation Rules:
|
||||
* - Minimum 10 digits
|
||||
* - Allows +, spaces, hyphens, parentheses
|
||||
* - International format supported
|
||||
*
|
||||
* Use case: Contact information, emergency contacts
|
||||
*/
|
||||
export const isValidPhone = (phone: string): boolean => {
|
||||
const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
|
||||
return phoneRegex.test(phone);
|
||||
};
|
||||
|
||||
/**
|
||||
* isValidMRN Function
|
||||
*
|
||||
* Purpose: Validate Medical Record Number format
|
||||
*
|
||||
* @param mrn - The MRN to validate
|
||||
* @returns Boolean indicating if MRN is valid
|
||||
*
|
||||
* Validation Rules:
|
||||
* - 6-12 characters long
|
||||
* - Alphanumeric only (A-Z, 0-9)
|
||||
* - No special characters or spaces
|
||||
*
|
||||
* Use case: Patient identification, medical record lookup
|
||||
*/
|
||||
export const isValidMRN = (mrn: string): boolean => {
|
||||
const mrnRegex = /^[A-Z0-9]{6,12}$/;
|
||||
return mrnRegex.test(mrn);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ASYNC HELPERS - Functions for asynchronous operations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* sleep Function
|
||||
*
|
||||
* Purpose: Create a delay for specified milliseconds
|
||||
*
|
||||
* @param ms - Milliseconds to delay
|
||||
* @returns Promise that resolves after the delay
|
||||
*
|
||||
* Example:
|
||||
* await sleep(1000); // Wait for 1 second
|
||||
*
|
||||
* Use case: Rate limiting, loading states, animation delays
|
||||
*/
|
||||
export const sleep = (ms: number): Promise<void> => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
/**
|
||||
* retry Function
|
||||
*
|
||||
* Purpose: Retry an async function with exponential backoff
|
||||
*
|
||||
* @param fn - The async function to retry
|
||||
* @param retries - Number of retry attempts (default: 3)
|
||||
* @param delay - Initial delay in milliseconds (default: 1000)
|
||||
* @returns Promise that resolves with function result or rejects after all retries
|
||||
*
|
||||
* Retry Strategy:
|
||||
* - Exponential backoff (delay doubles after each failure)
|
||||
* - Stops after specified number of retries
|
||||
* - Throws error if all retries fail
|
||||
*
|
||||
* Example:
|
||||
* const result = await retry(fetchPatientData, 3, 1000);
|
||||
*
|
||||
* Use case: API calls, network requests, database operations
|
||||
*/
|
||||
export const retry = async <T>(
|
||||
fn: () => Promise<T>,
|
||||
retries: number = 3,
|
||||
delay: number = 1000
|
||||
): Promise<T> => {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
if (retries > 0) {
|
||||
await sleep(delay);
|
||||
return retry(fn, retries - 1, delay * 2); // Exponential backoff
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* End of File: helpers.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
17
app/shared/utils/index.ts
Normal file
17
app/shared/utils/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* File: index.ts
|
||||
* Description: Shared utility functions exports for the Physician App
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
export * from './api';
|
||||
export * from './constants';
|
||||
export * from './helpers';
|
||||
export * from './validators';
|
||||
|
||||
/*
|
||||
* End of File: index.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
48
app/shared/utils/toast.ts
Normal file
48
app/shared/utils/toast.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* File: Toast.js
|
||||
* Description: Utility for displaying toast notifications
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import Toast from 'react-native-toast-message';
|
||||
//shows success toast
|
||||
const showSuccess=(text1='Successfull',text2='')=>{
|
||||
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1,
|
||||
text2
|
||||
});
|
||||
}
|
||||
//shows error text
|
||||
const showError=(text1='something went wrong',text2='')=>{
|
||||
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1,
|
||||
text2
|
||||
});
|
||||
}
|
||||
//shows warning text
|
||||
const showWarning=(text1='you have some warning',text2='')=>{
|
||||
|
||||
Toast.show({
|
||||
type: 'info',
|
||||
text1,
|
||||
text2
|
||||
});
|
||||
}
|
||||
|
||||
const handleError=()=>{
|
||||
|
||||
|
||||
}
|
||||
|
||||
export {showSuccess,showError,showWarning,handleError};
|
||||
|
||||
/*
|
||||
* End of File: Toast.js
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user