code generated for dashboard case view ,profile and auth section

This commit is contained in:
yashwin-foxy 2025-07-18 18:40:19 +05:30
parent c7288f54a8
commit 20ab5d9d21
142 changed files with 7392 additions and 754 deletions

View File

@ -0,0 +1,3 @@
---
alwaysApply: true
---

730
README.md
View File

@ -1,698 +1,52 @@
1. RADIOLOGIST APP - DETAILED WORKFLOW # NeoScan Radiologist App
1.1 App Launch & Authentication
App Launch → Biometric Auth → Dashboard Loading
Load Case Queue → Priority Sort → Display Cases
Critical (RED) → Urgent (ORANGE) → Routine (GREEN)
1.2 Critical Finding Workflow (Acute Subdural Hemorrhage)
STEP 1: Alert Reception
Push Notification → "CRITICAL - Acute Subdural Hemorrhage"
Tap Notification → App Opens → Case Preview
Patient: John Doe, Age 45, Head Trauma
AI Confidence: 93%
Midline Shift: 7mm
STEP 2: Full Case Review A next-generation radiology workflow app designed for rapid, AI-assisted case review and reporting. Built with a modular architecture and a Clinical Blue Interface theme.
Case Preview → Open DICOM Viewer
AI Overlay ON → Hemorrhage Highlighted
Review Images → Confirm Finding → Add Observations
"Large acute subdural hemorrhage with mass effect"
STEP 3: Report Generation ## Features
Voice-to-Text → "Acute subdural hemorrhage..." - Modular structure: Auth, Dashboard, Case Review, Reporting, Notifications, Analytics, Profile
- Clinical Blue Interface theme
Review Text → Edit if needed → Submit Report - Biometric and PIN authentication
- Real-time case queue with priority sorting
Preliminary Report Sent (< 5 minutes) - DICOM viewer with AI overlays
- Voice-to-text reporting
- Push notifications for critical findings
- Redux state management
STEP 4: System Learning ## Project Structure
System Logs → Radiologist Confirmation ```
app/
Time Metrics Recorded → Model Improvement Data modules/ # Feature modules (Auth, Dashboard, CaseReview, ...)
1.3 Routine Scan Workflow (Negative Finding) navigation/ # Navigation setup
STEP 1: Queue Processing redux/ # Redux store setup
Dashboard → Routine Queue → Select Case constants/ # Global constants
shared/
Patient: Jane Smith, Age 30, Headache src/
AI: No acute abnormalities (99% confidence) components/ # Shared UI components
theme/ # Design system (colors, typography, ...)
STEP 2: Confirmation Review utils/ # Utility functions
DICOM Viewer → Quick Review → Confirm Negative hooks/ # Shared hooks
types/ # TypeScript types
"No acute intracranial abnormality" assets/ # App-wide assets
STEP 3: Report & Documentation
Submit Report → System Logs Concordance
Next Case in Queue
1.4 System Uncertainty Protocol
AI Confidence 70-85% → "REVIEW RECOMMENDED"
Special Yellow Flag → Enhanced Attention Required
Detailed Review → Feedback to System
3. RADIOLOGIST APP WIREFRAMES
3.1 Login Screen
+------------------------+
| [HOSPITAL LOGO] |
| |
| Radiologist Portal |
| |
| [👤 Dr. Smith] |
| [🔒 Biometric Login] |
| |
| [Face ID Icon] |
| Touch to Login |
| |
| OR |
| |
| PIN: [****] |
| [LOGIN] |
| |
| Emergency Access |
+------------------------+
3.2 Dashboard - Case Queue
+------------------------+
| Dr. Smith [🔔3][⚙️] |
| On-Call Status: ACTIVE |
| |
| CRITICAL CASES |
| 🔴 Bed 3 - Hemorrhage |
| 📱 2 min ago |
| AI: 93% confidence |
| [REVIEW NOW] |
| |
| 🔴 Bed 7 - Stroke |
| 📱 5 min ago |
| AI: 87% confidence |
| [REVIEW NOW] |
| |
| URGENT CASES (4) |
| 🟡 Show All |
| |
| ROUTINE CASES (12) |
| 🟢 Show All |
+------------------------+
3.3 Critical Case Details
+------------------------+
| ← CRITICAL CASE |
| |
| Patient: John Doe, 45M |
| Bed: 3 | Time: 14:35 |
| Clinical: Head trauma |
| |
| AI ANALYSIS: |
| 🔴 Acute Subdural |
| Confidence: 93% |
| Midline Shift: 7mm |
| Mass Effect: Present |
| |
| [VIEW DICOM IMAGES] |
| [VOICE REPORT] |
| [QUICK REPORT] |
| |
| Status: PENDING REVIEW |
+------------------------+
3.4 DICOM Viewer
+------------------------+
| ← John Doe - CT Brain |
| |
| [ CT SCAN IMAGE ]|
| [ WITH AI OVERLAY ]|
| [ HEMORRHAGE ]|
| [ HIGHLIGHTED ]|
| |
| Tools: [🔍][📏][✏️] |
| AI: [ON] Conf: 93% |
| |
| Measurements: |
| • Hemorrhage: 3.2cm |
| • Midline: 7mm shift |
| |
| [🎤 START REPORT] |
+------------------------+
3.5 Voice Report Interface
+------------------------+
| ← Voice Report |
| |
| [🎤 RECORDING 01:23] |
| |
| "There is a large |
| acute subdural |
| hemorrhage in the |
| right frontoparietal |
| region..." |
| |
| [PAUSE] [STOP] [PLAY] |
| |
| [SAVE DRAFT] |
| [SUBMIT REPORT] |
| |
| Estimated: 2 min left |
+------------------------+
5.1 React Native Project Structure
🏥 RADIOLOGIST APP STRUCTURE
NeoScan_Radiologist/
├── app/
│ ├── modules/ # 🌐 Feature-wise modular architecture
│ │ ├── Auth/ # 🔐 Authentication Module
│ │ │ ├── components/
│ │ │ │ ├── BiometricLogin.tsx
│ │ │ │ ├── PinInput.tsx
│ │ │ │ ├── EmergencyAccess.tsx
│ │ │ │ └── index.ts
│ │ │ ├── screens/
│ │ │ │ ├── LoginScreen.tsx
│ │ │ │ ├── SetupBiometricScreen.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useAuth.ts
│ │ │ │ ├── useBiometric.ts
│ │ │ │ └── index.ts
│ │ │ ├── redux/
│ │ │ │ ├── authSlice.ts
│ │ │ │ ├── authActions.ts
│ │ │ │ ├── authSelectors.ts
│ │ │ │ └── index.ts
│ │ │ ├── services/
│ │ │ │ ├── authAPI.ts
│ │ │ │ ├── biometricService.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── AuthService.test.ts
│ │ │ │ ├── LoginScreen.test.tsx
│ │ │ │ └── authSlice.test.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── Dashboard/ # 📊 Dashboard Module
│ │ │ ├── components/
│ │ │ │ ├── CaseQueue.tsx
│ │ │ │ ├── CaseCard.tsx
│ │ │ │ ├── PriorityIndicator.tsx
│ │ │ │ ├── StatsPanel.tsx
│ │ │ │ ├── FilterBar.tsx
│ │ │ │ └── index.ts
│ │ │ ├── screens/
│ │ │ │ ├── DashboardScreen.tsx
│ │ │ │ ├── CaseListScreen.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useCaseQueue.ts
│ │ │ │ ├── useRealTimeUpdates.ts
│ │ │ │ ├── useFilterCases.ts
│ │ │ │ └── index.ts
│ │ │ ├── redux/
│ │ │ │ ├── dashboardSlice.ts
│ │ │ │ ├── caseQueueSlice.ts
│ │ │ │ ├── dashboardActions.ts
│ │ │ │ ├── dashboardSelectors.ts
│ │ │ │ └── index.ts
│ │ │ ├── services/
│ │ │ │ ├── caseAPI.ts
│ │ │ │ ├── websocketService.ts
│ │ │ │ ├── notificationService.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── CaseQueue.test.tsx
│ │ │ │ ├── dashboardSlice.test.ts
│ │ │ │ └── caseAPI.test.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── CaseReview/ # 🔍 Case Review Module
│ │ │ ├── components/
│ │ │ │ ├── DICOMViewer.tsx
│ │ │ │ ├── AIOverlay.tsx
│ │ │ │ ├── MeasurementTools.tsx
│ │ │ │ ├── AnnotationTools.tsx
│ │ │ │ ├── CaseMetadata.tsx
│ │ │ │ ├── PriorStudyComparison.tsx
│ │ │ │ └── index.ts
│ │ │ ├── screens/
│ │ │ │ ├── CaseDetailsScreen.tsx
│ │ │ │ ├── DICOMViewerScreen.tsx
│ │ │ │ ├── ComparisonScreen.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useDICOMViewer.ts
│ │ │ │ ├── useAIOverlay.ts
│ │ │ │ ├── useMeasurements.ts
│ │ │ │ ├── useAnnotations.ts
│ │ │ │ └── index.ts
│ │ │ ├── redux/
│ │ │ │ ├── caseReviewSlice.ts
│ │ │ │ ├── dicomSlice.ts
│ │ │ │ ├── aiAnalysisSlice.ts
│ │ │ │ ├── caseReviewActions.ts
│ │ │ │ ├── caseReviewSelectors.ts
│ │ │ │ └── index.ts
│ │ │ ├── services/
│ │ │ │ ├── dicomAPI.ts
│ │ │ │ ├── aiAnalysisAPI.ts
│ │ │ │ ├── dicomParser.ts
│ │ │ │ ├── imageProcessor.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── DICOMViewer.test.tsx
│ │ │ │ ├── AIOverlay.test.tsx
│ │ │ │ ├── dicomParser.test.ts
│ │ │ │ └── caseReviewSlice.test.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── Reporting/ # 📝 Reporting Module
│ │ │ ├── components/
│ │ │ │ ├── VoiceRecorder.tsx
│ │ │ │ ├── ReportEditor.tsx
│ │ │ │ ├── ReportTemplate.tsx
│ │ │ │ ├── QuickReportButtons.tsx
│ │ │ │ ├── ReportPreview.tsx
│ │ │ │ └── index.ts
│ │ │ ├── screens/
│ │ │ │ ├── ReportingScreen.tsx
│ │ │ │ ├── VoiceReportScreen.tsx
│ │ │ │ ├── ReportHistoryScreen.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useVoiceRecording.ts
│ │ │ │ ├── useReportGeneration.ts
│ │ │ │ ├── useSpeechToText.ts
│ │ │ │ └── index.ts
│ │ │ ├── redux/
│ │ │ │ ├── reportingSlice.ts
│ │ │ │ ├── voiceRecordingSlice.ts
│ │ │ │ ├── reportingActions.ts
│ │ │ │ ├── reportingSelectors.ts
│ │ │ │ └── index.ts
│ │ │ ├── services/
│ │ │ │ ├── reportAPI.ts
│ │ │ │ ├── voiceToTextAPI.ts
│ │ │ │ ├── reportTemplateService.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── VoiceRecorder.test.tsx
│ │ │ │ ├── reportingSlice.test.ts
│ │ │ │ └── voiceToTextAPI.test.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── Notifications/ # 🔔 Notifications Module
│ │ │ ├── components/
│ │ │ │ ├── NotificationPanel.tsx
│ │ │ │ ├── AlertBanner.tsx
│ │ │ │ ├── CriticalAlert.tsx
│ │ │ │ ├── NotificationSettings.tsx
│ │ │ │ └── index.ts
│ │ │ ├── screens/
│ │ │ │ ├── NotificationsScreen.tsx
│ │ │ │ ├── AlertDetailsScreen.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useNotifications.ts
│ │ │ │ ├── usePushNotifications.ts
│ │ │ │ ├── useAlertHandling.ts
│ │ │ │ └── index.ts
│ │ │ ├── redux/
│ │ │ │ ├── notificationsSlice.ts
│ │ │ │ ├── alertsSlice.ts
│ │ │ │ ├── notificationActions.ts
│ │ │ │ ├── notificationSelectors.ts
│ │ │ │ └── index.ts
│ │ │ ├── services/
│ │ │ │ ├── pushNotificationService.ts
│ │ │ │ ├── alertService.ts
│ │ │ │ ├── notificationAPI.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── NotificationPanel.test.tsx
│ │ │ │ ├── alertService.test.ts
│ │ │ │ └── notificationsSlice.test.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── Analytics/ # 📈 Analytics Module
│ │ │ ├── components/
│ │ │ │ ├── PerformanceMetrics.tsx
│ │ │ │ ├── ResponseTimeChart.tsx
│ │ │ │ ├── ConcordanceStats.tsx
│ │ │ │ ├── WorkloadAnalysis.tsx
│ │ │ │ └── index.ts
│ │ │ ├── screens/
│ │ │ │ ├── AnalyticsScreen.tsx
│ │ │ │ ├── PerformanceScreen.tsx
│ │ │ │ └── index.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useAnalytics.ts
│ │ │ │ ├── usePerformanceMetrics.ts
│ │ │ │ └── index.ts
│ │ │ ├── redux/
│ │ │ │ ├── analyticsSlice.ts
│ │ │ │ ├── analyticsActions.ts
│ │ │ │ ├── analyticsSelectors.ts
│ │ │ │ └── index.ts
│ │ │ ├── services/
│ │ │ │ ├── analyticsAPI.ts
│ │ │ │ ├── metricsCollector.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── PerformanceMetrics.test.tsx
│ │ │ │ ├── analyticsSlice.test.ts
│ │ │ │ └── metricsCollector.test.ts
│ │ │ └── index.ts
│ │ │
│ │ └── Profile/ # 👤 User Profile Module
│ │ ├── components/
│ │ │ ├── ProfileHeader.tsx
│ │ │ ├── SettingsPanel.tsx
│ │ │ ├── PreferencesForm.tsx
│ │ │ ├── SecuritySettings.tsx
│ │ │ └── index.ts
│ │ ├── screens/
│ │ │ ├── ProfileScreen.tsx
│ │ │ ├── SettingsScreen.tsx
│ │ │ ├── PreferencesScreen.tsx
│ │ │ └── index.ts
│ │ ├── hooks/
│ │ │ ├── useProfile.ts
│ │ │ ├── useSettings.ts
│ │ │ └── index.ts
│ │ ├── redux/
│ │ │ ├── profileSlice.ts
│ │ │ ├── settingsSlice.ts
│ │ │ ├── profileActions.ts
│ │ │ ├── profileSelectors.ts
│ │ │ └── index.ts
│ │ ├── services/
│ │ │ ├── profileAPI.ts
│ │ │ ├── settingsAPI.ts
│ │ │ └── index.ts
│ │ ├── __tests__/
│ │ │ ├── ProfileHeader.test.tsx
│ │ │ ├── profileSlice.test.ts
│ │ │ └── profileAPI.test.ts
│ │ └── index.ts
│ │
│ ├── navigation/ # 🧭 Navigation Setup
│ │ ├── AppNavigator.tsx
│ │ ├── AuthNavigator.tsx
│ │ ├── MainNavigator.tsx
│ │ ├── TabNavigator.tsx
│ │ ├── ModalNavigator.tsx
│ │ ├── navigationTypes.ts
│ │ └── index.ts
│ │
│ ├── redux/ # 🗃️ Redux Store Setup
│ │ ├── store.ts
│ │ ├── rootReducer.ts
│ │ ├── middleware.ts
│ │ ├── persistConfig.ts
│ │ └── index.ts
│ │
│ ├── constants/ # 📋 Global Constants
│ │ ├── apiEndpoints.ts
│ │ ├── appConstants.ts
│ │ ├── errorMessages.ts
│ │ ├── notificationTypes.ts
│ │ ├── priorityLevels.ts
│ │ └── index.ts
│ │
│ ├── App.tsx # 🎯 Root Component
│ └── index.ts # 🚀 App Bootstrap
├── shared/ # ✅ Shared Components & Utilities
│ ├── src/
│ │ ├── components/ # 🎨 Global UI Components
│ │ │ ├── Button/
│ │ │ │ ├── Button.tsx
│ │ │ │ ├── Button.styles.ts
│ │ │ │ ├── Button.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── Input/
│ │ │ │ ├── TextInput.tsx
│ │ │ │ ├── SearchInput.tsx
│ │ │ │ ├── Input.styles.ts
│ │ │ │ ├── Input.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── Modal/
│ │ │ │ ├── Modal.tsx
│ │ │ │ ├── ConfirmModal.tsx
│ │ │ │ ├── AlertModal.tsx
│ │ │ │ ├── Modal.styles.ts
│ │ │ │ ├── Modal.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── Card/
│ │ │ │ ├── Card.tsx
│ │ │ │ ├── InfoCard.tsx
│ │ │ │ ├── Card.styles.ts
│ │ │ │ ├── Card.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── Loading/
│ │ │ │ ├── Spinner.tsx
│ │ │ │ ├── LoadingOverlay.tsx
│ │ │ │ ├── Loading.styles.ts
│ │ │ │ └── index.ts
│ │ │ ├── Icons/
│ │ │ │ ├── CustomIcon.tsx
│ │ │ │ ├── IconButton.tsx
│ │ │ │ ├── Icons.types.ts
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── theme/ # 🎨 Design System
│ │ │ ├── colors.ts
│ │ │ ├── spacing.ts
│ │ │ ├── typography.ts
│ │ │ ├── shadows.ts
│ │ │ ├── borderRadius.ts
│ │ │ ├── animations.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── assets/ # 📁 Static Assets
│ │ │ ├── icons/
│ │ │ │ ├── critical-alert.svg
│ │ │ │ ├── urgent-alert.svg
│ │ │ │ ├── routine-alert.svg
│ │ │ │ └── index.ts
│ │ │ ├── images/
│ │ │ │ ├── logo.png
│ │ │ │ ├── placeholder.png
│ │ │ │ └── index.ts
│ │ │ ├── fonts/
│ │ │ │ ├── Roboto-Regular.ttf
│ │ │ │ ├── Roboto-Bold.ttf
│ │ │ │ └── index.ts
│ │ │ └── sounds/
│ │ │ ├── critical-alert.wav
│ │ │ ├── urgent-alert.wav
│ │ │ └── routine-alert.wav
│ │ │
│ │ ├── utils/ # 🛠️ Utility Functions
│ │ │ ├── dateTime/
│ │ │ │ ├── formatDate.ts
│ │ │ │ ├── timeAgo.ts
│ │ │ │ └── index.ts
│ │ │ ├── validation/
│ │ │ │ ├── validators.ts
│ │ │ │ ├── schemas.ts
│ │ │ │ └── index.ts
│ │ │ ├── helpers/
│ │ │ │ ├── debounce.ts
│ │ │ │ ├── throttle.ts
│ │ │ │ ├── formatters.ts
│ │ │ │ └── index.ts
│ │ │ ├── constants/
│ │ │ │ ├── regex.ts
│ │ │ │ ├── formats.ts
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── hooks/ # 🎣 Shared Custom Hooks
│ │ │ ├── useTheme.ts
│ │ │ ├── useDebounce.ts
│ │ │ ├── useKeyboard.ts
│ │ │ ├── useNetworkStatus.ts
│ │ │ ├── useOrientation.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── types/ # 📝 TypeScript Types
│ │ │ ├── common.ts
│ │ │ ├── api.ts
│ │ │ ├── navigation.ts
│ │ │ ├── theme.ts
│ │ │ └── index.ts
│ │ │
│ │ └── index.ts # 📦 Barrel Export
│ │
│ ├── package.json # 📦 Shared Package Config
│ └── tsconfig.json # ⚙️ TypeScript Config
├── __tests__/ # 🧪 Global Tests
│ ├── App.test.tsx
│ ├── Navigation.test.tsx
│ ├── Redux.test.tsx
│ └── Integration.test.tsx
├── assets/ # 🖼️ App-wide Assets
│ ├── images/
│ ├── videos/
│ └── audio/
├── android/ # 🤖 Android Native
├── ios/ # 🍎 iOS Native
├── index.js # 🎯 RN Entry Point
├── package.json # 📦 Root Dependencies
├── tsconfig.json # ⚙️ Base TypeScript Config
├── metro.config.js # 📦 Metro Bundler Config
├── babel.config.js # 🔄 Babel Config
└── .eslintrc.js # 📏 ESLint Config
Key Features:
Real-time WebSocket connections
Offline caching for critical cases
Biometric authentication for radiologists
Performance monitoring with timing metrics
Radiologist App Options
Option 1: "Clinical Gray Spectrum"
Primary: #2C3E50 (Dark Blue-Gray)
Secondary: #34495E (Medium Blue-Gray)
Tertiary: #95A5A6 (Light Gray)
Text Primary: #2C3E50 (Dark Blue-Gray)
Text Secondary: #7F8C8D (Medium Gray)
Text Muted: #BDC3C7 (Light Gray)
Background: #F8F9FA (Light Gray)
Background Alt: #E9ECEF (Darker Light Gray)
Success: #27AE60 (Green)
Warning: #F39C12 (Orange)
Error: #E74C3C (Red)
Info: #3498DB (Blue)
Option 2: "Deep Tech Purple"
Primary: #6C5CE7 (Modern Purple)
Secondary: #A29BFE (Light Purple)
Tertiary: #DDD6FE (Very Light Purple)
Text Primary: #2D3436 (Charcoal)
Text Secondary: #636E72 (Gray)
Text Muted: #B2BEC3 (Light Gray)
Background: #FFFFFF (White)
Background Alt: #F8F7FF (Very Light Purple)
Success: #00B894 (Teal)
Warning: #FDCB6E (Yellow)
Error: #FD79A8 (Pink)
Info: #74B9FF (Light Blue)
Option 3: "Dark Professional"
Primary: #1A365D (Navy Blue)
Secondary: #2D3748 (Dark Gray)
Tertiary: #4A5568 (Medium Gray)
Text Primary: #1A202C (Very Dark Gray)
Text Secondary: #4A5568 (Medium Gray)
Text Muted: #718096 (Light Gray)
Background: #FFFFFF (White)
Background Alt: #F7FAFC (Off White)
Success: #38A169 (Green)
Warning: #DD6B20 (Orange)
Error: #E53E3E (Red)
Info: #3182CE (Blue)
Option 4: "Sophisticated Slate"
Primary: #475569 (Slate Gray)
Secondary: #64748B (Medium Slate)
Tertiary: #CBD5E1 (Light Slate)
Text Primary: #0F172A (Almost Black)
Text Secondary: #475569 (Slate Gray)
Text Muted: #94A3B8 (Light Slate)
Background: #FFFFFF (White)
Background Alt: #F8FAFC (Very Light Slate)
Success: #059669 (Emerald)
Warning: #D97706 (Amber)
Error: #DC2626 (Red)
Info: #0284C7 (Sky Blue)
Radiologist App - "Clinical Blue Interface"
Primary: #5B7CE6 (Blue Purple - from the selected buttons)
Secondary: #7B94F0 (Light Blue Purple)
Tertiary: #E8EFFF (Very Light Blue)
Quaternary: #3B5998 (Deep Blue)
Text Primary: #1A1D29 (Almost Black)
Text Secondary: #4A5568 (Medium Gray)
Text Muted: #9CA3AF (Light Gray)
Background: #FFFFFF (White)
Background Alt: #F8FAFF (Very Light Blue Tint)
Background Accent: #F1F5FF (Soft Blue)
Card Background: #FFFFFF (White with shadow)
Success: #10B981 (Green - from the security icon)
Warning: #F59E0B (Amber)
Error: #EF4444 (Red)
Info: #3B82F6 (Blue)
Border: #E5E7EB (Light Gray)
Selected State: #5B7CE6 (Same as Primary)
Inactive State: #F3F4F6 (Light Gray)
Shadow: rgba(91, 124, 230, 0.1) (Blue Shadow)
Gradient Background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)
Dark professional Prefered
```sh
# Using npm
npm start
# OR using Yarn
yarn start
``` ```
## Step 2: Build and run your app ## Theme
- Primary: #5B7CE6
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: - Secondary: #7B94F0
- Tertiary: #E8EFFF
### Android - Quaternary: #3B5998
- ... (see `shared/src/theme/colors.ts`)
## Setup
1. Install dependencies:
```sh ```sh
# Using npm npm install
npm run android ```
2. Run the app:
# OR using Yarn ```sh
yarn android npm run android # or npm run ios
``` ```
### iOS ## Copyright
Design & Developed by Tech4Biz Solutions
For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). Copyright (c) Spurrin Innovations. All rights reserved.
The first time you create a new project, run the Ruby bundler to install CocoaPods itself:
```sh
bundle install
```
Then, and every time you update your native dependencies, run:
```sh
bundle exec pod install
```
For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html).
```sh
# Using npm
npm run ios
# OR using Yarn
yarn ios
```
If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device.
This is one way to run your app — you can also build it directly from Android Studio or Xcode.

View File

@ -0,0 +1,30 @@
/*
* File: BiometricLogin.test.tsx
* Description: Test for BiometricLogin component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import BiometricLogin from '../components/BiometricLogin';
jest.mock('../hooks/useBiometric', () => ({
useBiometric: () => ({ authenticate: jest.fn() })
}));
describe('BiometricLogin', () => {
it('renders button and triggers handler', () => {
const { getByText } = render(<BiometricLogin />);
const button = getByText('Login with Biometric');
expect(button).toBeTruthy();
fireEvent.press(button);
// No error means handler is called
});
});
/*
* End of File: BiometricLogin.test.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,26 @@
/*
* File: authAPI.test.ts
* Description: Test for authAPI service
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { authAPI } from '../services/authAPI';
jest.mock('apisauce', () => ({
create: () => ({ post: jest.fn(() => Promise.resolve({ ok: true, data: { id: '1', name: 'Dr. Smith', email: 'dr@hospital.com' } })) })
}));
describe('authAPI', () => {
it('calls login and returns data', async () => {
const response = await authAPI.login('dr@hospital.com', 'password');
expect(response.ok).toBe(true);
expect(response.data).toHaveProperty('email', 'dr@hospital.com');
});
});
/*
* End of File: authAPI.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,40 @@
/*
* File: authSlice.test.ts
* Description: Test for authSlice reducer
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import reducer, { loginSuccess, loginFailure } from '../redux/authSlice';
const initialState = {
user: null,
loading: false,
error: null,
isAuthenticated: false,
};
describe('authSlice', () => {
it('handles loginSuccess', () => {
const user = { id: '1', name: 'Dr. Smith', email: 'dr@hospital.com' };
const state = reducer(initialState, loginSuccess(user));
expect(state.user).toEqual(user);
expect(state.isAuthenticated).toBe(true);
expect(state.loading).toBe(false);
expect(state.error).toBeNull();
});
it('handles loginFailure', () => {
const error = 'Invalid credentials';
const state = reducer(initialState, loginFailure(error));
expect(state.loading).toBe(false);
expect(state.error).toBe(error);
expect(state.isAuthenticated).toBe(false);
});
});
/*
* End of File: authSlice.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,32 @@
/*
* File: BiometricLogin.tsx
* Description: Component for biometric login button
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { Button } from 'shared/src/components/Button';
import { useBiometric } from '../hooks/useBiometric';
/**
* BiometricLogin - button to trigger biometric authentication
*/
const BiometricLogin: React.FC = () => {
const { authenticate } = useBiometric();
const handleBiometric = async () => {
await authenticate();
// TODO: Handle result and login
};
return <Button title="Login with Biometric" onPress={handleBiometric} />;
};
export default BiometricLogin;
/*
* End of File: BiometricLogin.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,28 @@
/*
* File: EmergencyAccess.tsx
* Description: Component for emergency access button
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { Button } from 'shared/src/components/Button';
/**
* EmergencyAccess - button to trigger emergency access
*/
const EmergencyAccess: React.FC = () => {
const handleEmergency = () => {
// TODO: Handle emergency access
};
return <Button title="Emergency Access" onPress={handleEmergency} />;
};
export default EmergencyAccess;
/*
* End of File: EmergencyAccess.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,69 @@
/*
* File: PinInput.tsx
* Description: Component for PIN entry
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, StyleSheet, TextInput as RNTextInput } from 'react-native';
import { Colors, Spacing, BorderRadius, Typography } from 'shared/src/theme';
interface PinInputProps {
value: string;
onChange: (val: string) => void;
length?: number;
}
/**
* PinInput - input for entering PIN code
*/
const PinInput: React.FC<PinInputProps> = ({ value, onChange, length = 4 }) => {
return (
<View style={styles.container}>
{Array.from({ length }).map((_, idx) => (
<RNTextInput
key={idx}
style={styles.input}
value={value[idx] || ''}
onChangeText={text => {
const newValue = value.substring(0, idx) + text + value.substring(idx + 1);
onChange(newValue);
}}
maxLength={1}
keyboardType="number-pad"
secureTextEntry
/>
))}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'center',
marginVertical: Spacing.md,
},
input: {
width: 40,
height: 48,
marginHorizontal: Spacing.xs,
backgroundColor: Colors.backgroundAlt,
borderRadius: BorderRadius.md,
textAlign: 'center',
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.textPrimary,
borderWidth: 1,
borderColor: Colors.border,
},
});
export default PinInput;
/*
* End of File: PinInput.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,16 @@
/*
* File: index.ts
* Description: Barrel export for Auth components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as BiometricLogin } from './BiometricLogin';
export { default as PinInput } from './PinInput';
export { default as EmergencyAccess } from './EmergencyAccess';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,15 @@
/*
* File: index.ts
* Description: Barrel export for Auth hooks
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './useAuth';
export * from './useBiometric';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,24 @@
/*
* File: useAuth.ts
* Description: Custom hook for accessing auth state from redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useSelector } from 'react-redux';
import { selectUser, selectIsAuthenticated } from '../redux/authSelectors';
/**
* useAuth - returns the current user and authentication status from redux state
*/
export const useAuth = () => {
const user = useSelector(selectUser);
const isAuthenticated = useSelector(selectIsAuthenticated);
return { user, isAuthenticated };
};
/*
* End of File: useAuth.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

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

View File

@ -0,0 +1,51 @@
/*
* File: AuthNavigator.tsx
* Description: Stack navigator for 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 { LoginScreen, SetupBiometricScreen } from '../screens';
import { Colors } from 'shared/src/theme';
export type AuthStackParamList = {
Login: undefined;
SetupBiometric: undefined;
};
const Stack = createStackNavigator<AuthStackParamList>();
/**
* AuthNavigator sets up stack navigation for Auth module
*/
const AuthNavigator: React.FC = () => (
<Stack.Navigator
initialRouteName="Login"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
headerTintColor: Colors.background,
headerTitleStyle: { fontWeight: 'bold' },
}}
>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ title: 'Login' }}
/>
<Stack.Screen
name="SetupBiometric"
component={SetupBiometricScreen}
options={{ title: 'Setup Biometric' }}
/>
</Stack.Navigator>
);
export default AuthNavigator;
/*
* End of File: AuthNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Barrel export for Auth navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AuthNavigator } from './AuthNavigator';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,38 @@
/*
* 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 { loginStart, loginSuccess, loginFailure } from './authSlice';
import { authAPI } from '../services/authAPI';
/**
* Thunk to login user
*/
export const login = createAsyncThunk(
'auth/login',
async (credentials: { email: string; password: string }, { dispatch, rejectWithValue }) => {
try {
dispatch(loginStart());
const response = await authAPI.login(credentials.email, credentials.password);
if (response.ok && response.data) {
dispatch(loginSuccess(response.data));
} else {
dispatch(loginFailure(response.problem || 'Unknown error'));
return rejectWithValue(response.problem || 'Unknown error');
}
} catch (error: any) {
dispatch(loginFailure(error.message));
return rejectWithValue(error.message);
}
}
);
/*
* End of File: authActions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,19 @@
/*
* File: authSelectors.ts
* Description: Selectors for Auth redux state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { RootState } from 'app/redux/rootReducer';
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;
/*
* End of File: authSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,73 @@
/*
* 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';
// Sample user type
export interface User {
id: string;
name: string;
email: string;
}
// Auth state type
interface AuthState {
user: User | 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: {
loginStart(state) {
state.loading = true;
state.error = null;
},
loginSuccess(state, action: PayloadAction<User>) {
state.user = action.payload;
state.isAuthenticated = true;
state.loading = false;
},
loginFailure(state, action: PayloadAction<string>) {
state.loading = false;
state.error = action.payload;
state.isAuthenticated = false;
},
logout(state) {
state.user = null;
state.isAuthenticated = false;
},
},
});
export const {
loginStart,
loginSuccess,
loginFailure,
logout,
} = authSlice.actions;
export default authSlice.reducer;
/*
* End of File: authSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View 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.
*/

View File

@ -0,0 +1,79 @@
/*
* File: LoginScreen.tsx
* Description: Login screen for user authentication
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { TextInput } from 'shared/src/components/Input';
import { Button } from 'shared/src/components/Button';
import { Colors, Spacing, Typography } from 'shared/src/theme';
import { useDispatch } from 'react-redux';
import { login } from '../redux/authActions';
/**
* LoginScreen - allows user to login with email and password
*/
const LoginScreen: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const handleLogin = () => {
dispatch(login({ email, password }));
};
return (
<View style={styles.container}>
<Text style={styles.title}>Radiologist Portal</Text>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
style={styles.input}
autoCapitalize="none"
keyboardType="email-address"
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
style={styles.input}
secureTextEntry
/>
<Button title="Login" onPress={handleLogin} style={styles.button} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
justifyContent: 'center',
padding: Spacing.lg,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.title,
color: Colors.primary,
marginBottom: Spacing.xl,
textAlign: 'center',
},
input: {
marginBottom: Spacing.md,
},
button: {
marginTop: Spacing.md,
},
});
export default LoginScreen;
/*
* End of File: LoginScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,66 @@
/*
* File: SetupBiometricScreen.tsx
* Description: Screen for setting up biometric authentication
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Button } from 'shared/src/components/Button';
import { Colors, Spacing, Typography } from 'shared/src/theme';
import { useBiometric } from '../hooks/useBiometric';
/**
* SetupBiometricScreen - allows user to setup biometric authentication
*/
const SetupBiometricScreen: React.FC = () => {
const { authenticate } = useBiometric();
const handleSetup = async () => {
await authenticate();
// TODO: Handle result and navigate
};
return (
<View style={styles.container}>
<Text style={styles.title}>Setup Biometric Login</Text>
<Text style={styles.info}>Enable Face ID or Touch ID for quick and secure login.</Text>
<Button title="Enable Biometric" onPress={handleSetup} style={styles.button} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
justifyContent: 'center',
padding: Spacing.lg,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.title,
color: Colors.primary,
marginBottom: Spacing.lg,
textAlign: 'center',
},
info: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
marginBottom: Spacing.xl,
textAlign: 'center',
},
button: {
marginTop: Spacing.md,
},
});
export default SetupBiometricScreen;
/*
* End of File: SetupBiometricScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,15 @@
/*
* File: index.ts
* Description: Barrel export for Auth screens
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as LoginScreen } from './LoginScreen';
export { default as SetupBiometricScreen } from './SetupBiometricScreen';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,27 @@
/*
* 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';
const api = create({
baseURL: 'https://api.example.com', // TODO: Replace with actual endpoint
timeout: 10000,
});
/**
* login - authenticates user with email and password
*/
export const authAPI = {
login: (email: string, password: string) => api.post('/login', { email, password }),
// Add more endpoints as needed
};
/*
* End of File: authAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View 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.
*/

View 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 './authAPI';
export * from './biometricService';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,26 @@
/*
* File: DICOMViewer.test.tsx
* Description: Test for DICOMViewer component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { render } from '@testing-library/react-native';
import DICOMViewer from '../components/DICOMViewer';
describe('DICOMViewer', () => {
it('renders image and findings', () => {
const { getByText } = render(
<DICOMViewer image="https://example.com/image.png" findings="Hemorrhage" confidence={93} />
);
expect(getByText(/AI: Hemorrhage/)).toBeTruthy();
expect(getByText(/93%/)).toBeTruthy();
});
});
/*
* End of File: DICOMViewer.test.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,34 @@
/*
* File: caseReviewSlice.test.ts
* Description: Test for caseReviewSlice reducer
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import reducer, { selectCase, setError, ReviewCase } from '../redux/caseReviewSlice';
const initialState = {
selectedCase: null,
loading: false,
error: null,
};
describe('caseReviewSlice', () => {
it('handles selectCase', () => {
const reviewCase: ReviewCase = { id: '1', patientName: 'John Doe', images: [], aiFindings: 'None' };
const state = reducer(initialState, selectCase(reviewCase));
expect(state.selectedCase).toEqual(reviewCase);
});
it('handles setError', () => {
const error = 'Failed to load';
const state = reducer(initialState, setError(error));
expect(state.error).toBe(error);
});
});
/*
* End of File: caseReviewSlice.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,26 @@
/*
* File: dicomAPI.test.ts
* Description: Test for dicomAPI service
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { dicomAPI } from '../services/dicomAPI';
jest.mock('apisauce', () => ({
create: () => ({ get: jest.fn(() => Promise.resolve({ ok: true, data: ['image1.png', 'image2.png'] })) })
}));
describe('dicomAPI', () => {
it('calls getImages and returns data', async () => {
const response = await dicomAPI.getImages('1');
expect(response.ok).toBe(true);
expect(Array.isArray(response.data)).toBe(true);
});
});
/*
* End of File: dicomAPI.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,54 @@
/*
* File: AIOverlay.tsx
* Description: Component for displaying AI overlay graphics (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface AIOverlayProps {
findings: string;
confidence: number;
}
/**
* AIOverlay - displays AI overlay graphics (stub)
*/
const AIOverlay: React.FC<AIOverlayProps> = ({ findings, confidence }) => (
<View style={styles.overlay}>
{/* TODO: Render overlay graphics */}
<Text style={styles.text}>AI: {findings} ({confidence}%)</Text>
</View>
);
const styles = StyleSheet.create({
overlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(91, 124, 230, 0.1)',
},
text: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.error,
backgroundColor: Colors.background,
padding: Spacing.xs,
borderRadius: 4,
},
});
export default AIOverlay;
/*
* End of File: AIOverlay.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,56 @@
/*
* File: AnnotationTools.tsx
* Description: Component for annotation tools UI (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet, Button as RNButton } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface AnnotationToolsProps {
annotations: any[];
addAnnotation: (a: any) => void;
}
/**
* AnnotationTools - UI for adding/viewing annotations (stub)
*/
const AnnotationTools: React.FC<AnnotationToolsProps> = ({ annotations, addAnnotation }) => (
<View style={styles.container}>
<Text style={styles.title}>Annotations</Text>
{annotations.map((a, idx) => (
<Text key={idx} style={styles.annotation}>{JSON.stringify(a)}</Text>
))}
<RNButton title="Add Annotation" onPress={() => addAnnotation({ note: 'Sample annotation' })} />
</View>
);
const styles = StyleSheet.create({
container: {
marginVertical: Spacing.md,
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.sm,
},
annotation: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
},
});
export default AnnotationTools;
/*
* End of File: AnnotationTools.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,52 @@
/*
* File: CaseMetadata.tsx
* Description: Component for displaying case metadata (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface CaseMetadataProps {
caseData: any;
}
/**
* CaseMetadata - displays metadata for a case (stub)
*/
const CaseMetadata: React.FC<CaseMetadataProps> = ({ caseData }) => (
<View style={styles.container}>
<Text style={styles.title}>Case Metadata</Text>
<Text style={styles.meta}>{JSON.stringify(caseData)}</Text>
</View>
);
const styles = StyleSheet.create({
container: {
marginVertical: Spacing.md,
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.sm,
},
meta: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
},
});
export default CaseMetadata;
/*
* End of File: CaseMetadata.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,53 @@
/*
* File: DICOMViewer.tsx
* Description: Component for displaying a DICOM image with AI overlay (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Image, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface DICOMViewerProps {
image: string;
findings: string;
confidence: number;
}
/**
* DICOMViewer - displays a DICOM image with AI overlay (stub)
*/
const DICOMViewer: React.FC<DICOMViewerProps> = ({ image, findings, confidence }) => (
<View style={styles.container}>
<Image source={{ uri: image }} style={styles.image} resizeMode="contain" />
<Text style={styles.findings}>AI: {findings} ({confidence}%)</Text>
{/* TODO: Render overlay graphics */}
</View>
);
const styles = StyleSheet.create({
container: {
alignItems: 'center',
marginBottom: Spacing.md,
},
image: {
width: '100%',
height: 300,
backgroundColor: Colors.backgroundAlt,
marginBottom: Spacing.sm,
},
findings: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.info,
},
});
export default DICOMViewer;
/*
* End of File: DICOMViewer.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,56 @@
/*
* File: MeasurementTools.tsx
* Description: Component for measurement tools UI (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet, Button as RNButton } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface MeasurementToolsProps {
measurements: any[];
addMeasurement: (m: any) => void;
}
/**
* MeasurementTools - UI for adding/viewing measurements (stub)
*/
const MeasurementTools: React.FC<MeasurementToolsProps> = ({ measurements, addMeasurement }) => (
<View style={styles.container}>
<Text style={styles.title}>Measurements</Text>
{measurements.map((m, idx) => (
<Text key={idx} style={styles.measure}>{JSON.stringify(m)}</Text>
))}
<RNButton title="Add Measurement" onPress={() => addMeasurement({ length: Math.random() * 10 })} />
</View>
);
const styles = StyleSheet.create({
container: {
marginVertical: Spacing.md,
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.sm,
},
measure: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
},
});
export default MeasurementTools;
/*
* End of File: MeasurementTools.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,56 @@
/*
* File: PriorStudyComparison.tsx
* Description: Component for comparing prior studies (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface PriorStudyComparisonProps {
prior: any;
current: any;
}
/**
* PriorStudyComparison - compares prior and current studies (stub)
*/
const PriorStudyComparison: React.FC<PriorStudyComparisonProps> = ({ prior, current }) => (
<View style={styles.container}>
<Text style={styles.title}>Prior Study</Text>
<Text style={styles.data}>{JSON.stringify(prior)}</Text>
<Text style={styles.title}>Current Study</Text>
<Text style={styles.data}>{JSON.stringify(current)}</Text>
</View>
);
const styles = StyleSheet.create({
container: {
marginVertical: Spacing.md,
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.sm,
},
data: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
marginBottom: Spacing.md,
},
});
export default PriorStudyComparison;
/*
* End of File: PriorStudyComparison.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,19 @@
/*
* File: index.ts
* Description: Barrel export for CaseReview components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as DICOMViewer } from './DICOMViewer';
export { default as AIOverlay } from './AIOverlay';
export { default as MeasurementTools } from './MeasurementTools';
export { default as AnnotationTools } from './AnnotationTools';
export { default as CaseMetadata } from './CaseMetadata';
export { default as PriorStudyComparison } from './PriorStudyComparison';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,17 @@
/*
* File: index.ts
* Description: Barrel export for CaseReview hooks
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './useDICOMViewer';
export * from './useAIOverlay';
export * from './useMeasurements';
export * from './useAnnotations';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,24 @@
/*
* File: useAIOverlay.ts
* Description: Custom hook for AI overlay logic (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useSelector } from 'react-redux';
import { selectAIFindings, selectAIConfidence } from '../redux/caseReviewSelectors';
/**
* useAIOverlay - returns AI findings and confidence (stub)
*/
export const useAIOverlay = () => {
const findings = useSelector(selectAIFindings);
const confidence = useSelector(selectAIConfidence);
return { findings, confidence };
};
/*
* End of File: useAIOverlay.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,23 @@
/*
* File: useAnnotations.ts
* Description: Custom hook for annotation tools (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useState } from 'react';
/**
* useAnnotations - manages annotations (stub)
*/
export const useAnnotations = () => {
const [annotations, setAnnotations] = useState<any[]>([]);
const addAnnotation = (a: any) => setAnnotations(prev => [...prev, a]);
return { annotations, addAnnotation };
};
/*
* End of File: useAnnotations.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,24 @@
/*
* File: useDICOMViewer.ts
* Description: Custom hook for DICOM viewer logic (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useSelector } from 'react-redux';
import { selectDICOMImages } from '../redux/caseReviewSelectors';
/**
* useDICOMViewer - returns current DICOM image and navigation helpers (stub)
*/
export const useDICOMViewer = () => {
const images = useSelector(selectDICOMImages);
// TODO: Add navigation logic (next/prev image)
return { images };
};
/*
* End of File: useDICOMViewer.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,23 @@
/*
* File: useMeasurements.ts
* Description: Custom hook for measurement tools (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useState } from 'react';
/**
* useMeasurements - manages measurements (stub)
*/
export const useMeasurements = () => {
const [measurements, setMeasurements] = useState<any[]>([]);
const addMeasurement = (m: any) => setMeasurements(prev => [...prev, m]);
return { measurements, addMeasurement };
};
/*
* End of File: useMeasurements.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,57 @@
/*
* File: CaseReviewNavigator.tsx
* Description: Stack navigator for CaseReview module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { CaseDetailsScreen, DICOMViewerScreen, ComparisonScreen } from '../screens';
import { Colors } from 'shared/src/theme';
export type CaseReviewStackParamList = {
CaseDetails: undefined;
DICOMViewer: undefined;
Comparison: undefined;
};
const Stack = createStackNavigator<CaseReviewStackParamList>();
/**
* CaseReviewNavigator sets up stack navigation for CaseReview module
*/
const CaseReviewNavigator: React.FC = () => (
<Stack.Navigator
initialRouteName="CaseDetails"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
headerTintColor: Colors.background,
headerTitleStyle: { fontWeight: 'bold' },
}}
>
<Stack.Screen
name="CaseDetails"
component={CaseDetailsScreen}
options={{ title: 'Case Details' }}
/>
<Stack.Screen
name="DICOMViewer"
component={DICOMViewerScreen}
options={{ title: 'DICOM Viewer' }}
/>
<Stack.Screen
name="Comparison"
component={ComparisonScreen}
options={{ title: 'Comparison' }}
/>
</Stack.Navigator>
);
export default CaseReviewNavigator;
/*
* End of File: CaseReviewNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Barrel export for CaseReview navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as CaseReviewNavigator } from './CaseReviewNavigator';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,58 @@
/*
* File: aiAnalysisSlice.ts
* Description: Redux slice for AI analysis state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface AIAnalysisState {
findings: string;
confidence: number;
loading: boolean;
}
const initialState: AIAnalysisState = {
findings: '',
confidence: 0,
loading: false,
};
/**
* aiAnalysisSlice for managing AI findings and confidence
*/
const aiAnalysisSlice = createSlice({
name: 'aiAnalysis',
initialState,
reducers: {
setFindings(state, action: PayloadAction<string>) {
state.findings = action.payload;
},
setConfidence(state, action: PayloadAction<number>) {
state.confidence = action.payload;
},
setLoading(state, action: PayloadAction<boolean>) {
state.loading = action.payload;
},
clearAnalysis(state) {
state.findings = '';
state.confidence = 0;
},
},
});
export const {
setFindings,
setConfidence,
setLoading,
clearAnalysis,
} = aiAnalysisSlice.actions;
export default aiAnalysisSlice.reducer;
/*
* End of File: aiAnalysisSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,53 @@
/*
* File: caseReviewActions.ts
* Description: Async actions (thunks) for CaseReview state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createAsyncThunk } from '@reduxjs/toolkit';
import { setLoading, selectCase, setError } from './caseReviewSlice';
import { setImages } from './dicomSlice';
import { setFindings, setConfidence } from './aiAnalysisSlice';
import { dicomAPI } from '../services/dicomAPI';
import { aiAnalysisAPI } from '../services/aiAnalysisAPI';
/**
* Thunk to fetch case details, DICOM images, and AI analysis
*/
export const fetchCaseDetails = createAsyncThunk(
'caseReview/fetchCaseDetails',
async (caseId: string, { dispatch, rejectWithValue }) => {
try {
dispatch(setLoading(true));
// Fetch DICOM images
const dicomRes = await dicomAPI.getImages(caseId);
if (dicomRes.ok && dicomRes.data) {
dispatch(setImages(dicomRes.data));
} else {
dispatch(setError('Failed to load DICOM images'));
return rejectWithValue('Failed to load DICOM images');
}
// Fetch AI analysis
const aiRes = await aiAnalysisAPI.getAnalysis(caseId);
if (aiRes.ok && aiRes.data) {
dispatch(setFindings(aiRes.data.findings));
dispatch(setConfidence(aiRes.data.confidence));
} else {
dispatch(setError('Failed to load AI analysis'));
return rejectWithValue('Failed to load AI analysis');
}
dispatch(setLoading(false));
} catch (error: any) {
dispatch(setError(error.message));
dispatch(setLoading(false));
return rejectWithValue(error.message);
}
}
);
/*
* End of File: caseReviewActions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,21 @@
/*
* File: caseReviewSelectors.ts
* Description: Selectors for CaseReview redux state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { RootState } from 'app/redux/rootReducer';
export const selectSelectedCase = (state: RootState) => state.caseReview.selectedCase;
export const selectCaseReviewLoading = (state: RootState) => state.caseReview.loading;
export const selectCaseReviewError = (state: RootState) => state.caseReview.error;
export const selectDICOMImages = (state: RootState) => state.dicom.images;
export const selectAIFindings = (state: RootState) => state.aiAnalysis.findings;
export const selectAIConfidence = (state: RootState) => state.aiAnalysis.confidence;
/*
* End of File: caseReviewSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,66 @@
/*
* File: caseReviewSlice.ts
* Description: Redux slice for CaseReview state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// Sample case type
export interface ReviewCase {
id: string;
patientName: string;
images: string[];
aiFindings: string;
}
// CaseReview state type
interface CaseReviewState {
selectedCase: ReviewCase | null;
loading: boolean;
error: string | null;
}
const initialState: CaseReviewState = {
selectedCase: null,
loading: false,
error: null,
};
/**
* CaseReview slice for managing selected case and review state
*/
const caseReviewSlice = createSlice({
name: 'caseReview',
initialState,
reducers: {
selectCase(state, action: PayloadAction<ReviewCase>) {
state.selectedCase = action.payload;
},
clearCase(state) {
state.selectedCase = null;
},
setLoading(state, action: PayloadAction<boolean>) {
state.loading = action.payload;
},
setError(state, action: PayloadAction<string | null>) {
state.error = action.payload;
},
},
});
export const {
selectCase,
clearCase,
setLoading,
setError,
} = caseReviewSlice.actions;
export default caseReviewSlice.reducer;
/*
* End of File: caseReviewSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,59 @@
/*
* File: dicomSlice.ts
* Description: Redux slice for DICOM image state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface DICOMState {
images: string[];
currentIndex: number;
loading: boolean;
}
const initialState: DICOMState = {
images: [],
currentIndex: 0,
loading: false,
};
/**
* dicomSlice for managing DICOM images and navigation
*/
const dicomSlice = createSlice({
name: 'dicom',
initialState,
reducers: {
setImages(state, action: PayloadAction<string[]>) {
state.images = action.payload;
state.currentIndex = 0;
},
setCurrentIndex(state, action: PayloadAction<number>) {
state.currentIndex = action.payload;
},
setLoading(state, action: PayloadAction<boolean>) {
state.loading = action.payload;
},
clearImages(state) {
state.images = [];
state.currentIndex = 0;
},
},
});
export const {
setImages,
setCurrentIndex,
setLoading,
clearImages,
} = dicomSlice.actions;
export default dicomSlice.reducer;
/*
* End of File: dicomSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,18 @@
/*
* File: index.ts
* Description: Barrel export for CaseReview redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as caseReviewReducer } from './caseReviewSlice';
export { default as dicomReducer } from './dicomSlice';
export { default as aiAnalysisReducer } from './aiAnalysisSlice';
export * from './caseReviewActions';
export * from './caseReviewSelectors';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,83 @@
/*
* File: CaseDetailsScreen.tsx
* Description: Screen for displaying case details
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Button } from 'shared/src/components/Button';
import { Card } from 'shared/src/components/Card';
import { Colors, Spacing, Typography } from 'shared/src/theme';
import { useSelector } from 'react-redux';
import { selectSelectedCase } from '../redux/caseReviewSelectors';
import { useAIOverlay } from '../hooks/useAIOverlay';
/**
* CaseDetailsScreen - shows patient info, AI findings, and navigation to DICOM viewer
*/
const CaseDetailsScreen: React.FC<any> = ({ navigation }) => {
const caseData = useSelector(selectSelectedCase);
const { findings, confidence } = useAIOverlay();
if (!caseData) {
return <View style={styles.container}><Text>No case selected.</Text></View>;
}
return (
<View style={styles.container}>
<Card>
<Text style={styles.title}>{caseData.patientName}</Text>
<Text style={styles.label}>AI Findings:</Text>
<Text style={styles.findings}>{findings}</Text>
<Text style={styles.confidence}>Confidence: {confidence}%</Text>
<Button title="View DICOM Images" onPress={() => navigation.navigate('DICOMViewer')} style={styles.button} />
</Card>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
justifyContent: 'center',
padding: Spacing.lg,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
marginBottom: Spacing.md,
},
label: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
marginTop: Spacing.sm,
},
findings: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
marginBottom: Spacing.sm,
},
confidence: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.info,
marginBottom: Spacing.md,
},
button: {
marginTop: Spacing.md,
},
});
export default CaseDetailsScreen;
/*
* End of File: CaseDetailsScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,63 @@
/*
* File: ComparisonScreen.tsx
* Description: Screen for comparing prior studies
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Card } from 'shared/src/components/Card';
import { Colors, Spacing, Typography } from 'shared/src/theme';
/**
* ComparisonScreen - shows prior/current images and findings (stub)
*/
const ComparisonScreen: React.FC = () => {
return (
<View style={styles.container}>
<Card>
<Text style={styles.title}>Comparison</Text>
<Text style={styles.label}>Prior Study:</Text>
<Text style={styles.stub}>[Prior Image/Findings Here]</Text>
<Text style={styles.label}>Current Study:</Text>
<Text style={styles.stub}>[Current Image/Findings Here]</Text>
</Card>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
justifyContent: 'center',
padding: Spacing.lg,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
marginBottom: Spacing.md,
},
label: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
marginTop: Spacing.sm,
},
stub: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
marginBottom: Spacing.sm,
},
});
export default ComparisonScreen;
/*
* End of File: ComparisonScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,66 @@
/*
* File: DICOMViewerScreen.tsx
* Description: Screen for viewing DICOM images
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet, Image } from 'react-native';
import { Button } from 'shared/src/components/Button';
import { Colors, Spacing, Typography } from 'shared/src/theme';
import { useDICOMViewer } from '../hooks/useDICOMViewer';
/**
* DICOMViewerScreen - shows current DICOM image and navigation controls (stub)
*/
const DICOMViewerScreen: React.FC<any> = ({ navigation }) => {
const { images } = useDICOMViewer();
// TODO: Add navigation logic for images
const currentImage = images[0];
return (
<View style={styles.container}>
<Text style={styles.title}>DICOM Viewer</Text>
{currentImage ? (
<Image source={{ uri: currentImage }} style={styles.image} resizeMode="contain" />
) : (
<Text>No images available.</Text>
)}
<Button title="Back to Details" onPress={() => navigation.goBack()} style={styles.button} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
justifyContent: 'center',
alignItems: 'center',
padding: Spacing.lg,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
marginBottom: Spacing.md,
},
image: {
width: '100%',
height: 300,
backgroundColor: Colors.backgroundAlt,
marginBottom: Spacing.md,
},
button: {
marginTop: Spacing.md,
},
});
export default DICOMViewerScreen;
/*
* End of File: DICOMViewerScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,16 @@
/*
* File: index.ts
* Description: Barrel export for CaseReview screens
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as CaseDetailsScreen } from './CaseDetailsScreen';
export { default as DICOMViewerScreen } from './DICOMViewerScreen';
export { default as ComparisonScreen } from './ComparisonScreen';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,27 @@
/*
* File: aiAnalysisAPI.ts
* Description: API service for fetching AI analysis using apisauce
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
const api = create({
baseURL: 'https://api.example.com', // TODO: Replace with actual endpoint
timeout: 10000,
});
/**
* getAnalysis - fetches AI analysis for a case
*/
export const aiAnalysisAPI = {
getAnalysis: (caseId: string) => api.get(`/cases/${caseId}/ai-analysis`),
// Add more endpoints as needed
};
/*
* End of File: aiAnalysisAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,27 @@
/*
* File: dicomAPI.ts
* Description: API service for fetching DICOM images using apisauce
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
const api = create({
baseURL: 'https://api.example.com', // TODO: Replace with actual endpoint
timeout: 10000,
});
/**
* getImages - fetches DICOM images for a case
*/
export const dicomAPI = {
getImages: (caseId: string) => api.get(`/cases/${caseId}/dicom`),
// Add more endpoints as needed
};
/*
* End of File: dicomAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,22 @@
/*
* File: dicomParser.ts
* Description: Utility for parsing DICOM files (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
/**
* dicomParser - stub for parsing DICOM files
*/
export const dicomParser = {
parse: (file: any) => {
// TODO: Implement DICOM parsing logic
return {};
},
};
/*
* End of File: dicomParser.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,22 @@
/*
* File: imageProcessor.ts
* Description: Utility for image processing (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
/**
* imageProcessor - stub for image processing utilities
*/
export const imageProcessor = {
process: (image: any) => {
// TODO: Implement image processing logic
return image;
},
};
/*
* End of File: imageProcessor.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,17 @@
/*
* File: index.ts
* Description: Barrel export for CaseReview services
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './dicomAPI';
export * from './aiAnalysisAPI';
export * from './dicomParser';
export * from './imageProcessor';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,34 @@
/*
* File: CaseQueue.test.tsx
* Description: Test for CaseQueue component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import CaseQueue from '../components/CaseQueue';
const mockCases = [
{ id: '1', patientName: 'John Doe', status: 'critical', aiConfidence: 93, description: 'Head trauma' },
{ id: '2', patientName: 'Jane Smith', status: 'routine', aiConfidence: 99, description: 'Headache' },
];
describe('CaseQueue', () => {
it('renders cases', () => {
const { getByText } = render(<CaseQueue cases={mockCases} onSelect={() => {}} />);
expect(getByText('John Doe')).toBeTruthy();
expect(getByText('Jane Smith')).toBeTruthy();
});
it('renders empty state', () => {
const { getByText } = render(<CaseQueue cases={[]} onSelect={() => {}} />);
expect(getByText('No cases in queue.')).toBeTruthy();
});
});
/*
* End of File: CaseQueue.test.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,26 @@
/*
* File: caseAPI.test.ts
* Description: Test for caseAPI service
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { caseAPI } from '../services/caseAPI';
jest.mock('apisauce', () => ({
create: () => ({ get: jest.fn(() => Promise.resolve({ ok: true, data: [] })) })
}));
describe('caseAPI', () => {
it('calls getCases and returns data', async () => {
const response = await caseAPI.getCases();
expect(response.ok).toBe(true);
expect(Array.isArray(response.data)).toBe(true);
});
});
/*
* End of File: caseAPI.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,37 @@
/*
* File: dashboardSlice.test.ts
* Description: Test for dashboardSlice reducer
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import reducer, { fetchCasesSuccess, fetchCasesFailure } from '../redux/dashboardSlice';
const initialState = {
caseQueue: [],
loading: false,
error: null,
};
describe('dashboardSlice', () => {
it('handles fetchCasesSuccess', () => {
const cases = [{ id: '1', patientName: 'John Doe', status: 'critical', aiConfidence: 93, description: 'Head trauma' }];
const state = reducer(initialState, fetchCasesSuccess(cases));
expect(state.caseQueue).toEqual(cases);
expect(state.loading).toBe(false);
expect(state.error).toBeNull();
});
it('handles fetchCasesFailure', () => {
const error = 'Failed to fetch';
const state = reducer(initialState, fetchCasesFailure(error));
expect(state.loading).toBe(false);
expect(state.error).toBe(error);
});
});
/*
* End of File: dashboardSlice.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,78 @@
/*
* File: CaseCard.tsx
* Description: Component to display a single case card
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Card } from 'shared/src/components/Card';
import { CustomIcon } from 'shared/src/components/Icons';
import { Colors, Spacing, Typography } from 'shared/src/theme';
import { Case } from '../redux/dashboardSlice';
interface CaseCardProps {
item: Case;
onPress: (item: Case) => void;
}
/**
* CaseCard - displays a single case card
*/
const CaseCard: React.FC<CaseCardProps> = ({ item, onPress }) => (
<TouchableOpacity onPress={() => onPress(item)}>
<Card style={styles.card}>
<View style={styles.row}>
<CustomIcon
name={item.status === 'critical' ? 'alert' : item.status === 'urgent' ? 'alert-outline' : 'check-circle'}
color={item.status === 'critical' ? Colors.error : item.status === 'urgent' ? Colors.warning : Colors.success}
size={24}
/>
<View style={styles.info}>
<Text style={styles.patient}>{item.patientName}</Text>
<Text style={styles.desc}>{item.description}</Text>
</View>
<Text style={styles.confidence}>{item.aiConfidence}%</Text>
</View>
</Card>
</TouchableOpacity>
);
const styles = StyleSheet.create({
card: {
marginBottom: Spacing.sm,
},
row: {
flexDirection: 'row',
alignItems: 'center',
},
info: {
flex: 1,
marginLeft: Spacing.sm,
},
patient: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
},
desc: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
},
confidence: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.info,
marginLeft: Spacing.sm,
},
});
export default CaseCard;
/*
* End of File: CaseCard.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,95 @@
/*
* File: CaseQueue.tsx
* Description: Component to display a list of cases in the queue
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { Card, InfoCard } from 'shared/src/components/Card';
import { CustomIcon } from 'shared/src/components/Icons';
import { Colors, Spacing, Typography } from 'shared/src/theme';
import { Case } from '../redux/dashboardSlice';
interface CaseQueueProps {
cases: Case[];
onSelect: (item: Case) => void;
}
/**
* CaseQueue - displays a list of cases in the queue
*/
const CaseQueue: React.FC<CaseQueueProps> = ({ cases, onSelect }) => {
if (!cases.length) {
return <InfoCard><Text style={styles.empty}>No cases in queue.</Text></InfoCard>;
}
return (
<FlatList
data={cases}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => onSelect(item)}>
<Card style={styles.card}>
<View style={styles.row}>
<CustomIcon
name={item.status === 'critical' ? 'alert' : item.status === 'urgent' ? 'alert-outline' : 'check-circle'}
color={item.status === 'critical' ? Colors.error : item.status === 'urgent' ? Colors.warning : Colors.success}
size={24}
/>
<View style={styles.info}>
<Text style={styles.patient}>{item.patientName}</Text>
<Text style={styles.desc}>{item.description}</Text>
</View>
<Text style={styles.confidence}>{item.aiConfidence}%</Text>
</View>
</Card>
</TouchableOpacity>
)}
/>
);
};
const styles = StyleSheet.create({
card: {
marginBottom: Spacing.sm,
},
row: {
flexDirection: 'row',
alignItems: 'center',
},
info: {
flex: 1,
marginLeft: Spacing.sm,
},
patient: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
},
desc: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
},
confidence: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.info,
marginLeft: Spacing.sm,
},
empty: {
textAlign: 'center',
color: Colors.textMuted,
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
},
});
export default CaseQueue;
/*
* End of File: CaseQueue.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,70 @@
/*
* File: FilterBar.tsx
* Description: Component to filter cases by status
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'shared/src/components/Button';
import { Colors, Spacing } from 'shared/src/theme';
interface FilterBarProps {
filter: 'critical' | 'urgent' | 'routine';
onChange: (filter: 'critical' | 'urgent' | 'routine') => void;
}
/**
* FilterBar - allows filtering cases by status
*/
const FilterBar: React.FC<FilterBarProps> = ({ filter, onChange }) => (
<View style={styles.container}>
<Button
title="Critical"
onPress={() => onChange('critical')}
style={[styles.button, filter === 'critical' && styles.selected]}
textStyle={filter === 'critical' ? styles.selectedText : undefined}
/>
<Button
title="Urgent"
onPress={() => onChange('urgent')}
style={[styles.button, filter === 'urgent' && styles.selected]}
textStyle={filter === 'urgent' ? styles.selectedText : undefined}
/>
<Button
title="Routine"
onPress={() => onChange('routine')}
style={[styles.button, filter === 'routine' && styles.selected]}
textStyle={filter === 'routine' ? styles.selectedText : undefined}
/>
</View>
);
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: Spacing.md,
},
button: {
flex: 1,
marginHorizontal: Spacing.xs,
backgroundColor: Colors.inactiveState,
},
selected: {
backgroundColor: Colors.selectedState,
},
selectedText: {
color: Colors.background,
fontWeight: 'bold',
},
});
export default FilterBar;
/*
* End of File: FilterBar.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,61 @@
/*
* File: PriorityIndicator.tsx
* Description: Component to display a colored dot and label for case priority
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface PriorityIndicatorProps {
status: 'critical' | 'urgent' | 'routine';
}
/**
* PriorityIndicator - displays a colored dot and label for case priority
*/
export const PriorityIndicator: React.FC<PriorityIndicatorProps> = ({ status }) => {
const color =
status === 'critical'
? Colors.error
: status === 'urgent'
? Colors.warning
: Colors.success;
const label =
status === 'critical'
? 'Critical'
: status === 'urgent'
? 'Urgent'
: 'Routine';
return (
<View style={styles.container}>
<View style={[styles.dot, { backgroundColor: color }]} />
<Text style={[styles.label, { color }]}>{label}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
marginRight: Spacing.xs,
},
label: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.sm,
},
});
/*
* End of File: PriorityIndicator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,70 @@
/*
* File: StatsPanel.tsx
* Description: Component to display dashboard statistics (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Card } from 'shared/src/components/Card';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface StatsPanelProps {
stats: { label: string; value: string | number }[];
}
/**
* StatsPanel - displays dashboard statistics (stub)
*/
const StatsPanel: React.FC<StatsPanelProps> = ({ stats }) => (
<Card style={styles.card}>
<Text style={styles.title}>Statistics</Text>
<View style={styles.statsRow}>
{stats.map((stat, idx) => (
<View key={idx} style={styles.statItem}>
<Text style={styles.statValue}>{stat.value}</Text>
<Text style={styles.statLabel}>{stat.label}</Text>
</View>
))}
</View>
</Card>
);
const styles = StyleSheet.create({
card: {
marginBottom: Spacing.md,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
marginBottom: Spacing.sm,
},
statsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
statItem: {
alignItems: 'center',
flex: 1,
},
statValue: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
},
statLabel: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
color: Colors.textSecondary,
},
});
export default StatsPanel;
/*
* End of File: StatsPanel.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,18 @@
/*
* File: index.ts
* Description: Barrel export for Dashboard components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as CaseQueue } from './CaseQueue';
export { default as CaseCard } from './CaseCard';
export { PriorityIndicator } from './PriorityIndicator';
export { default as StatsPanel } from './StatsPanel';
export { default as FilterBar } from './FilterBar';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,16 @@
/*
* File: index.ts
* Description: Barrel export for Dashboard hooks
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './useCaseQueue';
export * from './useRealTimeUpdates';
export * from './useFilterCases';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,22 @@
/*
* File: useCaseQueue.ts
* Description: Custom hook for accessing the case queue from redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useSelector } from 'react-redux';
import { selectCaseQueue } from '../redux/dashboardSelectors';
/**
* useCaseQueue - returns the current case queue from redux state
*/
export const useCaseQueue = () => {
return useSelector(selectCaseQueue);
};
/*
* End of File: useCaseQueue.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,22 @@
/*
* File: useFilterCases.ts
* Description: Custom hook for filtering cases by status
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useMemo } from 'react';
import { Case } from '../redux/dashboardSlice';
/**
* useFilterCases - returns filtered cases by status
*/
export const useFilterCases = (cases: Case[], status: 'critical' | 'urgent' | 'routine') => {
return useMemo(() => cases.filter(c => c.status === status), [cases, status]);
};
/*
* End of File: useFilterCases.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,27 @@
/*
* File: useRealTimeUpdates.ts
* Description: Custom hook for subscribing to real-time updates (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useEffect } from 'react';
/**
* useRealTimeUpdates - subscribes to real-time updates (stub)
*/
export const useRealTimeUpdates = () => {
useEffect(() => {
// TODO: Connect to websocket and handle updates
// Example: websocketService.subscribe(...)
return () => {
// Cleanup subscription
};
}, []);
};
/*
* End of File: useRealTimeUpdates.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,47 @@
/*
* File: DashboardNavigator.tsx
* Description: Stack navigator for 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 { DashboardScreen } from '../screens';
import { Colors } from 'shared/src/theme';
export type DashboardStackParamList = {
Dashboard: undefined;
// Add more screens here as needed
};
const Stack = createStackNavigator<DashboardStackParamList>();
/**
* DashboardNavigator sets up stack navigation for Dashboard module
*/
const DashboardNavigator: React.FC = () => (
<Stack.Navigator
initialRouteName="Dashboard"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
headerTintColor: Colors.background,
headerTitleStyle: { fontWeight: 'bold' },
}}
>
<Stack.Screen
name="Dashboard"
component={DashboardScreen}
options={{ title: 'Dashboard' }}
/>
{/* Add more screens here */}
</Stack.Navigator>
);
export default DashboardNavigator;
/*
* End of File: DashboardNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Barrel export for Dashboard navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as DashboardNavigator } from './DashboardNavigator';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,61 @@
/*
* File: caseQueueSlice.ts
* Description: Redux slice for case queue state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Case } from './dashboardSlice';
interface CaseQueueState {
queue: Case[];
loading: boolean;
error: string | null;
}
const initialState: CaseQueueState = {
queue: [],
loading: false,
error: null,
};
/**
* Slice for managing the case queue
*/
const caseQueueSlice = createSlice({
name: 'caseQueue',
initialState,
reducers: {
fetchQueueStart(state) {
state.loading = true;
state.error = null;
},
fetchQueueSuccess(state, action: PayloadAction<Case[]>) {
state.queue = action.payload;
state.loading = false;
},
fetchQueueFailure(state, action: PayloadAction<string>) {
state.loading = false;
state.error = action.payload;
},
clearQueue(state) {
state.queue = [];
},
},
});
export const {
fetchQueueStart,
fetchQueueSuccess,
fetchQueueFailure,
clearQueue,
} = caseQueueSlice.actions;
export default caseQueueSlice.reducer;
/*
* End of File: caseQueueSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,38 @@
/*
* File: dashboardActions.ts
* Description: Async actions (thunks) for Dashboard state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createAsyncThunk } from '@reduxjs/toolkit';
import { fetchCasesStart, fetchCasesSuccess, fetchCasesFailure } from './dashboardSlice';
import { caseAPI } from '../services/caseAPI';
/**
* Thunk to fetch case queue from API
*/
export const fetchCases = createAsyncThunk(
'dashboard/fetchCases',
async (_, { dispatch, rejectWithValue }) => {
try {
dispatch(fetchCasesStart());
const response = await caseAPI.getCases();
if (response.ok && response.data) {
dispatch(fetchCasesSuccess(response.data));
} else {
dispatch(fetchCasesFailure(response.problem || 'Unknown error'));
return rejectWithValue(response.problem || 'Unknown error');
}
} catch (error: any) {
dispatch(fetchCasesFailure(error.message));
return rejectWithValue(error.message);
}
}
);
/*
* End of File: dashboardActions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,18 @@
/*
* File: dashboardSelectors.ts
* Description: Selectors for Dashboard redux state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { RootState } from 'app/redux/rootReducer';
export const selectCaseQueue = (state: RootState) => state.dashboard.caseQueue;
export const selectDashboardLoading = (state: RootState) => state.dashboard.loading;
export const selectDashboardError = (state: RootState) => state.dashboard.error;
/*
* End of File: dashboardSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,70 @@
/*
* File: dashboardSlice.ts
* Description: Redux slice for Dashboard state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// Sample case type
export interface Case {
id: string;
patientName: string;
status: 'critical' | 'urgent' | 'routine';
aiConfidence: number;
description: string;
}
// Dashboard state type
interface DashboardState {
caseQueue: Case[];
loading: boolean;
error: string | null;
}
const initialState: DashboardState = {
caseQueue: [],
loading: false,
error: null,
};
/**
* Dashboard slice for managing case queue and dashboard state
*/
const dashboardSlice = createSlice({
name: 'dashboard',
initialState,
reducers: {
fetchCasesStart(state) {
state.loading = true;
state.error = null;
},
fetchCasesSuccess(state, action: PayloadAction<Case[]>) {
state.caseQueue = action.payload;
state.loading = false;
},
fetchCasesFailure(state, action: PayloadAction<string>) {
state.loading = false;
state.error = action.payload;
},
clearCases(state) {
state.caseQueue = [];
},
},
});
export const {
fetchCasesStart,
fetchCasesSuccess,
fetchCasesFailure,
clearCases,
} = dashboardSlice.actions;
export default dashboardSlice.reducer;
/*
* End of File: dashboardSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,16 @@
/*
* File: index.ts
* Description: Barrel export for Dashboard redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as dashboardReducer } from './dashboardSlice';
export * from './dashboardActions';
export * from './dashboardSelectors';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,175 @@
/*
* File: DashboardScreen.tsx
* Description: Sample dashboard screen using Clinical Blue Interface and shared components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'shared/src/components/Button';
import { Card, InfoCard } from 'shared/src/components/Card';
import { TextInput, SearchInput } from 'shared/src/components/Input';
import { Modal, ConfirmModal, AlertModal } from 'shared/src/components/Modal';
import { Spinner, LoadingOverlay } from 'shared/src/components/Loading';
import { CustomIcon, IconButton } from 'shared/src/components/Icons';
import { Colors, Spacing, Typography } from 'shared/src/theme';
/**
* DashboardScreen demonstrates usage of all shared, themed components.
* This is a reference implementation for other modules.
*/
const DashboardScreen: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
const [confirmVisible, setConfirmVisible] = useState(false);
const [alertVisible, setAlertVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState('');
const [input, setInput] = useState('');
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<Text style={styles.header}>Dashboard</Text>
<SearchInput
placeholder="Search cases..."
value={search}
onChangeText={setSearch}
containerStyle={styles.search}
/>
<InfoCard>
<Text style={styles.infoTitle}>Welcome, Dr. Smith</Text>
<Text style={styles.infoText}>On-Call Status: <Text style={styles.active}>ACTIVE</Text></Text>
</InfoCard>
<Card>
<Text style={styles.cardTitle}>Critical Cases</Text>
<View style={styles.row}>
<CustomIcon name="alert" color={Colors.error} size={24} />
<Text style={styles.criticalText}>Bed 3 - Hemorrhage (93% AI)</Text>
<Button title="Review Now" onPress={() => setModalVisible(true)} style={styles.button} />
</View>
</Card>
<Card>
<Text style={styles.cardTitle}>Routine Cases</Text>
<View style={styles.row}>
<CustomIcon name="check-circle" color={Colors.success} size={24} />
<Text style={styles.routineText}>Bed 12 - Headache (99% AI)</Text>
<IconButton name="chevron-right" onPress={() => setConfirmVisible(true)} />
</View>
</Card>
<TextInput
placeholder="Add note..."
value={input}
onChangeText={setInput}
style={styles.input}
/>
<Button title="Show Alert" onPress={() => setAlertVisible(true)} style={styles.button} />
<Button title={loading ? "Loading..." : "Show Loading Overlay"} onPress={() => setLoading(true)} style={styles.button} />
<Spinner style={styles.spinner} />
{/* Modals */}
<Modal visible={modalVisible} onRequestClose={() => setModalVisible(false)}>
<Text style={styles.modalTitle}>Critical Case Details</Text>
<Text>Patient: John Doe, 45M</Text>
<Button title="Close" onPress={() => setModalVisible(false)} style={styles.button} />
</Modal>
<ConfirmModal
visible={confirmVisible}
title="Confirm Action"
message="Are you sure you want to proceed?"
onConfirm={() => { setConfirmVisible(false); }}
onCancel={() => setConfirmVisible(false)}
/>
<AlertModal
visible={alertVisible}
title="Critical Alert"
message="Acute Subdural Hemorrhage detected!"
iconName="alert-circle"
iconColor={Colors.error}
onDismiss={() => setAlertVisible(false)}
/>
<LoadingOverlay visible={loading} />
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
},
content: {
padding: Spacing.lg,
},
header: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.title,
color: Colors.primary,
marginBottom: Spacing.lg,
textAlign: 'center',
},
search: {
marginBottom: Spacing.md,
},
infoTitle: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.textPrimary,
marginBottom: Spacing.xs,
},
infoText: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
},
active: {
color: Colors.success,
fontWeight: 'bold',
},
cardTitle: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
marginBottom: Spacing.sm,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: Spacing.sm,
},
criticalText: {
color: Colors.error,
flex: 1,
marginLeft: Spacing.sm,
fontFamily: Typography.fontFamily.regular,
},
routineText: {
color: Colors.success,
flex: 1,
marginLeft: Spacing.sm,
fontFamily: Typography.fontFamily.regular,
},
button: {
marginLeft: Spacing.sm,
},
input: {
marginVertical: Spacing.md,
},
spinner: {
marginVertical: Spacing.md,
},
modalTitle: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
marginBottom: Spacing.md,
textAlign: 'center',
},
});
export default DashboardScreen;
/*
* End of File: DashboardScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Barrel export for Dashboard screens
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as DashboardScreen } from './DashboardScreen';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,27 @@
/*
* File: caseAPI.ts
* Description: API service for fetching cases using apisauce
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
const api = create({
baseURL: 'https://api.example.com', // TODO: Replace with actual endpoint
timeout: 10000,
});
/**
* getCases - fetches the list of cases from the server
*/
export const caseAPI = {
getCases: () => api.get('/cases'),
// Add more endpoints as needed
};
/*
* End of File: caseAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,16 @@
/*
* File: index.ts
* Description: Barrel export for Dashboard services
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './caseAPI';
export * from './websocketService';
export * from './notificationService';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,20 @@
/*
* File: notificationService.ts
* Description: Service for sending notifications (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// Stub for notification service
export const notificationService = {
sendNotification: (title: string, message: string) => {
// TODO: Implement notification logic
// Example: PushNotification.localNotification({ title, message })
},
};
/*
* End of File: notificationService.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,25 @@
/*
* File: websocketService.ts
* Description: Service for real-time websocket updates (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// Stub for websocket connection
class WebsocketService {
subscribe(callback: (data: any) => void) {
// TODO: Implement websocket subscription
// Example: ws.onmessage = (event) => callback(event.data)
}
unsubscribe() {
// TODO: Implement unsubscribe logic
}
}
export const websocketService = new WebsocketService();
/*
* End of File: websocketService.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,25 @@
/*
* File: ProfileHeader.test.tsx
* Description: Test for ProfileHeader component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { render } from '@testing-library/react-native';
import ProfileHeader from '../components/ProfileHeader';
describe('ProfileHeader', () => {
it('renders avatar and name', () => {
const { getByText } = render(
<ProfileHeader user={{ name: 'Dr. Smith', avatar: 'https://example.com/avatar.png' }} />
);
expect(getByText('Dr. Smith')).toBeTruthy();
});
});
/*
* End of File: ProfileHeader.test.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,26 @@
/*
* File: profileAPI.test.ts
* Description: Test for profileAPI service
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { profileAPI } from '../services/profileAPI';
jest.mock('apisauce', () => ({
create: () => ({ get: jest.fn(() => Promise.resolve({ ok: true, data: { id: '1', name: 'Dr. Smith', email: 'dr@hospital.com' } })) })
}));
describe('profileAPI', () => {
it('calls getProfile and returns data', async () => {
const response = await profileAPI.getProfile('1');
expect(response.ok).toBe(true);
expect(response.data).toHaveProperty('email', 'dr@hospital.com');
});
});
/*
* End of File: profileAPI.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,37 @@
/*
* File: profileSlice.test.ts
* Description: Test for profileSlice reducer
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import reducer, { fetchProfileSuccess, fetchProfileFailure, UserProfile } from '../redux/profileSlice';
const initialState = {
userProfile: null,
loading: false,
error: null,
};
describe('profileSlice', () => {
it('handles fetchProfileSuccess', () => {
const user: UserProfile = { id: '1', name: 'Dr. Smith', email: 'dr@hospital.com' };
const state = reducer(initialState, fetchProfileSuccess(user));
expect(state.userProfile).toEqual(user);
expect(state.loading).toBe(false);
expect(state.error).toBeNull();
});
it('handles fetchProfileFailure', () => {
const error = 'Failed to fetch';
const state = reducer(initialState, fetchProfileFailure(error));
expect(state.loading).toBe(false);
expect(state.error).toBe(error);
});
});
/*
* End of File: profileSlice.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,55 @@
/*
* File: PreferencesForm.tsx
* Description: Component for user preferences form (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface PreferencesFormProps {
preferences: any;
onChange: (changes: any) => void;
}
/**
* PreferencesForm - displays user preferences form (stub)
*/
const PreferencesForm: React.FC<PreferencesFormProps> = ({ preferences, onChange }) => (
<View style={styles.container}>
<Text style={styles.title}>Preferences</Text>
<Text style={styles.stub}>[Preferences Form Fields Here]</Text>
</View>
);
const styles = StyleSheet.create({
container: {
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.md,
textAlign: 'center',
},
stub: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
textAlign: 'center',
marginTop: Spacing.lg,
},
});
export default PreferencesForm;
/*
* End of File: PreferencesForm.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,50 @@
/*
* File: ProfileHeader.tsx
* Description: Component for displaying user avatar and name
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface ProfileHeaderProps {
user: { name: string; avatar?: string };
}
/**
* ProfileHeader - displays user avatar and name
*/
const ProfileHeader: React.FC<ProfileHeaderProps> = ({ user }) => (
<View style={styles.container}>
{user.avatar && <Image source={{ uri: user.avatar }} style={styles.avatar} />}
<Text style={styles.name}>{user.name}</Text>
</View>
);
const styles = StyleSheet.create({
container: {
alignItems: 'center',
marginBottom: Spacing.md,
},
avatar: {
width: 64,
height: 64,
borderRadius: 32,
marginBottom: Spacing.sm,
},
name: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
},
});
export default ProfileHeader;
/*
* End of File: ProfileHeader.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,55 @@
/*
* File: SecuritySettings.tsx
* Description: Component for user security settings (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface SecuritySettingsProps {
security: any;
onChange: (changes: any) => void;
}
/**
* SecuritySettings - displays user security settings (stub)
*/
const SecuritySettings: React.FC<SecuritySettingsProps> = ({ security, onChange }) => (
<View style={styles.container}>
<Text style={styles.title}>Security Settings</Text>
<Text style={styles.stub}>[Security Options Here]</Text>
</View>
);
const styles = StyleSheet.create({
container: {
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.md,
textAlign: 'center',
},
stub: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
textAlign: 'center',
marginTop: Spacing.lg,
},
});
export default SecuritySettings;
/*
* End of File: SecuritySettings.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,75 @@
/*
* File: SettingsPanel.tsx
* Description: Component for displaying and toggling user settings (stub)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { View, Text, StyleSheet, Switch } from 'react-native';
import { Colors, Spacing, Typography } from 'shared/src/theme';
interface SettingsPanelProps {
settings: { notificationsEnabled: boolean; darkMode: boolean; language: string };
onChange: (changes: Partial<SettingsPanelProps['settings']>) => void;
}
/**
* SettingsPanel - displays and toggles user settings (stub)
*/
const SettingsPanel: React.FC<SettingsPanelProps> = ({ settings, onChange }) => (
<View style={styles.container}>
<Text style={styles.title}>Settings</Text>
<View style={styles.row}>
<Text style={styles.label}>Notifications</Text>
<Switch value={settings.notificationsEnabled} onValueChange={v => onChange({ notificationsEnabled: v })} />
</View>
<View style={styles.row}>
<Text style={styles.label}>Dark Mode</Text>
<Switch value={settings.darkMode} onValueChange={v => onChange({ darkMode: v })} />
</View>
<View style={styles.row}>
<Text style={styles.label}>Language</Text>
<Text style={styles.value}>{settings.language}</Text>
</View>
</View>
);
const styles = StyleSheet.create({
container: {
padding: Spacing.md,
backgroundColor: Colors.backgroundAlt,
borderRadius: 8,
},
title: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
marginBottom: Spacing.md,
textAlign: 'center',
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: Spacing.md,
},
label: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
},
value: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
},
});
export default SettingsPanel;
/*
* End of File: SettingsPanel.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,17 @@
/*
* File: index.ts
* Description: Barrel export for Profile components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as ProfileHeader } from './ProfileHeader';
export { default as SettingsPanel } from './SettingsPanel';
export { default as PreferencesForm } from './PreferencesForm';
export { default as SecuritySettings } from './SecuritySettings';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,15 @@
/*
* File: index.ts
* Description: Barrel export for Profile hooks
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './useProfile';
export * from './useSettings';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,22 @@
/*
* File: useProfile.ts
* Description: Custom hook for accessing user profile from redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useSelector } from 'react-redux';
import { selectUserProfile } from '../redux/profileSelectors';
/**
* useProfile - returns the current user profile from redux state
*/
export const useProfile = () => {
return useSelector(selectUserProfile);
};
/*
* End of File: useProfile.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,22 @@
/*
* File: useSettings.ts
* Description: Custom hook for accessing user settings from redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useSelector } from 'react-redux';
import { selectUserSettings } from '../redux/profileSelectors';
/**
* useSettings - returns the current user settings from redux state
*/
export const useSettings = () => {
return useSelector(selectUserSettings);
};
/*
* End of File: useSettings.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,57 @@
/*
* File: ProfileNavigator.tsx
* Description: Stack navigator for Profile module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { ProfileScreen, SettingsScreen, PreferencesScreen } from '../screens';
import { Colors } from 'shared/src/theme';
export type ProfileStackParamList = {
Profile: undefined;
Settings: undefined;
Preferences: undefined;
};
const Stack = createStackNavigator<ProfileStackParamList>();
/**
* ProfileNavigator sets up stack navigation for Profile module
*/
const ProfileNavigator: React.FC = () => (
<Stack.Navigator
initialRouteName="Profile"
screenOptions={{
headerStyle: { backgroundColor: Colors.primary },
headerTintColor: Colors.background,
headerTitleStyle: { fontWeight: 'bold' },
}}
>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={{ title: 'Profile' }}
/>
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={{ title: 'Settings' }}
/>
<Stack.Screen
name="Preferences"
component={PreferencesScreen}
options={{ title: 'Preferences' }}
/>
</Stack.Navigator>
);
export default ProfileNavigator;
/*
* End of File: ProfileNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Barrel export for Profile navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as ProfileNavigator } from './ProfileNavigator';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,17 @@
/*
* File: index.ts
* Description: Barrel export for Profile redux
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as profileReducer } from './profileSlice';
export { default as settingsReducer } from './settingsSlice';
export * from './profileActions';
export * from './profileSelectors';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,38 @@
/*
* File: profileActions.ts
* Description: Async actions (thunks) for Profile state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createAsyncThunk } from '@reduxjs/toolkit';
import { fetchProfileStart, fetchProfileSuccess, fetchProfileFailure } from './profileSlice';
import { profileAPI } from '../services/profileAPI';
/**
* Thunk to fetch user profile from API
*/
export const fetchProfile = createAsyncThunk(
'profile/fetchProfile',
async (userId: string, { dispatch, rejectWithValue }) => {
try {
dispatch(fetchProfileStart());
const response = await profileAPI.getProfile(userId);
if (response.ok && response.data) {
dispatch(fetchProfileSuccess(response.data));
} else {
dispatch(fetchProfileFailure(response.problem || 'Unknown error'));
return rejectWithValue(response.problem || 'Unknown error');
}
} catch (error: any) {
dispatch(fetchProfileFailure(error.message));
return rejectWithValue(error.message);
}
}
);
/*
* End of File: profileActions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,19 @@
/*
* File: profileSelectors.ts
* Description: Selectors for Profile redux state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { RootState } from 'app/redux/rootReducer';
export const selectUserProfile = (state: RootState) => state.profile.userProfile;
export const selectProfileLoading = (state: RootState) => state.profile.loading;
export const selectProfileError = (state: RootState) => state.profile.error;
export const selectUserSettings = (state: RootState) => state.settings.settings;
/*
* End of File: profileSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,76 @@
/*
* File: profileSlice.ts
* Description: Redux slice for Profile state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// Sample user profile type
export interface UserProfile {
id: string;
name: string;
email: string;
avatar?: string;
role?: string;
}
// Profile state type
interface ProfileState {
userProfile: UserProfile | null;
loading: boolean;
error: string | null;
}
const initialState: ProfileState = {
userProfile: null,
loading: false,
error: null,
};
/**
* Profile slice for managing user profile state
*/
const profileSlice = createSlice({
name: 'profile',
initialState,
reducers: {
fetchProfileStart(state) {
state.loading = true;
state.error = null;
},
fetchProfileSuccess(state, action: PayloadAction<UserProfile>) {
state.userProfile = action.payload;
state.loading = false;
},
fetchProfileFailure(state, action: PayloadAction<string>) {
state.loading = false;
state.error = action.payload;
},
updateProfile(state, action: PayloadAction<Partial<UserProfile>>) {
if (state.userProfile) {
state.userProfile = { ...state.userProfile, ...action.payload };
}
},
clearProfile(state) {
state.userProfile = null;
},
},
});
export const {
fetchProfileStart,
fetchProfileSuccess,
fetchProfileFailure,
updateProfile,
clearProfile,
} = profileSlice.actions;
export default profileSlice.reducer;
/*
* End of File: profileSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,63 @@
/*
* File: settingsSlice.ts
* Description: Redux slice for user settings state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface UserSettings {
notificationsEnabled: boolean;
darkMode: boolean;
language: string;
}
interface SettingsState {
settings: UserSettings;
loading: boolean;
error: string | null;
}
const initialState: SettingsState = {
settings: {
notificationsEnabled: true,
darkMode: false,
language: 'en',
},
loading: false,
error: null,
};
/**
* settingsSlice for managing user settings
*/
const settingsSlice = createSlice({
name: 'settings',
initialState,
reducers: {
updateSettings(state, action: PayloadAction<Partial<UserSettings>>) {
state.settings = { ...state.settings, ...action.payload };
},
setSettingsLoading(state, action: PayloadAction<boolean>) {
state.loading = action.payload;
},
setSettingsError(state, action: PayloadAction<string | null>) {
state.error = action.payload;
},
},
});
export const {
updateSettings,
setSettingsLoading,
setSettingsError,
} = settingsSlice.actions;
export default settingsSlice.reducer;
/*
* End of File: settingsSlice.ts
* 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