Compare commits

..

No commits in common. "f875f62383b11f1a9824c9c3ec1b02a6a17adce8" and "d0fe2eaa3428a60068d2a988aad776eb81b1d790" have entirely different histories.

63 changed files with 560 additions and 3921 deletions

View File

@ -7,7 +7,7 @@ alwaysApply: true
### 1. Root Level Organization ### 1. Root Level Organization
``` ```
NeoScan_Physician/ NeoScan_Radiologist/
├── app/ # Main application code ├── app/ # Main application code
├── docs/ # Documentation ├── docs/ # Documentation
├── android/ # Android native code ├── android/ # Android native code

View File

@ -10,7 +10,7 @@
## 📁 Complete Directory Structure ## 📁 Complete Directory Structure
``` ```
NeoScan_Physician/ NeoScan_Radiologist/
├── app/ # Main application code ├── app/ # Main application code
│ ├── modules/ # Feature-based modules │ ├── modules/ # Feature-based modules
│ │ ├── Auth/ # Authentication module │ │ ├── Auth/ # Authentication module
@ -150,7 +150,7 @@ NeoScan_Physician/
│ │ ├── AndroidManifest.xml # Main manifest │ │ ├── AndroidManifest.xml # Main manifest
│ │ ├── java/ # Java source │ │ ├── java/ # Java source
│ │ │ └── com/ # Package structure │ │ │ └── com/ # Package structure
│ │ │ └── neoscan_physician/ │ │ │ └── neoscan_radiologist/
│ │ │ ├── MainActivity.kt # Main activity │ │ │ ├── MainActivity.kt # Main activity
│ │ │ └── MainApplication.kt # Application class │ │ │ └── MainApplication.kt # Application class
│ │ └── res/ # Resources │ │ └── res/ # Resources
@ -166,7 +166,7 @@ NeoScan_Physician/
│ ├── gradlew.bat # Windows gradle wrapper │ ├── gradlew.bat # Windows gradle wrapper
│ └── settings.gradle # Gradle settings │ └── settings.gradle # Gradle settings
├── ios/ # iOS native code ├── ios/ # iOS native code
│ ├── NeoScan_Physician/ # iOS app │ ├── NeoScan_Radiologist/ # iOS app
│ │ ├── AppDelegate.swift # App delegate │ │ ├── AppDelegate.swift # App delegate
│ │ ├── Images.xcassets/ # Image assets │ │ ├── Images.xcassets/ # Image assets
│ │ │ ├── AppIcon.appiconset/ # App icons │ │ │ ├── AppIcon.appiconset/ # App icons
@ -174,11 +174,11 @@ NeoScan_Physician/
│ │ ├── Info.plist # App info │ │ ├── Info.plist # App info
│ │ ├── LaunchScreen.storyboard # Launch screen │ │ ├── LaunchScreen.storyboard # Launch screen
│ │ └── PrivacyInfo.xcprivacy # Privacy info │ │ └── PrivacyInfo.xcprivacy # Privacy info
│ ├── NeoScan_Physician.xcodeproj/ # Xcode project │ ├── NeoScan_Radiologist.xcodeproj/ # Xcode project
│ │ ├── project.pbxproj # Project file │ │ ├── project.pbxproj # Project file
│ │ └── xcshareddata/ # Shared data │ │ └── xcshareddata/ # Shared data
│ │ └── xcschemes/ # Build schemes │ │ └── xcschemes/ # Build schemes
│ │ └── NeoScan_Physician.xcscheme │ │ └── NeoScan_Radiologist.xcscheme
│ └── Podfile # CocoaPods configuration │ └── Podfile # CocoaPods configuration
├── __tests__/ # Test files ├── __tests__/ # Test files
│ ├── App.test.tsx # App component tests │ ├── App.test.tsx # App component tests

View File

@ -42,7 +42,7 @@ A comprehensive React Native application designed for emergency department physi
### Project Structure ### Project Structure
``` ```
NeoScan_Physician/ NeoScan_Radiologist/
├── app/ # Main application code ├── app/ # Main application code
│ ├── modules/ # Feature-based modules │ ├── modules/ # Feature-based modules
│ │ ├── Auth/ # Authentication module │ │ ├── Auth/ # Authentication module
@ -120,7 +120,7 @@ NeoScan_Physician/
1. **Clone the repository** 1. **Clone the repository**
```bash ```bash
git clone <repository-url> git clone <repository-url>
cd NeoScan_Physician cd NeoScan_Radiologist
``` ```
2. **Install dependencies** 2. **Install dependencies**

View File

@ -79,7 +79,7 @@ android {
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion compileSdk rootProject.ext.compileSdkVersion
namespace "com.neoscan_physician" namespace "com.neoscan_radiologist"
splits { splits {
abi { abi {
enable true enable true
@ -88,7 +88,7 @@ android {
} }
} }
defaultConfig { defaultConfig {
applicationId "com.neoscan_physician" applicationId "com.neoscan_radiologist"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1

View File

@ -10,6 +10,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false" android:allowBackup="false"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View File

@ -1,4 +1,4 @@
package com.neoscan_physician package com.neoscan_radiologist
import com.facebook.react.ReactActivity import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate import com.facebook.react.ReactActivityDelegate
@ -11,7 +11,7 @@ class MainActivity : ReactActivity() {
* Returns the name of the main component registered from JavaScript. This is used to schedule * Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component. * rendering of the component.
*/ */
override fun getMainComponentName(): String = "NeoScan_Physician" override fun getMainComponentName(): String = "NeoScan_Radiologist"
/** /**
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]

View File

@ -1,4 +1,4 @@
package com.neoscan_physician package com.neoscan_radiologist
import android.app.Application import android.app.Application
import com.facebook.react.PackageList import com.facebook.react.PackageList

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

After

Width:  |  Height:  |  Size: 227 KiB

View File

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Physician</string> <string name="app_name">Radiologist</string>
</resources> </resources>

View File

@ -4,7 +4,7 @@
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item> <item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style> </style>
</resources> </resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@ -1,6 +1,6 @@
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") } plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
rootProject.name = 'NeoScan_Physician' rootProject.name = 'NeoScan_Radiologist'
include ':app' include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin') includeBuild('../node_modules/@react-native/gradle-plugin')

View File

@ -1,4 +1,4 @@
{ {
"name": "NeoScan_Physician", "name": "NeoScan_Radiologist",
"displayName": "NeoScan_Physician" "displayName": "NeoScan_Radiologist"
} }

View File

@ -14,7 +14,6 @@ import {
RefreshControl, RefreshControl,
TouchableOpacity, TouchableOpacity,
Alert, Alert,
StatusBar,
SafeAreaView, SafeAreaView,
} from 'react-native'; } from 'react-native';
import Icon from 'react-native-vector-icons/Feather'; import Icon from 'react-native-vector-icons/Feather';
@ -552,7 +551,7 @@ const AIPredictionsScreen: React.FC<AIPredictionsScreenProps> = ({ navigation })
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.headerTitle}>AI Predictions</Text> <Text style={styles.headerTitle}>AI Predictions</Text>

View File

@ -18,7 +18,7 @@ export const login = createAsyncThunk(
async (credentials: { email: string; password: string }, { rejectWithValue }) => { async (credentials: { email: string; password: string }, { rejectWithValue }) => {
try { try {
const response:any = await authAPI.login(credentials.email, credentials.password,'web'); const response:any = await authAPI.login(credentials.email, credentials.password,'web');
console.log('response',response)
if(response.data.message && !response.data.success){ if(response.data.message && !response.data.success){
showError(response.data.message) showError(response.data.message)
return rejectWithValue(response.data.message); return rejectWithValue(response.data.message);
@ -30,7 +30,7 @@ export const login = createAsyncThunk(
if (response.ok && response.data && response.data.data) { if (response.ok && response.data && response.data.data) {
// Return the user data for the fulfilled case // Return the user data for the fulfilled case
if(response.data.data.user.dashboard_role !=='er_physician'){ if(response.data.data.user.dashboard_role !=='radiologist'){
showWarning('You are not authorized to access this application') showWarning('You are not authorized to access this application')
return rejectWithValue('Not Authorized'); return rejectWithValue('Not Authorized');
} }

View File

@ -138,7 +138,7 @@ const LoginScreen: React.FC<LoginScreenProps> = ({ navigation }) => {
* HEADER SECTION - App branding and title * HEADER SECTION - App branding and title
* ======================================================================== */} * ======================================================================== */}
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.title}>Physician</Text> <Text style={styles.title}>Radiologist</Text>
{/* <Text style={styles.subtitle}>Emergency Department Access</Text> */} {/* <Text style={styles.subtitle}>Emergency Department Access</Text> */}
</View> </View>
<View style={styles.imageContainer}> <View style={styles.imageContainer}>

View File

@ -8,7 +8,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
View, View,
StyleSheet, StyleSheet,
Alert, Alert,
Text, Text,
TouchableOpacity, TouchableOpacity,
@ -225,7 +225,7 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
setIsLoading(true); setIsLoading(true);
try { try {
let role = 'er_physician'; let role = 'radiologist';
// Prepare form data with proper file handling // Prepare form data with proper file handling
const formFields = { const formFields = {

View File

@ -1,469 +0,0 @@
/*
* File: PredictionCard.tsx
* Description: Prediction card component for displaying AI prediction data and patient information
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Image,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme/theme';
import type { PredictionData } from '../types/predictions';
// ============================================================================
// INTERFACES
// ============================================================================
interface PredictionCardProps {
prediction: PredictionData;
onPress: () => void;
}
// ============================================================================
// PREDICTION CARD COMPONENT
// ============================================================================
/**
* PredictionCard Component
*
* Purpose: Display AI prediction data with patient information in a card format
*
* Features:
* - Patient basic information
* - AI prediction results
* - Confidence scores
* - Clinical urgency
* - Feedback information
* - Modern card design
*/
export const PredictionCard: React.FC<PredictionCardProps> = ({
prediction,
onPress,
}) => {
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Get Urgency Color Configuration
*
* Purpose: Get color and icon based on clinical urgency
*/
const getUrgencyConfig = (urgency: string) => {
switch (urgency.toLowerCase()) {
case 'critical':
return {
color: theme.colors.error,
icon: 'alert-triangle',
bgColor: '#FFEBEE'
};
case 'urgent':
return {
color: theme.colors.warning,
icon: 'clock',
bgColor: '#FFF3E0'
};
case 'non-urgent':
return {
color: theme.colors.success,
icon: 'check-circle',
bgColor: '#E8F5E8'
};
default:
return {
color: theme.colors.primary,
icon: 'info',
bgColor: '#E3F2FD'
};
}
};
/**
* Get Confidence Color
*
* Purpose: Get color based on confidence score
*/
const getConfidenceColor = (confidence: number) => {
if (confidence >= 0.8) return theme.colors.success;
if (confidence >= 0.6) return theme.colors.warning;
return theme.colors.error;
};
/**
* Format Date
*
* Purpose: Format processed date for display
*/
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
});
};
// ============================================================================
// RENDER
// ============================================================================
const urgencyConfig = getUrgencyConfig(prediction.prediction.clinical_urgency);
const confidenceColor = getConfidenceColor(prediction.prediction.confidence_score);
return (
<TouchableOpacity
style={styles.container}
onPress={onPress}
activeOpacity={0.7}
>
{/* Header Section */}
<View style={styles.header}>
<View style={styles.patientInfo}>
<Text style={styles.patientName} numberOfLines={1}>
{prediction.patientdetails.Name || 'Unknown Patient'}
</Text>
<Text style={styles.patientDetails} numberOfLines={1} ellipsizeMode="head">
{prediction.patientdetails.PatID}
</Text>
<Text style={styles.patientDetailsSecondary}>
{prediction.patientdetails.PatAge} {prediction.patientdetails.PatSex}
</Text>
</View>
{/* Urgency Badge */}
<View style={[styles.urgencyBadge, { backgroundColor: urgencyConfig.bgColor }]}>
<Icon name={urgencyConfig.icon} size={16} color={urgencyConfig.color} />
<Text style={[styles.urgencyText, { color: urgencyConfig.color }]}>
{prediction.prediction.clinical_urgency}
</Text>
</View>
</View>
{/* Prediction Results Section */}
<View style={styles.predictionSection}>
<View style={styles.predictionHeader}>
<Icon name="activity" size={20} color={theme.colors.primary} />
<Text style={styles.predictionTitle}>AI Prediction</Text>
</View>
<View style={styles.predictionDetails}>
<View style={styles.predictionRow}>
<Text style={styles.predictionLabel}>Finding:</Text>
<Text style={styles.predictionValue}>
{prediction.prediction.label}
</Text>
</View>
<View style={styles.predictionRow}>
<Text style={styles.predictionLabel}>Type:</Text>
<Text style={styles.predictionValue}>
{prediction.prediction.finding_type}
</Text>
</View>
<View style={styles.predictionRow}>
<Text style={styles.predictionLabel}>Confidence:</Text>
<View style={styles.confidenceContainer}>
<Text style={[styles.confidenceValue, { color: confidenceColor }]}>
{(prediction.prediction.confidence_score * 100).toFixed(1)}%
</Text>
<View style={[styles.confidenceBar, { backgroundColor: confidenceColor }]} />
</View>
</View>
</View>
</View>
{/* Medical Information Section */}
<View style={styles.medicalSection}>
<View style={styles.medicalRow}>
<View style={styles.medicalItem}>
<Icon name="home" size={14} color={theme.colors.textSecondary} />
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
{prediction.patientdetails.InstName || 'Unknown Institution'}
</Text>
</View>
<View style={styles.medicalItem}>
<Icon name="activity" size={14} color={theme.colors.textSecondary} />
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
{prediction.patientdetails.Modality || 'Unknown Modality'}
</Text>
</View>
</View>
<View style={styles.medicalRow}>
<View style={styles.medicalItem}>
<Icon name="calendar" size={14} color={theme.colors.textSecondary} />
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
{formatDate(prediction.processed_at)}
</Text>
</View>
<View style={styles.medicalItem}>
<Icon name="layers" size={14} color={theme.colors.textSecondary} />
<Text style={styles.medicalText} numberOfLines={1} ellipsizeMode="tail">
{prediction.prediction.processing_info.frame_count} Frames
</Text>
</View>
</View>
</View>
{/* Feedback Section */}
{prediction.has_provided_feedback && (
<View style={styles.feedbackSection}>
<View style={styles.feedbackHeader}>
<Icon name="message-circle" size={16} color={theme.colors.success} />
<Text style={styles.feedbackTitle}>Feedback Available</Text>
</View>
<View style={styles.feedbackDetails}>
<Text style={styles.feedbackCount}>
{prediction.user_feedback_count} feedback(s)
</Text>
<Text style={styles.feedbackType}>
Latest: {prediction.latest_feedback_type}
</Text>
</View>
</View>
)}
{/* Footer */}
<View style={styles.footer}>
<Text style={styles.processedText}>
Processed: {formatDate(prediction.processed_at)}
</Text>
<View style={styles.actionButton}>
<Icon name="chevron-right" size={16} color={theme.colors.primary} />
</View>
</View>
</TouchableOpacity>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.md,
marginBottom: theme.spacing.md,
// Custom shadow properties to ensure visibility
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.md,
},
patientInfo: {
flex: 1,
marginRight: theme.spacing.sm,
},
patientName: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
patientDetails: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
patientDetailsSecondary: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
urgencyBadge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
gap: theme.spacing.xs,
},
urgencyText: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium,
textTransform: 'capitalize',
},
predictionSection: {
marginBottom: theme.spacing.md,
},
predictionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.sm,
gap: theme.spacing.sm,
},
predictionTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
},
predictionDetails: {
gap: theme.spacing.xs,
},
predictionRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
predictionLabel: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary,
},
predictionValue: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
textTransform: 'capitalize',
},
confidenceContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
confidenceValue: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.bold,
},
confidenceBar: {
width: 20,
height: 4,
borderRadius: 2,
},
medicalSection: {
marginBottom: theme.spacing.md,
paddingTop: theme.spacing.md,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
medicalRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: theme.spacing.sm,
},
medicalItem: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.md,
flex: 1,
maxWidth: '48%', // Prevent items from taking too much space
},
medicalText: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
flex: 1, // Allow text to take remaining space
height: 20, // Uniform height for all medical text
lineHeight: 20, // Ensure text is vertically centered
},
feedbackSection: {
marginBottom: theme.spacing.md,
paddingTop: theme.spacing.md,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
feedbackHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.xs,
gap: theme.spacing.xs,
},
feedbackTitle: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.success,
},
feedbackDetails: {
flexDirection: 'row',
justifyContent: 'space-between',
},
feedbackCount: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
feedbackType: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
textTransform: 'capitalize',
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: theme.spacing.md,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
processedText: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textMuted,
},
actionButton: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: theme.colors.backgroundAlt,
justifyContent: 'center',
alignItems: 'center',
},
});
/*
* End of File: PredictionCard.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,404 +0,0 @@
/*
* File: PredictionsList.tsx
* Description: Predictions list component with tabbed interface for feedback status
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useMemo, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
FlatList,
RefreshControl,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme/theme';
import { PredictionCard } from './PredictionCard';
import { usePredictions } from '../hooks/usePredictions';
import type { PredictionData } from '../types/predictions';
// ============================================================================
// INTERFACES
// ============================================================================
interface PredictionsListProps {
onPredictionPress: (prediction: PredictionData) => void;
}
// ============================================================================
// PREDICTIONS LIST COMPONENT
// ============================================================================
/**
* PredictionsList Component
*
* Purpose: Display AI predictions organized by radiologist feedback status
*
* Features:
* - Two tabs: Radiologist Reviewed and Pending Review
* - Search functionality
* - Pull-to-refresh
* - Loading states
* - Error handling
* - Empty states
*
* Performance Optimizations:
* - Both datasets (with/without feedback) are pre-loaded upfront
* - Tab switching is instant - no filtering needed
* - React.memo prevents unnecessary re-renders
* - useCallback optimizes event handlers
*/
export const PredictionsList: React.FC<PredictionsListProps> = React.memo(({
onPredictionPress,
}) => {
const {
activeTab,
currentPredictions,
currentLoadingState,
currentError,
switchTab,
refreshPredictions,
} = usePredictions();
// Performance optimization: Memoize feedback count calculation
const feedbackCount = useMemo(() => {
if (activeTab === 'with-feedback') {
return currentPredictions.filter(p => p.feedbacks?.length > 0).length;
}
return 0;
}, [activeTab, currentPredictions]);
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Handle Tab Switch
*
* Purpose: Switch between radiologist feedback status tabs
*/
const handleTabSwitch = useCallback((tab: 'with-feedback' | 'without-feedback') => {
switchTab(tab);
}, [switchTab]);
/**
* Handle Prediction Press
*
* Purpose: Handle when a prediction card is pressed
*/
const handlePredictionPress = useCallback((prediction: PredictionData) => {
onPredictionPress(prediction);
}, [onPredictionPress]);
/**
* Render Tab Button
*
* Purpose: Render individual radiologist feedback status tab button
*/
const renderTabButton = (tab: 'with-feedback' | 'without-feedback', label: string, icon: string) => {
const isActive = activeTab === tab;
return (
<TouchableOpacity
style={[styles.tabButton, isActive && styles.activeTabButton]}
onPress={() => handleTabSwitch(tab)}
activeOpacity={0.7}
>
<Icon
name={icon}
size={20}
color={isActive ? theme.colors.primary : theme.colors.textSecondary}
/>
<Text style={[styles.tabButtonText, isActive && styles.activeTabButtonText]}>
{label}
</Text>
</TouchableOpacity>
);
};
/**
* Render Empty State
*
* Purpose: Render empty state when no predictions available
*/
const renderEmptyState = () => (
<View style={styles.emptyState}>
<Icon name="inbox" size={48} color={theme.colors.textMuted} />
<Text style={styles.emptyStateTitle}>No Predictions Found</Text>
<Text style={styles.emptyStateSubtitle}>
{activeTab === 'with-feedback'
? 'No predictions have been reviewed by radiologists yet.'
: 'No predictions are waiting for radiologist review at the moment.'
}
</Text>
</View>
);
/**
* Render Error State
*
* Purpose: Render error state when API call fails
*/
const renderErrorState = () => (
<View style={styles.errorState}>
<Icon name="alert-circle" size={48} color={theme.colors.error} />
<Text style={styles.errorStateTitle}>Something went wrong</Text>
<Text style={styles.errorStateSubtitle}>
{currentError || 'Failed to load predictions. Please try again.'}
</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={refreshPredictions}
activeOpacity={0.7}
>
<Icon name="refresh-cw" size={16} color={'white'} />
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
);
// ============================================================================
// RENDER
// ============================================================================
// Performance optimization: Only log when needed (development mode)
// Note: Tab switching is now instant due to pre-loaded datasets
if (__DEV__ && currentPredictions.length > 0) {
console.log('🔍 PredictionsList render debug:', {
activeTab,
count: currentPredictions.length,
performance: 'Instant tab switching - datasets pre-loaded'
});
}
return (
<View style={styles.container}>
{/* Tab Navigation */}
<View style={styles.tabContainer}>
{renderTabButton('with-feedback', 'Radiologist Reviewed', 'message-circle')}
{renderTabButton('without-feedback', 'Pending Review', 'message-square')}
</View>
{/* Content Area */}
<View style={styles.contentContainer}>
{currentError ? (
renderErrorState()
) : (
<>
{/* Fixed Header - Not part of scrolling */}
{currentPredictions.length > 0 && (
<View style={styles.listHeader}>
<Text style={styles.listHeaderTitle}>
{activeTab === 'with-feedback' ? 'Radiologist Reviewed Predictions' : 'Predictions Awaiting Review'}
</Text>
<Text style={styles.listHeaderSubtitle}>
{currentPredictions.length} prediction{currentPredictions.length !== 1 ? 's' : ''} found
{activeTab === 'with-feedback' && (
<Text style={styles.feedbackCount}>
{' • '}{feedbackCount} with feedback
</Text>
)}
</Text>
</View>
)}
{/* Horizontal Scrolling Predictions */}
<View style={styles.listWrapper}>
<FlatList
data={currentPredictions}
renderItem={({ item }) => (
<View style={styles.predictionCardWrapper}>
<PredictionCard
prediction={item}
onPress={() => handlePredictionPress(item)}
/>
</View>
)}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.listContainer}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
horizontal={true}
scrollEnabled={true}
refreshControl={
<RefreshControl
refreshing={currentLoadingState}
onRefresh={refreshPredictions}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
ListEmptyComponent={renderEmptyState}
/>
</View>
</>
)}
</View>
</View>
);
});
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
flex: 1,
},
tabContainer: {
flexDirection: 'row',
backgroundColor: theme.colors.background,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
tabButton: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: theme.spacing.md,
paddingHorizontal: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.xs,
},
activeTabButton: {
backgroundColor: theme.colors.tertiary,
},
tabButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary,
},
activeTabButtonText: {
color: theme.colors.primary,
},
contentContainer: {
flex: 1,
minHeight: 0,
},
listWrapper: {
// flex: 1,
minHeight: 0, // Prevent flex overflow
marginBottom: theme.spacing.md, // Add bottom margin for shadow visibility
},
listContainer: {
padding: 2,
paddingBottom: theme.spacing.xxl, // Increased bottom padding for shadow visibility
alignItems: 'flex-start',
},
listHeader: {
marginBottom: theme.spacing.md,
paddingBottom: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
width: '100%',
},
listHeaderTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
listHeaderSubtitle: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
feedbackCount: {
color: theme.colors.primary,
fontFamily: theme.typography.fontFamily.medium,
},
emptyState: {
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.xxl,
},
emptyStateTitle: {
fontSize: theme.typography.fontSize.displayMedium,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginTop: theme.spacing.md,
marginBottom: theme.spacing.sm,
textAlign: 'center',
},
emptyStateSubtitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
textAlign: 'center',
},
errorState: {
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.xxl,
},
errorStateTitle: {
fontSize: theme.typography.fontSize.displayMedium,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginTop: theme.spacing.md,
marginBottom: theme.spacing.sm,
textAlign: 'center',
},
errorStateSubtitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
textAlign: 'center',
marginBottom: theme.spacing.lg,
},
retryButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
},
retryButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.background,
},
predictionCardWrapper: {
marginRight: theme.spacing.md,
marginBottom: theme.spacing.md, // Add bottom margin for shadow visibility
width: 280, // Uniform width for all cards
// Height will be determined by content naturally
},
});
/*
* End of File: PredictionsList.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -4,6 +4,4 @@ export { DashboardHeader } from './DashboardHeader';
export { QuickActions } from './QuickActions'; export { QuickActions } from './QuickActions';
export { DepartmentStats } from './DepartmentStats'; export { DepartmentStats } from './DepartmentStats';
export { BrainPredictionsOverview } from './BrainPredictionsOverview'; export { BrainPredictionsOverview } from './BrainPredictionsOverview';
export { FeedbackAnalysisPieChart } from './FeedbackAnalysisPieChart'; export { FeedbackAnalysisPieChart } from './FeedbackAnalysisPieChart';
export { PredictionCard } from './PredictionCard';
export { PredictionsList } from './PredictionsList';

View File

@ -1,187 +0,0 @@
/*
* File: usePredictions.ts
* Description: Custom hook for managing AI predictions state and actions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useEffect, useCallback } from 'react';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import {
fetchAllPredictions,
setActiveTab,
setSearchQuery,
clearErrors,
clearSearch,
selectActiveTab,
selectSearchQuery,
selectPredictionsWithFeedback,
selectPredictionsWithoutFeedback,
selectIsLoading,
selectError,
selectCurrentPredictions,
selectCurrentLoadingState,
selectCurrentError,
} from '../redux/predictionsSlice';
import { selectUser } from '../../Auth/redux';
import type { PredictionTabType } from '../types/predictions';
// ============================================================================
// USE PREDICTIONS HOOK
// ============================================================================
/**
* usePredictions Hook
*
* Purpose: Manage AI predictions state and provide actions
*
* Features:
* - Fetch all predictions from single API call
* - Frontend filtering for feedback status
* - Manage active tab state
* - Handle search functionality
* - Provide loading and error states
* - Auto-fetch data when needed
*/
export const usePredictions = () => {
const dispatch = useAppDispatch();
// ============================================================================
// SELECTORS
// ============================================================================
const activeTab = useAppSelector(selectActiveTab);
const searchQuery = useAppSelector(selectSearchQuery);
// Performance optimization: Load both datasets upfront
const predictionsWithFeedback = useAppSelector(selectPredictionsWithFeedback);
const predictionsWithoutFeedback = useAppSelector(selectPredictionsWithoutFeedback);
const isLoading = useAppSelector(selectIsLoading);
const error = useAppSelector(selectError);
// Performance optimization: Direct dataset selection - no filtering needed
const currentPredictions = useAppSelector(selectCurrentPredictions);
const currentLoadingState = useAppSelector(selectCurrentLoadingState);
const currentError = useAppSelector(selectCurrentError);
// Get authentication token from auth store
const authToken = useAppSelector(selectUser)?.access_token;
// ============================================================================
// ACTIONS
// ============================================================================
/**
* Switch Active Tab
*
* Purpose: Change between feedback tabs
* Performance: Instant switching - no filtering needed as both datasets are pre-loaded
*/
const switchTab = useCallback((tab: PredictionTabType) => {
dispatch(setActiveTab(tab));
}, [dispatch]);
/**
* Update Search Query
*
* Purpose: Update search query for filtering predictions
*/
const updateSearchQuery = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Clear Search
*
* Purpose: Clear search query
*/
const clearSearchQuery = useCallback(() => {
dispatch(clearSearch());
}, [dispatch]);
/**
* Clear Errors
*
* Purpose: Clear error states
*/
const clearErrorStates = useCallback(() => {
dispatch(clearErrors());
}, [dispatch]);
/**
* Refresh Predictions
*
* Purpose: Refresh all predictions data
*/
const refreshPredictions = useCallback(() => {
if (!authToken) return;
dispatch(fetchAllPredictions(authToken));
}, [dispatch, authToken]);
/**
* Fetch All Predictions
*
* Purpose: Fetch all predictions from API
*/
const fetchPredictions = useCallback(() => {
if (!authToken) return;
dispatch(fetchAllPredictions(authToken));
}, [dispatch, authToken]);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Auto-fetch data when component mounts or token changes
* Performance optimization: Load both datasets upfront to eliminate tab switching delays
*/
useEffect(() => {
if (!authToken) return;
// Performance optimization: Always fetch if either dataset is empty
// This ensures both datasets are loaded upfront for instant tab switching
if (predictionsWithFeedback.length === 0 || predictionsWithoutFeedback.length === 0) {
dispatch(fetchAllPredictions(authToken));
}
}, [authToken, predictionsWithFeedback.length, predictionsWithoutFeedback.length, dispatch]);
// ============================================================================
// RETURN VALUES
// ============================================================================
return {
// State
activeTab,
searchQuery,
predictionsWithFeedback,
predictionsWithoutFeedback,
currentPredictions,
// Loading states
isLoading,
currentLoadingState,
// Error states
error,
currentError,
// Actions
switchTab,
updateSearchQuery,
clearSearchQuery,
clearErrorStates,
refreshPredictions,
fetchPredictions,
// Constants
authToken
};
};
/*
* End of File: usePredictions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -38,8 +38,6 @@ export { default as CriticalAlerts } from './components/CriticalAlerts';
export { default as DashboardHeader } from './components/DashboardHeader'; export { default as DashboardHeader } from './components/DashboardHeader';
export { default as QuickActions } from './components/QuickActions'; export { default as QuickActions } from './components/QuickActions';
export { default as DepartmentStats } from './components/DepartmentStats'; export { default as DepartmentStats } from './components/DepartmentStats';
export { PredictionCard } from './components/PredictionCard';
export { PredictionsList } from './components/PredictionsList';
// Export hooks // Export hooks
export * from './hooks'; export * from './hooks';
@ -86,30 +84,6 @@ export {
selectTimeAnalysis, selectTimeAnalysis,
} from './redux/aiDashboardSelectors'; } from './redux/aiDashboardSelectors';
// Export Predictions Redux
export {
fetchAllPredictions,
setActiveTab,
setSearchQuery,
clearErrors,
clearSearch,
filterPredictions,
} from './redux/predictionsSlice';
// Export Predictions Selectors
export {
selectActiveTab,
selectSearchQuery,
selectAllPredictions,
selectPredictionsWithFeedback,
selectPredictionsWithoutFeedback,
selectIsLoading,
selectError,
selectCurrentPredictions,
selectCurrentLoadingState,
selectCurrentError,
} from './redux/predictionsSlice';
export { export {
fetchAlerts, fetchAlerts,
acknowledgeAlert, acknowledgeAlert,

View File

@ -11,15 +11,9 @@ import { createStackNavigator } from '@react-navigation/stack';
// Import dashboard screens // Import dashboard screens
import { DashboardScreen } from '../screens/DashboardScreen'; import { DashboardScreen } from '../screens/DashboardScreen';
// Import PatientCare screens for dashboard integration
// import { PatientDetailsScreen } from '../../PatientCare/screens/PatientDetailsScreen';
// import { FeedbackDetailScreen } from '../../PatientCare/screens/FeedbackDetailScreen';
// Import navigation types // Import navigation types
import { DashboardStackParamList } from './navigationTypes'; import { DashboardStackParamList } from './navigationTypes';
import { theme } from '../../../theme'; import { theme } from '../../../theme';
import { PatientDetailsScreen } from '../../PatientCare';
import FeedbackDetailScreen from '../../PatientCare/screens/FeedbackDetailScreen';
// Create stack navigator for Dashboard module // Create stack navigator for Dashboard module
const Stack = createStackNavigator<DashboardStackParamList>(); const Stack = createStackNavigator<DashboardStackParamList>();
@ -84,50 +78,6 @@ const DashboardStackNavigator: React.FC = () => {
headerShown: false, // Hide header for main dashboard headerShown: false, // Hide header for main dashboard
}} }}
/> />
{/* Patient Details Screen - Accessible from dashboard */}
<Stack.Screen
name="PatientDetails"
component={PatientDetailsScreen}
options={{
title: 'Patient Details',
headerShown: true,
headerStyle: {
backgroundColor: '#FFFFFF',
elevation: 0,
shadowOpacity: 0,
},
headerTitleStyle: {
fontFamily: theme.typography.fontFamily.medium,
fontSize: 18,
color: '#212121',
},
headerTintColor: '#2196F3',
headerBackTitleVisible: false,
}}
/>
{/* Feedback Detail Screen - Accessible from dashboard */}
<Stack.Screen
name="FeedbackDetail"
component={FeedbackDetailScreen}
options={{
title: 'Feedback Details',
headerShown: true,
headerStyle: {
backgroundColor: '#FFFFFF',
elevation: 0,
shadowOpacity: 0,
},
headerTitleStyle: {
fontFamily: theme.typography.fontFamily.medium,
fontSize: 18,
color: '#212121',
},
headerTintColor: '#2196F3',
headerBackTitleVisible: false,
}}
/>
</Stack.Navigator> </Stack.Navigator>
); );
}; };

View File

@ -21,9 +21,6 @@ export type DashboardStackParamList = {
// Patient Details screen - Detailed patient information // Patient Details screen - Detailed patient information
PatientDetails: PatientDetailsScreenParams; PatientDetails: PatientDetailsScreenParams;
// Feedback Detail screen - Series feedback information
FeedbackDetail: FeedbackDetailScreenParams;
// Alert Details screen - Detailed alert information // Alert Details screen - Detailed alert information
AlertDetails: AlertDetailsScreenParams; AlertDetails: AlertDetailsScreenParams;
@ -91,30 +88,6 @@ export interface PatientDetailsScreenParams {
fromScreen?: keyof DashboardStackParamList; fromScreen?: keyof DashboardStackParamList;
} }
/**
* FeedbackDetailScreenParams
*
* Purpose: Parameters for the feedback detail screen
*
* Parameters:
* - patientId: Required patient ID
* - patientName: Required patient name
* - seriesNumber: Required series number
* - seriesData: Optional series data
* - patientData: Optional patient data
* - feedbackData: Optional feedback data
* - onFeedbackSubmitted: Optional callback for feedback submission
*/
export interface FeedbackDetailScreenParams {
patientId: string;
patientName: string;
seriesNumber: string;
seriesData?: any;
patientData?: any;
feedbackData?: any[];
onFeedbackSubmitted?: () => void;
}
/** /**
* AlertDetailsScreenParams * AlertDetailsScreenParams
* *
@ -176,11 +149,6 @@ export type DashboardScreenProps = DashboardScreenProps<'ERDashboard'>;
*/ */
export type PatientDetailsScreenProps = DashboardScreenProps<'PatientDetails'>; export type PatientDetailsScreenProps = DashboardScreenProps<'PatientDetails'>;
/**
* FeedbackDetailScreenProps - Props for FeedbackDetailScreen component
*/
export type FeedbackDetailScreenProps = DashboardScreenProps<'FeedbackDetail'>;
/** /**
* AlertDetailsScreenProps - Props for AlertDetailsScreen component * AlertDetailsScreenProps - Props for AlertDetailsScreen component
*/ */

View File

@ -1,249 +0,0 @@
/*
* File: predictionsSlice.ts
* Description: Redux slice for managing AI predictions state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { predictionsAPI } from '../services/predictionsAPI';
import type { PredictionsResponse, PredictionData, PredictionTabType } from '../types/predictions';
// ============================================================================
// ASYNC THUNKS
// ============================================================================
/**
* Fetch All Predictions Async Thunk
*
* Purpose: Fetch all predictions and handle filtering on frontend
*/
export const fetchAllPredictions = createAsyncThunk(
'predictions/fetchAll',
async (token: string, { rejectWithValue }) => {
try {
const response :any = await predictionsAPI.fetchAllPredictions(token);
console.log('dashboard predction data response', response);
if (response.ok && response.data && response.data.data) {
return response.data.data as PredictionData[];
} else {
return rejectWithValue(response.problem || 'Failed to fetch predictions');
}
} catch (error) {
return rejectWithValue('Network error occurred while fetching predictions');
}
}
);
// ============================================================================
// STATE INTERFACE
// ============================================================================
interface PredictionsState {
// Data
allPredictions: PredictionData[];
predictionsWithFeedback: PredictionData[];
predictionsWithoutFeedback: PredictionData[];
// Loading states
isLoading: boolean;
// Error states
error: string | null;
// UI state
activeTab: PredictionTabType;
searchQuery: string;
}
// ============================================================================
// INITIAL STATE
// ============================================================================
const initialState: PredictionsState = {
// Data
allPredictions: [],
predictionsWithFeedback: [],
predictionsWithoutFeedback: [],
// Loading states
isLoading: false,
// Error states
error: null,
// UI state
activeTab: 'with-feedback',
searchQuery: '',
};
// ============================================================================
// PREDICTIONS SLICE
// ============================================================================
const predictionsSlice = createSlice({
name: 'predictions',
initialState,
reducers: {
/**
* Set Active Tab
*
* Purpose: Switch between feedback tabs
*/
setActiveTab: (state, action: PayloadAction<PredictionTabType>) => {
state.activeTab = action.payload;
},
/**
* Set Search Query
*
* Purpose: Update search query for filtering
*/
setSearchQuery: (state, action: PayloadAction<string>) => {
state.searchQuery = action.payload;
},
/**
* Clear Errors
*
* Purpose: Clear error states
*/
clearErrors: (state) => {
state.error = null;
},
/**
* Clear Search
*
* Purpose: Clear search query
*/
clearSearch: (state) => {
state.searchQuery = '';
},
/**
* Filter Predictions
*
* Purpose: Filter predictions based on feedback status
*/
filterPredictions: (state) => {
// Filter predictions with feedback
state.predictionsWithFeedback = state.allPredictions.filter(
prediction => prediction.has_provided_feedback
);
// Filter predictions without feedback
state.predictionsWithoutFeedback = state.allPredictions.filter(
prediction => !prediction.has_provided_feedback
);
},
},
extraReducers: (builder) => {
// ============================================================================
// FETCH ALL PREDICTIONS
// ============================================================================
// Pending
builder.addCase(fetchAllPredictions.pending, (state) => {
state.isLoading = true;
state.error = null;
});
// Fulfilled
builder.addCase(fetchAllPredictions.fulfilled, (state, action: PayloadAction<PredictionData[]>) => {
state.isLoading = false;
state.allPredictions = action.payload;
state.error = null;
// Debug logging to see what's happening with feedback filtering
console.log('🔍 Predictions filtering debug:');
console.log('Total predictions:', action.payload.length);
console.log('Predictions with feedback field:', action.payload.filter(p => p.has_provided_feedback).length);
console.log('Predictions with feedbacks array:', action.payload.filter(p => p.feedbacks && p.feedbacks.length > 0).length);
console.log('Sample prediction feedback data:', action.payload.slice(0, 2).map(p => ({
id: p.id,
has_provided_feedback: p.has_provided_feedback,
feedbacks_count: p.feedbacks?.length || 0,
user_feedback_count: p.user_feedback_count
})));
// Automatically filter predictions after fetching
// Primary filter: use has_provided_feedback field
// Fallback filter: check if feedbacks array has items
state.predictionsWithFeedback = action.payload.filter(
prediction => prediction.has_provided_feedback || (prediction.feedbacks && prediction.feedbacks.length > 0)
);
state.predictionsWithoutFeedback = action.payload.filter(
prediction => !prediction.has_provided_feedback && (!prediction.feedbacks || prediction.feedbacks.length === 0)
);
console.log('Filtered results:');
console.log('With feedback tab:', state.predictionsWithFeedback.length);
console.log('Without feedback tab:', state.predictionsWithoutFeedback.length);
});
// Rejected
builder.addCase(fetchAllPredictions.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message || 'Failed to fetch predictions';
});
},
});
// ============================================================================
// ACTIONS
// ============================================================================
export const {
setActiveTab,
setSearchQuery,
clearErrors,
clearSearch,
filterPredictions,
} = predictionsSlice.actions;
// ============================================================================
// SELECTORS
// ============================================================================
export const selectActiveTab = (state: { predictions: PredictionsState }) => state.predictions.activeTab;
export const selectSearchQuery = (state: { predictions: PredictionsState }) => state.predictions.searchQuery;
export const selectAllPredictions = (state: { predictions: PredictionsState }) => state.predictions.allPredictions;
export const selectPredictionsWithFeedback = (state: { predictions: PredictionsState }) => state.predictions.predictionsWithFeedback;
export const selectPredictionsWithoutFeedback = (state: { predictions: PredictionsState }) => state.predictions.predictionsWithoutFeedback;
export const selectIsLoading = (state: { predictions: PredictionsState }) => state.predictions.isLoading;
export const selectError = (state: { predictions: PredictionsState }) => state.predictions.error;
export const selectCurrentPredictions = (state: { predictions: PredictionsState }) => {
const { activeTab, predictionsWithFeedback, predictionsWithoutFeedback } = state.predictions;
// Performance optimization: Direct dataset selection instead of filtering
return activeTab === 'with-feedback' ? predictionsWithFeedback : predictionsWithoutFeedback;
};
// Performance optimization: Pre-computed selectors for instant tab switching
export const selectPredictionsForTab = (state: { predictions: PredictionsState }, tab: PredictionTabType) => {
const { predictionsWithFeedback, predictionsWithoutFeedback } = state.predictions;
return tab === 'with-feedback' ? predictionsWithFeedback : predictionsWithoutFeedback;
};
export const selectCurrentLoadingState = (state: { predictions: PredictionsState }) => {
return state.predictions.isLoading;
};
export const selectCurrentError = (state: { predictions: PredictionsState }) => {
return state.predictions.error;
};
// ============================================================================
// EXPORT
// ============================================================================
export default predictionsSlice.reducer;
/*
* End of File: predictionsSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -5,7 +5,7 @@
* Copyright (c) Spurrin Innovations. All rights reserved. * Copyright (c) Spurrin Innovations. All rights reserved.
*/ */
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
View, View,
Text, Text,
@ -15,22 +15,14 @@ import {
RefreshControl, RefreshControl,
FlatList, FlatList,
Dimensions, Dimensions,
Alert,
} from 'react-native'; } from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme/theme'; import { theme } from '../../../theme/theme';
import { DashboardHeader } from '../components/DashboardHeader'; import { DashboardHeader } from '../components/DashboardHeader';
import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview'; import { BrainPredictionsOverview } from '../components/BrainPredictionsOverview';
import { FeedbackAnalysisPieChart } from '../components/FeedbackAnalysisPieChart'; import { FeedbackAnalysisPieChart } from '../components/FeedbackAnalysisPieChart';
import { PredictionsList } from '../components/PredictionsList';
import { useAIDashboard } from '../hooks/useAIDashboard'; import { useAIDashboard } from '../hooks/useAIDashboard';
import { selectUserDisplayName, selectUserFirstName } from '../../Auth/redux/authSelectors'; import { selectUserDisplayName, selectUserFirstName } from '../../Auth/redux/authSelectors';
import { useAppSelector } from '../../../store/hooks'; import { useAppSelector } from '../../../store/hooks';
import { CompositeNavigationProp } from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { StackNavigationProp } from '@react-navigation/stack';
import { MainTabParamList } from '../../../navigation/navigationTypes';
import { PatientCareStackParamList } from '../../PatientCare/navigation/navigationTypes';
/** /**
* DashboardScreenProps Interface * DashboardScreenProps Interface
@ -38,15 +30,10 @@ import { PatientCareStackParamList } from '../../PatientCare/navigation/navigati
* Purpose: Defines the props required by the DashboardScreen component * Purpose: Defines the props required by the DashboardScreen component
* *
* Props: * Props:
* - navigation: Composite navigation object for tab and stack navigation * - navigation: React Navigation object for screen navigation
*/ */
type DashboardScreenNavigationProp = CompositeNavigationProp<
BottomTabNavigationProp<MainTabParamList, 'Dashboard'>,
StackNavigationProp<PatientCareStackParamList>
>;
interface DashboardScreenProps { interface DashboardScreenProps {
navigation: DashboardScreenNavigationProp; navigation: any;
} }
/** /**
@ -284,20 +271,11 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
* @param value - Main value to display * @param value - Main value to display
* @param subtitle - Optional subtitle * @param subtitle - Optional subtitle
* @param color - Optional color theme * @param color - Optional color theme
* @param iconName - Icon name for the stats card
* @returns Statistics card component * @returns Statistics card component
*/ */
const renderStatsCard = (title: string, value: string | number, subtitle?: string, color?: string, iconName?: string) => ( const renderStatsCard = (title: string, value: string | number, subtitle?: string, color?: string) => (
<View style={[styles.statsCard, color && { borderLeftColor: color, borderLeftWidth: 4 }]}> <View style={[styles.statsCard, color && { borderLeftColor: color, borderLeftWidth: 4 }]}>
{/* Icon and Title Row */} <Text style={styles.statsCardTitle}>{title}</Text>
<View style={styles.statsCardHeader}>
{iconName && (
<View style={[styles.statsCardIcon, { backgroundColor: color ? color + '20' : theme.colors.backgroundAccent }]}>
<Icon name={iconName} size={20} color={color || theme.colors.primary} />
</View>
)}
<Text style={styles.statsCardTitle}>{title}</Text>
</View>
<Text style={[styles.statsCardValue, color && { color }]}>{value}</Text> <Text style={[styles.statsCardValue, color && { color }]}>{value}</Text>
{subtitle && <Text style={styles.statsCardSubtitle}>{subtitle}</Text>} {subtitle && <Text style={styles.statsCardSubtitle}>{subtitle}</Text>}
</View> </View>
@ -308,12 +286,180 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
* *
* Purpose: Render confidence score breakdown section * Purpose: Render confidence score breakdown section
*/ */
const renderConfidenceBreakdown = () => {
// Check if dashboard data exists
if (!dashboardData) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
<View style={styles.emptyStateContainer}>
<Text style={styles.emptyStateText}>Dashboard data not available</Text>
<TouchableOpacity style={styles.retryButton} onPress={handleRefresh}>
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
</View>
);
}
// Check if confidence scores data exists
if (!dashboardData.data?.confidence_scores) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
<View style={styles.emptyStateContainer}>
<Text style={styles.emptyStateText}>Confidence data not available</Text>
<Text style={styles.emptyStateSubtext}>AI confidence scores are not currently accessible</Text>
<View style={styles.emptyStateInfo}>
<Text style={styles.emptyStateInfoText}> AI system may be initializing</Text>
<Text style={styles.emptyStateInfoText}> Check system status</Text>
<Text style={styles.emptyStateInfoText}> Refresh in a few minutes</Text>
</View>
</View>
</View>
);
}
const { high, medium, low } = dashboardData.data.confidence_scores;
// Check if the object is empty or if all values are undefined/null/zero
if (!high && !medium && !low) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
<View style={styles.emptyStateContainer}>
<Text style={styles.emptyStateText}>No data found</Text>
</View>
</View>
);
}
// Check if all required fields exist and are numbers
if (typeof high !== 'number' || typeof medium !== 'number' || typeof low !== 'number') {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
<View style={styles.emptyStateContainer}>
<Text style={styles.emptyStateText}>No confidence data available</Text>
</View>
</View>
);
}
const total = high + medium + low;
// If no predictions, show empty state
if (total === 0) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
<View style={styles.emptyStateContainer}>
<Text style={styles.emptyStateText}>No predictions available yet</Text>
<Text style={styles.emptyStateSubtext}>AI predictions will appear here once the system processes medical scans</Text>
</View>
</View>
);
}
// Calculate percentages for better visualization
const highPercent = Math.round((high / total) * 100);
const mediumPercent = Math.round((medium / total) * 100);
const lowPercent = Math.round((low / total) * 100);
// Helper function to get bar opacity
const getBarOpacity = (count: number) => {
if (count === 0) return 0.3; // Dimmed for zero values
return 0.9; // Full opacity for non-zero values
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Confidence Score Distribution</Text>
<View style={styles.confidenceContainer}>
{/* High Confidence */}
<View style={styles.confidenceItem}>
<View style={styles.confidenceHeader}>
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.success }]} />
<Text style={styles.confidenceLabel}>High Confidence</Text>
<Text style={styles.confidencePercentage}>{highPercent}%</Text>
</View>
<View style={styles.confidenceBarContainer}>
<View
style={[
styles.confidenceBar,
{
backgroundColor: theme.colors.success,
width: high === 0 ? 4 : `${Math.max(highPercent, 5)}%`,
opacity: getBarOpacity(high)
}
]}
/>
</View>
<Text style={styles.confidenceValue}>{high} predictions</Text>
</View>
{/* Medium Confidence */}
<View style={styles.confidenceItem}>
<View style={styles.confidenceHeader}>
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.warning }]} />
<Text style={styles.confidenceLabel}>Medium Confidence</Text>
<Text style={styles.confidencePercentage}>{mediumPercent}%</Text>
</View>
<View style={styles.confidenceBarContainer}>
<View
style={[
styles.confidenceBar,
{
backgroundColor: theme.colors.warning,
width: medium === 0 ? 4 : `${Math.max(mediumPercent, 5)}%`,
opacity: getBarOpacity(medium)
}
]}
/>
</View>
<Text style={styles.confidenceValue}>{medium} predictions</Text>
</View>
{/* Low Confidence */}
<View style={styles.confidenceItem}>
<View style={styles.confidenceHeader}>
<View style={[styles.confidenceIndicator, { backgroundColor: theme.colors.error }]} />
<Text style={styles.confidenceLabel}>Low Confidence</Text>
<Text style={styles.confidencePercentage}>{lowPercent}%</Text>
</View>
<View style={styles.confidenceBarContainer}>
<View
style={[
styles.confidenceBar,
{
backgroundColor: theme.colors.error,
width: low === 0 ? 4 : `${Math.max(lowPercent, 5)}%`,
opacity: getBarOpacity(low)
}
]}
/>
</View>
<Text style={styles.confidenceValue}>{low} predictions</Text>
</View>
</View>
{/* Summary Stats */}
<View style={styles.confidenceSummary}>
<Text style={styles.summaryText}>
Total Predictions: <Text style={styles.summaryValue}>{total}</Text>
</Text>
<Text style={styles.summaryText}>
High Confidence Rate: <Text style={[styles.summaryValue, { color: theme.colors.success }]}>{highPercent}%</Text>
</Text>
</View>
</View>
);
};
/** /**
* renderUrgencyBreakdown Function * renderUrgencyBreakdown Function
* *
* Purpose: Render urgency level breakdown section with animated colored circles * Purpose: Render urgency level breakdown section
*/ */
const renderUrgencyBreakdown = () => { const renderUrgencyBreakdown = () => {
if (!dashboardData?.data.urgency_levels) return null; if (!dashboardData?.data.urgency_levels) return null;
@ -347,45 +493,21 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
return ( return (
<View style={styles.section}> <View style={styles.section}>
<Text style={styles.sectionTitle}>Case Urgency Distribution</Text> <Text style={styles.sectionTitle}>Case Urgency Distribution</Text>
<View style={styles.urgencyContainer}> <View style={styles.urgencyContainer}>
{/* Critical Cases Circle */} <View style={styles.urgencyItem}>
<View style={styles.urgencyCircleItem}> <View style={[styles.urgencyIndicator, { backgroundColor: theme.colors.error }]} />
<View
style={[
styles.circleContainer,
{ borderColor: theme.colors.error }
]}
>
<Text style={styles.circleValue}>{critical}</Text>
</View>
<Text style={styles.urgencyLabel}>Critical</Text> <Text style={styles.urgencyLabel}>Critical</Text>
<Text style={styles.urgencyValue}>{critical}</Text>
</View> </View>
<View style={styles.urgencyItem}>
{/* Urgent Cases Circle */} <View style={[styles.urgencyIndicator, { backgroundColor: theme.colors.warning }]} />
<View style={styles.urgencyCircleItem}>
<View
style={[
styles.circleContainer,
{ borderColor: theme.colors.warning }
]}
>
<Text style={styles.circleValue}>{urgent}</Text>
</View>
<Text style={styles.urgencyLabel}>Urgent</Text> <Text style={styles.urgencyLabel}>Urgent</Text>
<Text style={styles.urgencyValue}>{urgent}</Text>
</View> </View>
<View style={styles.urgencyItem}>
{/* Routine Cases Circle */} <View style={[styles.urgencyIndicator, { backgroundColor: theme.colors.success }]} />
<View style={styles.urgencyCircleItem}>
<View
style={[
styles.circleContainer,
{ borderColor: theme.colors.success }
]}
>
<Text style={styles.circleValue}>{routine}</Text>
</View>
<Text style={styles.urgencyLabel}>Routine</Text> <Text style={styles.urgencyLabel}>Routine</Text>
<Text style={styles.urgencyValue}>{routine}</Text>
</View> </View>
</View> </View>
</View> </View>
@ -537,29 +659,25 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
'Total Predictions', 'Total Predictions',
dashboardData?.data.total_predictions || 0, dashboardData?.data.total_predictions || 0,
'AI analyses performed', 'AI analyses performed',
theme.colors.primary, theme.colors.primary
'activity'
)} )}
{renderStatsCard( {renderStatsCard(
'Total Patients', 'Total Patients',
dashboardData?.data.total_patients || 0, dashboardData?.data.total_patients || 0,
'Unique patients', 'Unique patients',
theme.colors.info, theme.colors.info
'users'
)} )}
{renderStatsCard( {renderStatsCard(
'Feedback Rate', 'Feedback Rate',
`${dashboardData?.data.feedback_rate_percentage || 0}%`, `${dashboardData?.data.feedback_rate_percentage || 0}%`,
'User feedback coverage', 'User feedback coverage',
theme.colors.success, theme.colors.success
'message-circle'
)} )}
{renderStatsCard( {renderStatsCard(
'Avg Confidence', 'Avg Confidence',
(dashboardData?.data.average_confidence_score || 0).toFixed(2), (dashboardData?.data.average_confidence_score || 0).toFixed(2),
'AI prediction confidence', 'AI prediction confidence',
theme.colors.warning, theme.colors.warning
'trending-up'
)} )}
</View> </View>
</View> </View>
@ -611,7 +729,8 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
{/* Dashboard header with key metrics */} {/* Dashboard header with key metrics */}
{renderHeader()} {renderHeader()}
{/* Confidence score breakdown */}
{renderConfidenceBreakdown()}
{/* Urgency level breakdown */} {/* Urgency level breakdown */}
{renderUrgencyBreakdown()} {renderUrgencyBreakdown()}
@ -622,84 +741,6 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
{/* Time-based analysis */} {/* Time-based analysis */}
{renderTimeAnalysis()} {renderTimeAnalysis()}
{/* AI Predictions List - Moved to main ScrollView */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>AI Predictions</Text>
<Text style={styles.sectionSubtitle}>
Review AI predictions with and without user feedback
</Text>
</View>
{/* PredictionsList rendered directly */}
<View style={styles.predictionsContainer}>
<PredictionsList
onPredictionPress={(prediction) => {
try {
// Navigate to FeedbackDetailScreen with required parameters
navigation.navigate('FeedbackDetail',
{
patientId: prediction.patid,
patientName: prediction.patientdetails.Name || 'Unknown Patient',
seriesNumber: prediction.prediction.processing_info.filename || 'Unknown Series',
seriesData: {
series_num: prediction.prediction.processing_info.filename || 'Unknown Series',
series_description: prediction.prediction.finding_type || 'AI Analysis',
total_images: prediction.prediction.processing_info.frame_count || 0,
png_preview: prediction.preview || '',
modality: prediction.patientdetails.Modality || 'Unknown'
},
patientData: {
patid: prediction.patid,
hospital_id: prediction.hospital_id,
patient_info: {
name: prediction.patientdetails.Name || 'Unknown Patient',
age: prediction.patientdetails.PatAge || 'Unknown',
sex: prediction.patientdetails.PatSex || 'Unknown',
date: prediction.patientdetails.Date || 'Unknown',
institution: prediction.patientdetails.InstName || 'Unknown Institution',
modality: prediction.patientdetails.Modality || 'Unknown Modality',
status: prediction.patientdetails.Status || 'Unknown',
report_status: prediction.patientdetails.ReportStatus || 'Unknown',
file_name: prediction.prediction.processing_info.filename || 'Unknown',
file_type: prediction.prediction.processing_info.file_type || 'Unknown',
frame_count: prediction.prediction.processing_info.frame_count || 0
},
series_summary: [{
series_num: prediction.prediction.processing_info.filename || 'Unknown Series',
series_description: prediction.prediction.finding_type || 'AI Analysis',
total_images: prediction.prediction.processing_info.frame_count || 0,
png_preview: prediction.preview || '',
modality: prediction.patientdetails.Modality || 'Unknown'
}],
processing_metadata: prediction.processing_metadata,
total_predictions: 1,
first_processed_at: prediction.processed_at,
last_processed_at: prediction.processed_at
},
feedbackData: prediction.feedbacks || [],
onFeedbackSubmitted: () => {
// Refresh dashboard data when feedback is submitted
console.log('Feedback submitted, refreshing dashboard...');
}
}
);
console.log('Navigation successful to FeedbackDetailScreen');
} catch (error) {
console.error('Navigation error:', error);
// Fallback: show alert or handle error gracefully
Alert.alert(
'Navigation Error',
'Unable to open feedback details. Please try again.',
[{ text: 'OK' }]
);
}
}}
/>
</View>
{/* Bottom spacing for tab bar */} {/* Bottom spacing for tab bar */}
<View style={styles.bottomSpacing} /> <View style={styles.bottomSpacing} />
</ScrollView> </ScrollView>
@ -790,29 +831,12 @@ const styles = StyleSheet.create({
...theme.shadows.primary, ...theme.shadows.primary,
}, },
// Stats card header styling (icon + title row)
statsCardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.xs,
gap: theme.spacing.sm,
},
// Stats card icon styling
statsCardIcon: {
width: 32,
height: 32,
borderRadius: theme.borderRadius.small,
justifyContent: 'center',
alignItems: 'center',
},
// Stats card title styling // Stats card title styling
statsCardTitle: { statsCardTitle: {
fontSize: theme.typography.fontSize.bodySmall, fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
flex: 1, marginBottom: theme.spacing.xs,
}, },
// Stats card value styling // Stats card value styling
@ -847,23 +871,6 @@ const styles = StyleSheet.create({
color: theme.colors.textPrimary, color: theme.colors.textPrimary,
marginBottom: theme.spacing.lg, marginBottom: theme.spacing.lg,
}, },
// Section subtitle styling
sectionSubtitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.md,
},
// Predictions container styling
predictionsContainer: {
height: 500, // Increased height for better FlatList performance
borderRadius: theme.borderRadius.medium,
overflow: 'hidden',
marginHorizontal: theme.spacing.md,
marginBottom: theme.spacing.md,
},
// Confidence breakdown container // Confidence breakdown container
confidenceContainer: { confidenceContainer: {
@ -940,48 +947,37 @@ const styles = StyleSheet.create({
urgencyContainer: { urgencyContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-around', justifyContent: 'space-around',
alignItems: 'flex-start',
paddingHorizontal: theme.spacing.sm,
}, },
// Circle Container styling // Urgency item styling
circleContainer: { urgencyItem: {
width: 80,
height: 80,
borderRadius: 40,
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
borderWidth: 8, flex: 1,
backgroundColor: theme.colors.background,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
}, },
// Circle Value styling // Urgency indicator styling
circleValue: { urgencyIndicator: {
fontSize: theme.typography.fontSize.bodyLarge, width: 16,
fontFamily: theme.typography.fontFamily.bold, height: 16,
color: theme.colors.textPrimary, borderRadius: 8,
marginBottom: theme.spacing.xs,
}, },
// Urgency Label styling // Urgency label styling
urgencyLabel: { urgencyLabel: {
fontSize: theme.typography.fontSize.bodySmall, fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
marginTop: theme.spacing.sm, marginBottom: theme.spacing.xs,
textAlign: 'center',
}, },
// Urgency Circle Item styling // Urgency value styling
urgencyCircleItem: { urgencyValue: {
alignItems: 'center', fontSize: theme.typography.fontSize.bodyMedium,
flex: 1, fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
}, },
// Feedback container styling // Feedback container styling
feedbackContainer: { feedbackContainer: {
flexDirection: 'row', flexDirection: 'row',

View File

@ -1,43 +0,0 @@
/*
* File: predictionsAPI.ts
* Description: API service for fetching AI prediction data and patient information
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
import { API_CONFIG, buildHeaders } from '../../../shared/utils';
const api = create({
baseURL: API_CONFIG.BASE_URL
});
/**
* PredictionsAPI Service
*
* Purpose: Handle API calls related to AI predictions and patient data
*
* Features:
* - Fetch all predictions from single endpoint
* - Frontend filtering for feedback status
* - Simple API calls without complex parameters
*/
export const predictionsAPI = {
/**
* Fetch All Predictions
*
* Purpose: Fetch all processed patient predictions
*
* @param token - Authentication token
* @returns Promise with all predictions response
*/
fetchAllPredictions: (token: string) => {
return api.get('/api/ai-cases/feedbacks/processed-patients', {}, buildHeaders({ token }));
}
};
/*
* End of File: predictionsAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -1,191 +0,0 @@
/*
* File: predictions.ts
* Description: Type definitions for AI prediction data and patient information
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// ============================================================================
// PREDICTION DATA TYPES
// ============================================================================
export interface PredictionProcessingInfo {
filename: string;
file_type: string;
frame_count: number;
is_multiframe: boolean;
averaging_applied: boolean;
}
export interface StrokeDetection {
Normal: number;
Stroke: number;
}
export interface BinaryHemorrhage {
Normal: number;
Hemorrhage: number;
}
export interface HemorrhageDetection {
Epidural: number;
Subdural: number;
Subarachnoid: number;
"Midline shift": number;
Intraparenchymal: number;
Intraventricular: number;
}
export interface DetailedResults {
stroke_detection: StrokeDetection;
binary_hemorrhage: BinaryHemorrhage;
hemorrhage_detection: HemorrhageDetection;
}
export interface Prediction {
label: string;
finding_type: string;
processing_info: PredictionProcessingInfo;
clinical_urgency: string;
confidence_score: number;
detailed_results: DetailedResults;
finding_category: string;
primary_severity: string;
anatomical_location: string;
}
export interface PatientDetails {
Date: string;
Name: string;
PatID: string;
PatAge: string;
PatSex: string;
Status: string;
InstName: string;
Modality: string;
ReportStatus: string | null;
medpacks_data: {
series: Array<{
Path: string[];
SerDes: string;
ViePos: string | null;
pngpath: string;
SeriesNum: string;
ImgTotalinSeries: string;
}>;
file_path: string;
basic_info: Record<string, any>;
study_info: {
modality: string;
};
hospital_id: string;
parsed_data: {
series: Array<{
Path: string[];
SerDes: string;
ViePos: string | null;
pngpath: string;
SeriesNum: string;
ImgTotalinSeries: string;
}>;
patientdetails: {
Date: string;
Name: string;
PatID: string;
PatAge: string;
PatSex: string;
Status: string;
InstName: string;
Modality: string;
ReportStatus: string | null;
};
};
dicom_images: any[];
dicom_series: any[];
dicom_studies: any[];
image_details: Record<string, any>;
all_dicom_data: any;
technical_details: Record<string, any>;
patient_demographics: Record<string, any>;
complete_patient_data: {
series: string;
file_id: string;
file_path: string;
series_id: string | null;
created_at: string;
updated_at: string;
hospital_id: string;
patientdetails: string;
};
complete_dicom_details: any;
};
}
export interface ProcessingMetadata {
hospital_id: string;
processed_at: string;
ai_model_used: string;
original_patid: string;
sync_timestamp: string;
processed_file_path: string;
complete_dicom_fetched: boolean;
}
export interface Feedback {
feedback_id: string;
patid: string;
prediction_id: number;
user_id: string;
feedback_text: string;
is_positive: boolean;
email: string;
created_at: string;
updated_at: string;
}
export interface PredictionData {
id: number;
patid: string;
hospital_id: string;
prediction: Prediction;
patientdetails: PatientDetails;
processing_metadata: ProcessingMetadata;
file_path: string;
processed_at: string;
preview: string;
feedbacks: Feedback[];
user_feedback_count: number;
latest_feedback_date: string;
latest_feedback_type: string;
has_provided_feedback: boolean;
}
export interface PredictionsResponse {
success: boolean;
data: PredictionData[];
}
// ============================================================================
// TAB TYPES
// ============================================================================
export type PredictionTabType = 'with-feedback' | 'without-feedback';
// ============================================================================
// FILTER TYPES
// ============================================================================
export interface PredictionFilters {
urgency?: string;
finding_type?: string;
confidence_min?: number;
confidence_max?: number;
date_from?: string;
date_to?: string;
}
/*
* End of File: predictions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -21,17 +21,18 @@ import Icon from 'react-native-vector-icons/Feather';
// ============================================================================ // ============================================================================
interface FilterTabsProps { interface FilterTabsProps {
selectedFilter: 'all' | 'processed' | 'pending'; selectedFilter: 'all' | 'processed' | 'pending' | 'error';
onFilterChange: (filter: 'all' | 'processed' | 'pending') => void; onFilterChange: (filter: 'all' | 'processed' | 'pending' | 'error') => void;
patientCounts: { patientCounts: {
all: number; all: number;
processed: number; processed: number;
pending: number; pending: number;
error: number;
}; };
} }
interface FilterTab { interface FilterTab {
id: 'all' | 'processed' | 'pending'; id: 'all' | 'processed' | 'pending' | 'error';
label: string; label: string;
icon: string; icon: string;
color: string; color: string;
@ -48,7 +49,7 @@ interface FilterTab {
* Purpose: Provide filtering options for patient list * Purpose: Provide filtering options for patient list
* *
* Features: * Features:
* - Multiple filter options (All, Processed, Pending) * - Multiple filter options (All, Processed, Pending, Error)
* - Patient count display for each filter * - Patient count display for each filter
* - Visual indicators with icons and colors * - Visual indicators with icons and colors
* - Horizontal scrollable layout * - Horizontal scrollable layout
@ -86,6 +87,13 @@ const FilterTabs: React.FC<FilterTabsProps> = ({
color: theme.colors.warning, color: theme.colors.warning,
activeColor: theme.colors.warning, activeColor: theme.colors.warning,
}, },
{
id: 'error',
label: 'Error',
icon: 'alert-triangle',
color: theme.colors.error,
activeColor: theme.colors.error,
},
]; ];
// ============================================================================ // ============================================================================
@ -108,6 +116,8 @@ const FilterTabs: React.FC<FilterTabsProps> = ({
return patientCounts.processed; return patientCounts.processed;
case 'pending': case 'pending':
return patientCounts.pending; return patientCounts.pending;
case 'error':
return patientCounts.error;
default: default:
return 0; return 0;
} }
@ -179,6 +189,13 @@ const FilterTabs: React.FC<FilterTabsProps> = ({
</Text> </Text>
</View> </View>
</View> </View>
{/* Error Indicator */}
{tab.id === 'error' && patientCount > 0 && (
<View style={styles.errorIndicator}>
<View style={styles.pulseDot} />
</View>
)}
</TouchableOpacity> </TouchableOpacity>
); );
}; };
@ -282,6 +299,22 @@ const styles = StyleSheet.create({
color: theme.colors.background, color: theme.colors.background,
}, },
// Error Indicator
errorIndicator: {
position: 'absolute',
top: 8,
right: 8,
width: 8,
height: 8,
},
pulseDot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: theme.colors.error,
// Note: In a real app, you'd add animation here
},
// Active Filter Indicator // Active Filter Indicator
activeIndicator: { activeIndicator: {
marginTop: theme.spacing.xs, marginTop: theme.spacing.xs,

View File

@ -1,6 +1,6 @@
/* /*
* File: PatientCard.tsx * File: PatientCard.tsx
* Description: Enhanced patient card component for displaying DICOM medical case information * Description: Patient card component for displaying DICOM medical case information
* Design & Developed by Tech4Biz Solutions * Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved. * Copyright (c) Spurrin Innovations. All rights reserved.
*/ */
@ -33,16 +33,16 @@ interface PatientCardProps {
/** /**
* PatientCard Component * PatientCard Component
* *
* Purpose: Display DICOM medical case information in a modern, enhanced card format * Purpose: Display DICOM medical case information in a card format
* *
* Features: * Features:
* - Enhanced visual hierarchy with modern design * - Patient basic information from DICOM data
* - Improved status indicators and color coding * - Modality and institution information
* - Better spacing and typography * - Processing status with color coding
* - Enhanced shadows and elevation * - Series information
* - More intuitive information layout * - Time since processed
* - Emergency alert for critical cases * - Emergency alert for critical cases
* - Modern ER-focused design with better UX * - Modern ER-focused design
*/ */
const PatientCard: React.FC<PatientCardProps> = ({ const PatientCard: React.FC<PatientCardProps> = ({
patient, patient,
@ -56,40 +56,36 @@ const PatientCard: React.FC<PatientCardProps> = ({
/** /**
* Get Status Color Configuration * Get Status Color Configuration
* *
* Purpose: Get enhanced color and icon based on processing status * Purpose: Get color and icon based on processing status
* *
* @param status - Processing status * @param status - Processing status
* @returns Enhanced color configuration object * @returns Color configuration object
*/ */
const getStatusConfig = (status: string) => { const getStatusConfig = (status: string) => {
switch (status.toLowerCase()) { switch (status.toLowerCase()) {
case 'processed': case 'processed':
return { return {
color: '#10B981', color: theme.colors.success,
icon: 'check-circle', icon: 'check-circle',
bgColor: '#ECFDF5', bgColor: '#F0FFF4'
borderColor: '#D1FAE5'
}; };
case 'pending': case 'pending':
return { return {
color: '#F59E0B', color: theme.colors.warning,
icon: 'clock', icon: 'clock',
bgColor: '#FFFBEB', bgColor: '#FFF8E1'
borderColor: '#FEF3C7'
}; };
case 'error': case 'error':
return { return {
color: '#EF4444', color: theme.colors.error,
icon: 'alert-triangle', icon: 'alert-triangle',
bgColor: '#FEF2F2', bgColor: '#FFF5F5'
borderColor: '#FECACA'
}; };
default: default:
return { return {
color: '#3B82F6', color: theme.colors.primary,
icon: 'info', icon: 'info',
bgColor: '#EFF6FF', bgColor: theme.colors.background
borderColor: '#DBEAFE'
}; };
} }
}; };
@ -97,21 +93,21 @@ const PatientCard: React.FC<PatientCardProps> = ({
/** /**
* Get Modality Color * Get Modality Color
* *
* Purpose: Get enhanced color based on imaging modality * Purpose: Get color based on imaging modality
* *
* @param modality - Imaging modality * @param modality - Imaging modality
* @returns Enhanced color code * @returns Color code
*/ */
const getModalityColor = (modality: string) => { const getModalityColor = (modality: string) => {
switch (modality.toUpperCase()) { switch (modality.toUpperCase()) {
case 'CT': case 'CT':
return '#3B82F6'; return '#4A90E2';
case 'MR': case 'MR':
return '#8B5CF6'; return '#7B68EE';
case 'DX': case 'DX':
return '#10B981'; return '#50C878';
case 'DICOM': case 'DICOM':
return '#EF4444'; return '#FF6B6B';
default: default:
return theme.colors.textSecondary; return theme.colors.textSecondary;
} }
@ -168,26 +164,21 @@ const PatientCard: React.FC<PatientCardProps> = ({
// ============================================================================ // ============================================================================
/** /**
* Render Enhanced Status Badge * Render Status Badge
* *
* Purpose: Render improved processing status indicator badge * Purpose: Render processing status indicator badge
*/ */
const renderStatusBadge = () => ( const renderStatusBadge = () => (
<View style={[styles.statusBadge, { <View style={[styles.statusBadge, { backgroundColor: statusConfig.bgColor, borderColor: statusConfig.color }]}>
backgroundColor: statusConfig.bgColor, <Icon name={statusConfig.icon} size={12} color={statusConfig.color} />
borderColor: statusConfig.borderColor <Text style={[styles.statusText, { color: statusConfig.color }]}>{patientInfo.status}</Text>
}]}>
<Icon name={statusConfig.icon} size={14} color={statusConfig.color} />
<Text style={[styles.statusText, { color: statusConfig.color }]}>
{patientInfo.status}
</Text>
</View> </View>
); );
/** /**
* Render Enhanced Emergency Button * Render Emergency Button
* *
* Purpose: Render improved emergency alert button for critical cases * Purpose: Render emergency alert button for critical cases
*/ */
const renderEmergencyButton = () => { const renderEmergencyButton = () => {
if (!isCritical) { if (!isCritical) {
@ -198,32 +189,14 @@ const PatientCard: React.FC<PatientCardProps> = ({
<TouchableOpacity <TouchableOpacity
style={styles.emergencyButton} style={styles.emergencyButton}
onPress={onEmergencyPress} onPress={onEmergencyPress}
activeOpacity={0.8} activeOpacity={0.7}
> >
<Icon name="alert-triangle" size={16} color={theme.colors.background} /> <Icon name="alert-triangle" size={14} color={theme.colors.background} />
<Text style={styles.emergencyButtonText}>ALERT</Text> <Text style={styles.emergencyButtonText}>ALERT</Text>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
/**
* Render Enhanced Modality Badge
*
* Purpose: Render improved modality indicator
*/
const renderModalityBadge = () => (
<View style={[styles.modalityBadge, {
backgroundColor: getModalityColor(patientInfo.modality) + '20',
borderColor: getModalityColor(patientInfo.modality)
}]}>
<Text style={[styles.modalityText, {
color: getModalityColor(patientInfo.modality)
}]}>
{patientInfo.modality || 'N/A'}
</Text>
</View>
);
// ============================================================================ // ============================================================================
// MAIN RENDER // MAIN RENDER
// ============================================================================ // ============================================================================
@ -235,17 +208,14 @@ const PatientCard: React.FC<PatientCardProps> = ({
{ borderLeftColor: statusConfig.color } { borderLeftColor: statusConfig.color }
]} ]}
onPress={onPress} onPress={onPress}
activeOpacity={0.8} activeOpacity={0.7}
> >
{/* Enhanced Header Section */} {/* Header Section */}
<View style={styles.header}> <View style={styles.header}>
<View style={styles.headerLeft}> <View style={styles.headerLeft}>
<View style={styles.patientNameRow}> <Text style={styles.patientName}>
<Text style={styles.patientName}> {patientInfo.name || 'Unknown Patient'}
{patientInfo.name || 'Unknown Patient'} </Text>
</Text>
{renderModalityBadge()}
</View>
<Text style={styles.patientInfo}> <Text style={styles.patientInfo}>
ID: {patient.patid} {patientInfo.age || 'N/A'}y {patientInfo.sex || 'N/A'} ID: {patient.patid} {patientInfo.age || 'N/A'}y {patientInfo.sex || 'N/A'}
</Text> </Text>
@ -256,80 +226,72 @@ const PatientCard: React.FC<PatientCardProps> = ({
</View> </View>
</View> </View>
{/* Enhanced Medical Information Section */} {/* Medical Information Section */}
<View style={styles.medicalSection}> <View style={styles.medicalSection}>
<View style={styles.infoGrid}> <View style={styles.infoRow}>
<View style={styles.infoCard}> <View style={styles.infoItem}>
<Icon name="file-text" size={16} color={theme.colors.primary} /> <Text style={styles.infoLabel}>Modality</Text>
<Text style={styles.infoValue}> <Text style={[
{patient.total_files_processed || 0} styles.infoValue,
styles.modalityText,
{ color: getModalityColor(patientInfo.modality) }
]}>
{patientInfo.modality || 'N/A'}
</Text> </Text>
</View>
<View style={styles.infoItem}>
<Text style={styles.infoLabel}>Files</Text> <Text style={styles.infoLabel}>Files</Text>
</View> <Text style={[
styles.infoValue,
<View style={styles.infoCard}> { color: theme.colors.primary }
<Icon name="layers" size={16} color={theme.colors.warning} /> ]}>
<Text style={styles.infoValue}> {patient.total_files_processed}
{seriesCount}
</Text> </Text>
<Text style={styles.infoLabel}>Series</Text>
</View> </View>
<View style={styles.infoItem}>
<View style={styles.infoCard}> <Text style={styles.infoLabel}>Report</Text>
<Icon name="clipboard" size={16} color={ <Text style={[
patientInfo.report_status === 'Available' ? theme.colors.success : theme.colors.warning styles.infoValue,
} /> { color: patientInfo.report_status === 'Available' ? theme.colors.success : theme.colors.warning }
<Text style={[styles.infoValue, { ]}>
color: patientInfo.report_status === 'Available' ? theme.colors.success : theme.colors.warning
}]}>
{patientInfo.report_status || 'Pending'} {patientInfo.report_status || 'Pending'}
</Text> </Text>
<Text style={styles.infoLabel}>Report</Text>
</View> </View>
</View> </View>
{/* Enhanced Institution Row */} {/* Institution */}
<View style={styles.institutionRow}> <View style={styles.institutionRow}>
<View style={styles.institutionIcon}> <Icon name="home" size={14} color={theme.colors.textSecondary} />
<Icon name="home" size={16} color={theme.colors.primary} />
</View>
<Text style={styles.institutionText}> <Text style={styles.institutionText}>
{patientInfo.institution || 'Unknown Institution'} {patientInfo.institution || 'Unknown Institution'}
</Text> </Text>
</View> </View>
</View> </View>
{/* Enhanced Series Information */} {/* Series Information */}
<View style={styles.seriesSection}> <View style={styles.seriesSection}>
<View style={styles.seriesHeader}> <View style={styles.seriesHeader}>
<Icon name="layers" size={16} color={theme.colors.textSecondary} /> <Icon name="layers" size={14} color={theme.colors.textSecondary} />
<Text style={styles.seriesLabel}>Series Details</Text> <Text style={styles.seriesLabel}>Series Information</Text>
</View>
<View style={styles.seriesInfo}>
<Text style={styles.seriesText}>
{seriesCount} Series Available
</Text>
<Text style={styles.frameText}>
{patientInfo.frame_count || 0} Total Frames
</Text>
</View> </View>
<Text style={styles.seriesText}>
{seriesCount} Series Available {patientInfo.frame_count} Total Frames
</Text>
</View> </View>
{/* Enhanced Footer */} {/* Footer */}
<View style={styles.footer}> <View style={styles.footer}>
<View style={styles.footerLeft}> <View style={styles.footerLeft}>
<Text style={styles.dateText}> <Text style={styles.dateText}>
{formatDate(patientInfo.date)} {formatDate(patientInfo.date)}
</Text> </Text>
<Text style={styles.processedText}> <Text style={styles.processedText}>
Processed {getTimeSinceProcessed(patient.last_processed_at)} {getTimeSinceProcessed(patient.last_processed_at)}
</Text> </Text>
</View> </View>
<View style={styles.footerRight}> <View style={styles.footerRight}>
<Text style={styles.caseId}>Case #{patient.patid}</Text> <Text style={styles.caseId}>Case #{patient.patid}</Text>
<View style={styles.chevronContainer}> <Icon name="chevron-right" size={16} color={theme.colors.textMuted} />
<Icon name="chevron-right" size={18} color={theme.colors.primary} />
</View>
</View> </View>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@ -337,21 +299,21 @@ const PatientCard: React.FC<PatientCardProps> = ({
}; };
// ============================================================================ // ============================================================================
// ENHANCED STYLES // STYLES
// ============================================================================ // ============================================================================
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: theme.colors.background, backgroundColor: theme.colors.background,
borderRadius: 16, borderRadius: 12,
padding: theme.spacing.md, padding: theme.spacing.md,
marginHorizontal: theme.spacing.md, marginHorizontal: theme.spacing.md,
marginVertical: theme.spacing.xs, marginVertical: theme.spacing.xs,
shadowColor: '#000000', shadowColor: theme.colors.shadow,
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06, shadowOpacity: 0.1,
shadowRadius: 8, shadowRadius: 4,
elevation: 3, elevation: 2,
borderWidth: 1, borderWidth: 1,
borderColor: theme.colors.border, borderColor: theme.colors.border,
borderLeftWidth: 4, borderLeftWidth: 4,
@ -359,12 +321,10 @@ const styles = StyleSheet.create({
containerCritical: { containerCritical: {
borderColor: theme.colors.error, borderColor: theme.colors.error,
borderWidth: 2, borderWidth: 2,
backgroundColor: '#FEF2F2', backgroundColor: '#FFF5F5',
shadowColor: theme.colors.error,
shadowOpacity: 0.15,
}, },
// Enhanced Header Section // Header Section
header: { header: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -373,220 +333,156 @@ const styles = StyleSheet.create({
}, },
headerLeft: { headerLeft: {
flex: 1, flex: 1,
marginRight: theme.spacing.md, marginRight: theme.spacing.sm,
}, },
headerRight: { headerRight: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: theme.spacing.sm,
},
patientNameRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.xs,
gap: theme.spacing.xs,
}, },
patientName: { patientName: {
fontSize: 20, fontSize: 18,
color: theme.colors.textPrimary, color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold, fontFamily: theme.typography.fontFamily.bold,
flex: 1,
}, },
patientInfo: { patientInfo: {
fontSize: 14, fontSize: 14,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium, marginTop: 2,
lineHeight: 20, fontFamily: theme.typography.fontFamily.regular,
}, },
// Enhanced Status Badge // Status Badge
statusBadge: { statusBadge: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 12, paddingHorizontal: 8,
paddingVertical: 6, paddingVertical: 4,
borderRadius: 20, borderRadius: 12,
borderWidth: 1.5, marginRight: theme.spacing.xs,
gap: theme.spacing.xs, borderWidth: 1,
}, },
statusText: { statusText: {
fontSize: 11, fontSize: 10,
fontFamily: theme.typography.fontFamily.bold, fontFamily: theme.typography.fontFamily.bold,
marginLeft: 4,
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: 0.5,
}, },
// Enhanced Emergency Button // Emergency Button
emergencyButton: { emergencyButton: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.colors.error, backgroundColor: theme.colors.error,
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
gap: theme.spacing.xs,
shadowColor: theme.colors.error,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 3,
},
emergencyButtonText: {
fontSize: 11,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.background,
letterSpacing: 0.5,
},
// Enhanced Modality Badge
modalityBadge: {
paddingHorizontal: 8, paddingHorizontal: 8,
paddingVertical: 4, paddingVertical: 4,
borderRadius: 12, borderRadius: 12,
borderWidth: 1.5,
}, },
modalityText: { emergencyButtonText: {
fontSize: 10, fontSize: 10,
fontFamily: theme.typography.fontFamily.bold, fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase', color: theme.colors.background,
letterSpacing: 0.5, marginLeft: 4,
}, },
// Enhanced Medical Section // Medical Section
medicalSection: { medicalSection: {
marginBottom: theme.spacing.sm, marginBottom: theme.spacing.sm,
paddingBottom: theme.spacing.sm, paddingBottom: theme.spacing.sm,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: theme.colors.border, borderBottomColor: theme.colors.border,
}, },
infoGrid: { infoRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
marginBottom: theme.spacing.sm, marginBottom: theme.spacing.sm,
gap: theme.spacing.sm,
}, },
infoCard: { infoItem: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
paddingVertical: theme.spacing.xs,
backgroundColor: theme.colors.backgroundAlt,
borderRadius: 12,
paddingHorizontal: theme.spacing.sm,
gap: theme.spacing.xs,
}, },
infoLabel: { infoLabel: {
fontSize: 10, fontSize: 10,
color: theme.colors.textMuted, color: theme.colors.textMuted,
marginBottom: 2,
textTransform: 'uppercase', textTransform: 'uppercase',
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.regular,
letterSpacing: 0.5,
textAlign: 'center',
}, },
infoValue: { infoValue: {
fontSize: 14, fontSize: 14,
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary, color: theme.colors.textPrimary,
textAlign: 'center', textAlign: 'center',
}, },
modalityText: {
fontFamily: theme.typography.fontFamily.bold,
},
// Enhanced Institution Row // Institution Row
institutionRow: { institutionRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.colors.backgroundAlt,
padding: theme.spacing.sm,
borderRadius: 12,
gap: theme.spacing.sm,
},
institutionIcon: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: theme.colors.primary + '20',
justifyContent: 'center',
alignItems: 'center',
}, },
institutionText: { institutionText: {
fontSize: 14, fontSize: 14,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
marginLeft: 6,
flex: 1, flex: 1,
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.regular,
}, },
// Enhanced Series Section // Series Section
seriesSection: { seriesSection: {
backgroundColor: theme.colors.backgroundAlt, backgroundColor: theme.colors.backgroundAlt,
borderRadius: 12, borderRadius: 8,
padding: theme.spacing.sm, padding: theme.spacing.sm,
marginBottom: theme.spacing.sm, marginBottom: theme.spacing.sm,
}, },
seriesHeader: { seriesHeader: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: theme.spacing.sm, marginBottom: 4,
gap: theme.spacing.xs,
}, },
seriesLabel: { seriesLabel: {
fontSize: 13, fontSize: 12,
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
textTransform: 'uppercase', marginLeft: 4,
letterSpacing: 0.5,
},
seriesInfo: {
gap: theme.spacing.xs,
}, },
seriesText: { seriesText: {
fontSize: 15, fontSize: 14,
color: theme.colors.textPrimary, color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.medium,
},
frameText: {
fontSize: 13,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular, fontFamily: theme.typography.fontFamily.regular,
}, },
// Enhanced Footer Section // Footer Section
footer: { footer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
paddingTop: theme.spacing.sm,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
}, },
footerLeft: { footerLeft: {
flex: 1, flex: 1,
}, },
dateText: { dateText: {
fontSize: 13, fontSize: 12,
color: theme.colors.textMuted, color: theme.colors.textMuted,
fontFamily: theme.typography.fontFamily.medium, fontFamily: theme.typography.fontFamily.regular,
marginBottom: 2,
}, },
processedText: { processedText: {
fontSize: 12, fontSize: 11,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
marginTop: 2,
fontFamily: theme.typography.fontFamily.regular, fontFamily: theme.typography.fontFamily.regular,
}, },
footerRight: { footerRight: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: theme.spacing.sm,
}, },
caseId: { caseId: {
fontSize: 12, fontSize: 12,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium, marginRight: theme.spacing.xs,
}, fontFamily: theme.typography.fontFamily.regular,
chevronContainer: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: theme.colors.primary + '20',
justifyContent: 'center',
alignItems: 'center',
}, },
}); });

View File

@ -13,7 +13,6 @@ import { PatientsScreen, PatientDetailsScreen, SeriesDetailScreen } from '../scr
// Import types // Import types
import { PatientCareStackParamList } from './navigationTypes'; import { PatientCareStackParamList } from './navigationTypes';
import FeedbackDetailScreen from '../screens/FeedbackDetailScreen';
// ============================================================================ // ============================================================================
// STACK NAVIGATOR // STACK NAVIGATOR
@ -65,7 +64,7 @@ const PatientCareStackNavigator: React.FC = () => {
gestureDirection: 'horizontal', gestureDirection: 'horizontal',
}} }}
/> />
{/* Series Detail Screen - Detailed series information with predictions and feedback */} {/* Series Detail Screen - Detailed series information with predictions and feedback */}
<Stack.Screen <Stack.Screen
name="SeriesDetail" name="SeriesDetail"
@ -76,18 +75,6 @@ const PatientCareStackNavigator: React.FC = () => {
gestureDirection: 'horizontal', gestureDirection: 'horizontal',
}} }}
/> />
<Stack.Screen
name="FeedbackDetail"
component={FeedbackDetailScreen}
options={{
title: 'Feedback Details',
gestureEnabled: true,
gestureDirection: 'horizontal',
headerShown: false
}}
/>
</Stack.Navigator> </Stack.Navigator>
); );
}; };

View File

@ -27,9 +27,6 @@ export type PatientCareStackParamList = {
// Series Detail Screen - Detailed series information with predictions and feedback // Series Detail Screen - Detailed series information with predictions and feedback
SeriesDetail: SeriesDetailScreenParams; SeriesDetail: SeriesDetailScreenParams;
// Feedback Detail Screen - Series feedback history and submission
FeedbackDetail: FeedbackDetailScreenParams;
}; };
// ============================================================================ // ============================================================================
@ -86,32 +83,6 @@ export interface SeriesDetailScreenParams {
onFeedbackSubmitted?: () => void; onFeedbackSubmitted?: () => void;
} }
/**
* FeedbackDetailScreenParams
*
* Purpose: Parameters for the feedback detail screen
*
* Parameters:
* - patientId: Required patient ID for the series
* - patientName: Required patient name for display
* - seriesNumber: Required series number to display
* - seriesData: Required series data object
* - patientData: Required patient data object for context
* - feedbackData: Required feedback data array for the series
* - onFeedbackSubmitted: Optional callback to refresh parent screen data
*/
export interface FeedbackDetailScreenParams {
patientId: string;
patientName: string;
seriesNumber: string;
seriesData: any;
patientData: any;
feedbackData: any[]; // Feedback data array for the series
// Callback function to refresh parent screen data when feedback is submitted
// This ensures PatientDetailsScreen shows updated information when user navigates back
onFeedbackSubmitted?: () => void;
}
// ============================================================================ // ============================================================================
// NAVIGATION PROP TYPES // NAVIGATION PROP TYPES
// ============================================================================ // ============================================================================
@ -157,16 +128,6 @@ export interface SeriesDetailScreenProps {
}; };
} }
/**
* FeedbackDetailScreenProps - Props for FeedbackDetailScreen component
*/
export interface FeedbackDetailScreenProps {
navigation: PatientCareNavigationProp;
route: {
params: FeedbackDetailScreenParams;
};
}
// ============================================================================ // ============================================================================
// NAVIGATION UTILITY TYPES // NAVIGATION UTILITY TYPES
// ============================================================================ // ============================================================================

View File

@ -271,17 +271,20 @@ export const selectPatientStats = createSelector(
total: 0, total: 0,
processed: 0, processed: 0,
pending: 0, pending: 0,
error: 0,
averageAge: 0, averageAge: 0,
modalities: {}, modalities: {},
totalFiles: 0, totalFiles: 0,
processedPercentage: 0, processedPercentage: 0,
pendingPercentage: 0, pendingPercentage: 0,
errorPercentage: 0,
}; };
} }
const total = patients.length; const total = patients.length;
const processed = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'processed').length; const processed = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'processed').length;
const pending = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'pending').length; const pending = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'pending').length;
const error = patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'error').length;
// Calculate average age // Calculate average age
const totalAge = patients.reduce((sum: number, patient: PatientData) => { const totalAge = patients.reduce((sum: number, patient: PatientData) => {
@ -304,11 +307,13 @@ export const selectPatientStats = createSelector(
total, total,
processed, processed,
pending, pending,
error,
averageAge, averageAge,
modalities, modalities,
totalFiles, totalFiles,
processedPercentage: total > 0 ? Math.round((processed / total) * 100) : 0, processedPercentage: total > 0 ? Math.round((processed / total) * 100) : 0,
pendingPercentage: total > 0 ? Math.round((pending / total) * 100) : 0, pendingPercentage: total > 0 ? Math.round((pending / total) * 100) : 0,
errorPercentage: total > 0 ? Math.round((error / total) * 100) : 0,
}; };
} }
); );
@ -387,13 +392,14 @@ export const selectPatientCounts = createSelector(
[selectPatients], [selectPatients],
(patients) => { (patients) => {
if (!patients || !Array.isArray(patients)) { if (!patients || !Array.isArray(patients)) {
return { all: 0, processed: 0, pending: 0 }; return { all: 0, processed: 0, pending: 0, error: 0 };
} }
return { return {
all: patients.length, all: patients.length,
processed: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'processed').length, processed: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'processed').length,
pending: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'pending').length, pending: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'pending').length,
error: patients.filter((p: PatientData) => p.patient_info.status.toLowerCase() === 'error').length,
}; };
} }
); );

View File

@ -63,7 +63,7 @@ export interface PatientCareState {
// Search and filtering // Search and filtering
searchQuery: string; searchQuery: string;
selectedFilter: 'all' | 'processed' | 'pending'; selectedFilter: 'all' | 'processed' | 'pending' | 'error';
sortBy: 'date' | 'name' | 'processed'; sortBy: 'date' | 'name' | 'processed';
sortOrder: 'asc' | 'desc'; sortOrder: 'asc' | 'desc';
@ -93,6 +93,7 @@ export const fetchPatients = createAsyncThunk(
async (token: string, { rejectWithValue }) => { async (token: string, { rejectWithValue }) => {
try { try {
const response: any = await patientAPI.getPatients(token); const response: any = await patientAPI.getPatients(token);
console.log('response', response);
if (response.ok && response.data&& response.data.data) { if (response.ok && response.data&& response.data.data) {
// Return the patients data directly from the new API structure // Return the patients data directly from the new API structure
@ -337,7 +338,7 @@ const patientCareSlice = createSlice({
* *
* Purpose: Set patient filter * Purpose: Set patient filter
*/ */
setFilter: (state, action: PayloadAction<'all' | 'processed' | 'pending'>) => { setFilter: (state, action: PayloadAction<'all' | 'processed' | 'pending' | 'error'>) => {
state.selectedFilter = action.payload; state.selectedFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering state.currentPage = 1; // Reset to first page when filtering
}, },

View File

@ -1,632 +0,0 @@
/*
* File: FeedbackDetailScreen.tsx
* Description: Feedback detail screen for a specific series showing feedback history (read-only)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*
* Features:
* - Display feedback history for the series (read-only)
* - Feedback data received from navigation parameters
* - Clinical insights and feedback analytics
* - Modern healthcare-focused UI design
*/
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Alert,
TextInput,
RefreshControl,
} from 'react-native';
import { theme } from '../../../theme/theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import Icon from 'react-native-vector-icons/Feather';
import { SafeAreaView } from 'react-native-safe-area-context';
// Import types and API
import { selectUser } from '../../Auth/redux/authSelectors';
import { FeedbackDetailScreenProps } from '../../Dashboard/navigation/navigationTypes';
// ============================================================================
// INTERFACES
// ============================================================================
interface SeriesSummary {
series_num: string;
series_description: string;
total_images: number;
png_preview: string;
modality: string;
}
interface Feedback {
feedback_id: string;
user_id: string;
feedback_text: string;
is_positive: boolean;
email: string;
created_at: string;
prediction_id: number;
prediction_file_path: string;
series_number: string;
feedback_type: string;
}
interface PatientData {
patid: string;
hospital_id: string;
patient_info: {
name: string;
age: string;
sex: string;
date: string;
institution: string;
modality: string;
status: string;
report_status: string;
file_name: string;
file_type: string;
frame_count: number;
};
series_summary: SeriesSummary[];
processing_metadata: any;
total_predictions: number;
first_processed_at: string;
last_processed_at: string;
}
// ============================================================================
// FEEDBACK DETAIL SCREEN COMPONENT
// ============================================================================
/**
* FeedbackDetailScreen Component
*
* Purpose: Display feedback details and history for a specific series (read-only)
*
* Features:
* - Feedback history display (read-only)
* - Clinical insights and analytics
* - Modern healthcare-focused UI design
*/
const FeedbackDetailScreen: React.FC<FeedbackDetailScreenProps> = ({ navigation, route }) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const dispatch = useAppDispatch();
// Route parameters
const { patientId, patientName, seriesNumber, seriesData, patientData, feedbackData, onFeedbackSubmitted } = route.params;
// Redux state
const user = useAppSelector(selectUser);
// Local state
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Component Mount Effect
*
* Purpose: Initialize screen and set navigation title
*/
useEffect(() => {
navigation.setOptions({
title: `Feedback - Series ${seriesNumber}`,
headerShown: true,
headerLeft: () => (
<TouchableOpacity
style={styles.headerBackButton}
onPress={handleBackPress}
activeOpacity={0.7}
>
<Icon name="arrow-left" size={24} color={theme.colors.primary} />
</TouchableOpacity>
),
});
}, [navigation, seriesNumber, patientId]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Back Navigation
*
* Purpose: Navigate back to previous screen
*/
const handleBackPress = useCallback(() => {
navigation.goBack();
}, [navigation]);
/**
* Handle Refresh
*
* Purpose: Pull-to-refresh functionality
*/
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
// TODO: Implement actual refresh logic
setTimeout(() => {
setIsRefreshing(false);
}, 1000);
}, []);
/**
* Handle Back to Patient Details
*
* Purpose: Navigate to PatientDetails screen within the Dashboard stack
*
* Note: Now that both screens are in the same Dashboard stack,
* navigation should work smoothly without loops.
*/
const handleBackToPatientDetails = useCallback(() => {
try {
// Navigate to PatientDetails screen in the same stack
navigation.navigate('PatientDetails', {
patientId: patientId,
patient: patientData || { name: patientName || 'Unknown Patient' },
});
} catch (error) {
console.warn('Navigation to PatientDetails failed:', error);
// Fallback: go back to previous screen
navigation.goBack();
}
}, [navigation, patientId, patientName, patientData]);
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Get Feedback Type Color
*
* Purpose: Get appropriate color for feedback type
*
* @param feedbackType - Type of feedback
*/
const getFeedbackTypeColor = (feedbackType: string) => {
switch (feedbackType.toLowerCase()) {
case 'clinical_accuracy':
return theme.colors.success;
case 'confidence_assessment':
return theme.colors.warning;
case 'technical_issue':
return theme.colors.error;
default:
return theme.colors.info;
}
};
/**
* Get Series Feedback
*
* Purpose: Get feedback for the current series
*/
const getSeriesFeedback = () => {
return feedbackData?.filter((feedback: Feedback) => feedback.series_number === seriesNumber) || [];
};
/**
* Is Feedback New
*
* Purpose: Check if feedback is recent (within 24 hours)
*
* @param feedbackId - Feedback ID to check
*/
const isFeedbackNew = (feedbackId: string) => {
const feedback = feedbackData?.find((f: Feedback) => f.feedback_id === feedbackId);
if (!feedback) return false;
const feedbackDate = new Date(feedback.created_at);
const now = new Date();
const diffHours = (now.getTime() - feedbackDate.getTime()) / (1000 * 60 * 60);
return diffHours < 24;
};
// ============================================================================
// RENDER HELPERS
// ============================================================================
/**
* Render Series Header
*
* Purpose: Render series information header
*/
const renderSeriesHeader = () => {
if (!seriesData) return null;
return (
<View style={styles.seriesHeader}>
<View style={styles.seriesHeaderTop}>
<View style={styles.seriesHeaderLeft}>
<Text style={styles.seriesTitle}>
Series {seriesData.series_description}
</Text>
<Text style={styles.seriesMeta}>
{seriesData.total_images} images {seriesData.modality} modality
</Text>
</View>
<View style={styles.seriesHeaderRight}>
<View style={styles.seriesStatusBadge}>
<Icon name="check-circle" size={16} color={'white'} />
<Text style={styles.seriesStatusText}>Processed</Text>
</View>
</View>
</View>
{patientData && (
<View style={styles.patientInfoRow}>
<Icon name="user" size={16} color={theme.colors.textSecondary} />
<Text style={styles.patientInfoText}>
{patientData.patient_info.name} ID: {patientData.patid}
</Text>
<TouchableOpacity
style={styles.patientDetailButton}
onPress={handleBackToPatientDetails}
activeOpacity={0.7}
>
<View style={styles.patientDetailButtonContent}>
<Icon name="user" size={14} color={theme.colors.primary} />
<Text style={styles.patientDetailButtonText}>View Details</Text>
</View>
</TouchableOpacity>
</View>
)}
</View>
);
};
/**
* Render Feedback History
*
* Purpose: Render feedback history display only
*/
const renderFeedbackHistory = () => {
const seriesFeedbacks = getSeriesFeedback();
return (
<View style={styles.feedbackHistory}>
{/* Feedback History */}
<View style={styles.feedbackList}>
<Text style={styles.feedbackListTitle}>
Feedback History ({seriesFeedbacks.length})
</Text>
{seriesFeedbacks.length === 0 ? (
<View style={styles.emptyFeedbackState}>
<Icon name="message-circle" size={48} color={theme.colors.textMuted} />
<Text style={styles.emptyFeedbackTitle}>No Feedback Yet</Text>
<Text style={styles.emptyFeedbackSubtitle}>
No feedback has been provided for this series yet
</Text>
</View>
) : (
seriesFeedbacks.map((feedback: Feedback) => (
<View key={feedback.feedback_id} style={styles.feedbackCard}>
<View style={styles.feedbackCardHeader}>
<View style={styles.feedbackCardHeaderLeft}>
<View style={[
styles.feedbackTypeIndicator,
{ backgroundColor: feedback.is_positive ? theme.colors.success : theme.colors.error }
]}>
<Icon
name={feedback.is_positive ? "thumbs-up" : "thumbs-down"}
size={12}
color={theme.colors.background}
/>
</View>
<Text style={styles.feedbackEmail}>{feedback.email}</Text>
{isFeedbackNew(feedback.feedback_id) && (
<View style={styles.newFeedbackBadge}>
<Text style={styles.newFeedbackBadgeText}>NEW</Text>
</View>
)}
</View>
<Text style={styles.feedbackTimestamp}>
{new Date(feedback.created_at).toLocaleDateString()}
</Text>
</View>
<Text style={styles.feedbackText}>{feedback.feedback_text}</Text>
<View style={styles.feedbackCardFooter}>
<View style={[
styles.feedbackTypeBadge,
{ backgroundColor: getFeedbackTypeColor(feedback.feedback_type) }
]}>
<Text style={styles.feedbackTypeBadgeText}>
{feedback.feedback_type.replace('_', ' ').toUpperCase()}
</Text>
</View>
</View>
</View>
))
)}
</View>
</View>
);
};
// ============================================================================
// MAIN RENDER
// ============================================================================
return (
<SafeAreaView style={styles.container}>
{/* Fixed Series Header */}
{renderSeriesHeader()}
{/* Scrollable Feedback Content */}
<View style={styles.scrollableContent}>
<ScrollView
style={styles.content}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
>
{/* Feedback History from Navigation Parameters */}
{renderFeedbackHistory()}
</ScrollView>
</View>
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
// Series Header Styles
seriesHeader: {
backgroundColor: theme.colors.background,
padding: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
// Fixed Feedback Title Styles
fixedFeedbackTitle: {
backgroundColor: theme.colors.background,
padding: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
seriesHeaderTop: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.sm,
},
seriesHeaderLeft: {
flex: 1,
marginRight: theme.spacing.md,
},
seriesHeaderRight: {
alignItems: 'flex-end',
},
seriesTitle: {
fontSize: 18,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: 4,
},
seriesMeta: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
},
seriesStatusBadge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.success,
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
seriesStatusText: {
fontSize: 10,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
marginLeft: 4,
textTransform: 'uppercase',
},
patientInfoRow: {
flexDirection: 'row',
alignItems: 'center',
},
patientInfoText: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
marginLeft: theme.spacing.xs,
flex: 1,
},
patientDetailButton: {
padding: theme.spacing.xs,
marginLeft: theme.spacing.sm,
backgroundColor: theme.colors.tertiary,
borderRadius: 16,
borderWidth: 1,
borderColor: theme.colors.primary,
},
patientDetailButtonContent: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
},
patientDetailButtonText: {
fontSize: 11,
color: theme.colors.primary,
fontFamily: theme.typography.fontFamily.medium,
marginLeft: 4,
textTransform: 'uppercase',
},
// Content Styles
scrollableContent: {
flex: 1,
},
content: {
flex: 1,
},
// Section Styles
sectionTitle: {
fontSize: 18,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: theme.spacing.md,
},
// Feedback Styles
feedbackHistory: {
padding: theme.spacing.md,
},
// Feedback List Styles
feedbackList: {
marginTop: theme.spacing.sm,
},
feedbackListTitle: {
fontSize: 16,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: theme.spacing.md,
},
feedbackCard: {
backgroundColor: theme.colors.backgroundAlt,
borderRadius: 8,
padding: theme.spacing.md,
marginBottom: theme.spacing.sm,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
feedbackCardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing.sm,
},
feedbackCardHeaderLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
feedbackTypeIndicator: {
width: 20,
height: 20,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
marginRight: theme.spacing.sm,
},
feedbackEmail: {
fontSize: 12,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium,
marginRight: theme.spacing.sm,
},
newFeedbackBadge: {
backgroundColor: theme.colors.error,
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 8,
},
newFeedbackBadgeText: {
fontSize: 8,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase',
},
feedbackTimestamp: {
fontSize: 10,
color: theme.colors.textMuted,
fontFamily: theme.typography.fontFamily.regular,
},
feedbackText: {
fontSize: 14,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.regular,
lineHeight: 20,
marginBottom: theme.spacing.sm,
},
feedbackCardFooter: {
alignItems: 'flex-end',
},
feedbackTypeBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
feedbackTypeBadgeText: {
fontSize: 10,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase',
},
// Empty State Styles
emptyFeedbackState: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: theme.spacing.xl,
},
emptyFeedbackTitle: {
fontSize: 18,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginTop: theme.spacing.md,
marginBottom: theme.spacing.sm,
},
emptyFeedbackSubtitle: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
textAlign: 'center',
lineHeight: 20,
},
// Header Back Button Style
headerBackButton: {
padding: theme.spacing.sm,
marginLeft: theme.spacing.xs,
},
});
export default FeedbackDetailScreen;
/*
* End of File: FeedbackDetailScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,7 @@ import { selectUser } from '../../Auth/redux/authSelectors';
* Features: * Features:
* - Real-time patient data fetching * - Real-time patient data fetching
* - Search functionality with real-time filtering * - Search functionality with real-time filtering
* - Filter tabs (All, Processed, Pending) * - Filter tabs (All, Processed, Pending, Error)
* - Sort options (Date, Name, Processed) * - Sort options (Date, Name, Processed)
* - Pull-to-refresh functionality * - Pull-to-refresh functionality
* - Patient cards with vital information * - Patient cards with vital information
@ -151,7 +151,7 @@ const PatientsScreen: React.FC = () => {
* *
* Purpose: Update the selected filter and refresh the list * Purpose: Update the selected filter and refresh the list
*/ */
const handleFilterChange = useCallback((filter: 'all' | 'processed' | 'pending') => { const handleFilterChange = useCallback((filter: 'all' | 'processed' | 'pending' | 'error') => {
dispatch(setFilter(filter)); dispatch(setFilter(filter));
}, [dispatch]); }, [dispatch]);

View File

@ -2432,6 +2432,8 @@ const styles = StyleSheet.create({
fontSize: 12, fontSize: 12,
color: theme.colors.textSecondary, color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular, fontFamily: theme.typography.fontFamily.regular,
paddingHorizontal:theme.spacing.sm,
flex:1
}, },
detailsGrid: { detailsGrid: {
marginBottom: theme.spacing.md, marginBottom: theme.spacing.md,

View File

@ -1,3 +0,0 @@

View File

@ -54,7 +54,7 @@ export const AppInfoScreen: React.FC<AppInfoScreenProps> = ({
// App version and build information // App version and build information
const appInfo = { const appInfo = {
name: 'NeoScan Physician', name: 'NeoScan Radiologist',
version: '1.0.0', version: '1.0.0',
buildNumber: '2025.08.001', buildNumber: '2025.08.001',
releaseDate: 'August 2025', releaseDate: 'August 2025',

View File

@ -769,7 +769,7 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
{user.display_name || `${user.first_name} ${user.last_name}`} {user.display_name || `${user.first_name} ${user.last_name}`}
</Text> </Text>
<Text style={styles.profileEmail}>{user.email}</Text> <Text style={styles.profileEmail}>{user.email}</Text>
<Text style={styles.profileRole}>Physician</Text> <Text style={styles.profileRole}>Radiologist</Text>
</View> </View>
</View> </View>
</View> </View>

View File

@ -7,7 +7,6 @@
import { NavigatorScreenParams } from '@react-navigation/native'; import { NavigatorScreenParams } from '@react-navigation/native';
import { ERDashboard, Patient, Alert as AlertType } from '../shared/types'; import { ERDashboard, Patient, Alert as AlertType } from '../shared/types';
import { PatientCareStackParamList } from '../modules/PatientCare/navigation/navigationTypes';
// ============================================================================ // ============================================================================
// ROOT NAVIGATION TYPES // ROOT NAVIGATION TYPES
@ -45,7 +44,7 @@ export type RootStackParamList = {
*/ */
export type MainTabParamList = { export type MainTabParamList = {
Dashboard: DashboardScreenParams; // Dashboard with initial data Dashboard: DashboardScreenParams; // Dashboard with initial data
Patients: NavigatorScreenParams<PatientCareStackParamList>; // Patient care stack navigator Patients: PatientsScreenParams; // Patient list screen
AIPredictions: AIPredictionScreenParams; // AI predictions screen AIPredictions: AIPredictionScreenParams; // AI predictions screen
Settings: SettingsScreenParams; // Settings screen Settings: SettingsScreenParams; // Settings screen
}; };

View File

@ -13,6 +13,7 @@ import {
ScrollView, ScrollView,
TouchableOpacity, TouchableOpacity,
SafeAreaView, SafeAreaView,
StatusBar,
Image, Image,
} from 'react-native'; } from 'react-native';
import { theme } from '../../theme/theme'; import { theme } from '../../theme/theme';
@ -104,6 +105,11 @@ export const ComingSoonScreen: React.FC<ComingSoonScreenProps> = ({
styles.container, styles.container,
backgroundColor && { backgroundColor } backgroundColor && { backgroundColor }
]}> ]}>
<StatusBar
barStyle="dark-content"
backgroundColor={backgroundColor || theme.colors.background}
translucent={false}
/>
<ScrollView <ScrollView
style={styles.scrollView} style={styles.scrollView}

View File

@ -19,7 +19,6 @@ import settingsReducer from '../modules/Settings/redux/settingsSlice';
import uiReducer from '../modules/Dashboard/redux/uiSlice'; import uiReducer from '../modules/Dashboard/redux/uiSlice';
import hospitalReducer from '../modules/Auth/redux/hospitalSlice'; import hospitalReducer from '../modules/Auth/redux/hospitalSlice';
import aiPredictionReducer from '../modules/AIPrediction/redux/aiPredictionSlice'; import aiPredictionReducer from '../modules/AIPrediction/redux/aiPredictionSlice';
import predictionsReducer from '../modules/Dashboard/redux/predictionsSlice';
// ============================================================================ // ============================================================================
// REDUX PERSIST CONFIGURATION // REDUX PERSIST CONFIGURATION
@ -60,7 +59,6 @@ const persistConfig = {
'alerts', // Temporary alerts and notifications 'alerts', // Temporary alerts and notifications
'dashboard', // Real-time dashboard data 'dashboard', // Real-time dashboard data
'aiDashboard', // AI dashboard statistics (fetched fresh each time) 'aiDashboard', // AI dashboard statistics (fetched fresh each time)
'predictions', // AI predictions data (fetched fresh each time)
'hospital', // Hospital data (fetched fresh each time) 'hospital', // Hospital data (fetched fresh each time)
], ],
@ -92,7 +90,6 @@ const persistConfig = {
* - aiDashboard: AI analysis dashboard statistics * - aiDashboard: AI analysis dashboard statistics
* - patientCare: Patient information and medical records * - patientCare: Patient information and medical records
* - aiPrediction: AI prediction cases and analysis * - aiPrediction: AI prediction cases and analysis
* - predictions: AI predictions with/without feedback
* - alerts: Critical alerts and notifications * - alerts: Critical alerts and notifications
* - settings: User preferences and app settings * - settings: User preferences and app settings
* - ui: User interface state (loading, modals, etc.) * - ui: User interface state (loading, modals, etc.)
@ -103,7 +100,6 @@ const rootReducer = combineReducers({
aiDashboard: aiDashboardReducer, aiDashboard: aiDashboardReducer,
patientCare: patientCareReducer, patientCare: patientCareReducer,
aiPrediction: aiPredictionReducer, aiPrediction: aiPredictionReducer,
predictions: predictionsReducer,
alerts: alertsReducer, alerts: alertsReducer,
settings: settingsReducer, settings: settingsReducer,
ui: uiReducer, ui: uiReducer,

View File

@ -38,7 +38,7 @@ export const shadows = {
}, },
primary: { primary: {
shadowColor: colors.primary, shadowColor: colors.primary,
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3, shadowOpacity: 0.3,
shadowRadius: 8, shadowRadius: 8,
elevation: 6, elevation: 6,

View File

@ -7,7 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0C80B921A6F3F58F76C31292 /* libPods-NeoScan_Physician.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-NeoScan_Physician.a */; }; 0C80B921A6F3F58F76C31292 /* libPods-NeoScan_Radiologist.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-NeoScan_Radiologist.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
@ -27,21 +27,21 @@
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1; proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A; remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = NeoScan_Physician; remoteInfo = NeoScan_Radiologist;
}; };
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* NeoScan_Physician.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NeoScan_Physician.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* NeoScan_Radiologist.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NeoScan_Radiologist.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NeoScan_Physician/Images.xcassets; sourceTree = "<group>"; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NeoScan_Radiologist/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = NeoScan_Physician/Info.plist; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = NeoScan_Radiologist/Info.plist; sourceTree = "<group>"; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = NeoScan_Physician/PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = NeoScan_Radiologist/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
3B4392A12AC88292D35C810B /* Pods-NeoScan_Physician.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeoScan_Physician.debug.xcconfig"; path = "Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician.debug.xcconfig"; sourceTree = "<group>"; }; 3B4392A12AC88292D35C810B /* Pods-NeoScan_Radiologist.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeoScan_Radiologist.debug.xcconfig"; path = "Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist.debug.xcconfig"; sourceTree = "<group>"; };
5709B34CF0A7D63546082F79 /* Pods-NeoScan_Physician.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeoScan_Physician.release.xcconfig"; path = "Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician.release.xcconfig"; sourceTree = "<group>"; }; 5709B34CF0A7D63546082F79 /* Pods-NeoScan_Radiologist.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeoScan_Radiologist.release.xcconfig"; path = "Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist.release.xcconfig"; sourceTree = "<group>"; };
5DCACB8F33CDC322A6C60F78 /* libPods-NeoScan_Physician.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NeoScan_Physician.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5DCACB8F33CDC322A6C60F78 /* libPods-NeoScan_Radiologist.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NeoScan_Radiologist.a"; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NeoScan_Physician/AppDelegate.swift; sourceTree = "<group>"; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NeoScan_Radiologist/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NeoScan_Physician/LaunchScreen.storyboard; sourceTree = "<group>"; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NeoScan_Radiologist/LaunchScreen.storyboard; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
6606F41B1382422DA695F61C /* WorkSans-Bold.ttf */ = {isa = PBXFileReference; name = "WorkSans-Bold.ttf"; path = "../app/assets/fonts/WorkSans-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; 6606F41B1382422DA695F61C /* WorkSans-Bold.ttf */ = {isa = PBXFileReference; name = "WorkSans-Bold.ttf"; path = "../app/assets/fonts/WorkSans-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
1569DC7537534ED39A79EE9E /* WorkSans-ExtraBold.ttf */ = {isa = PBXFileReference; name = "WorkSans-ExtraBold.ttf"; path = "../app/assets/fonts/WorkSans-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; 1569DC7537534ED39A79EE9E /* WorkSans-ExtraBold.ttf */ = {isa = PBXFileReference; name = "WorkSans-ExtraBold.ttf"; path = "../app/assets/fonts/WorkSans-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
@ -58,7 +58,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0C80B921A6F3F58F76C31292 /* libPods-NeoScan_Physician.a in Frameworks */, 0C80B921A6F3F58F76C31292 /* libPods-NeoScan_Radiologist.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -73,7 +73,7 @@
name = "Supporting Files"; name = "Supporting Files";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
13B07FAE1A68108700A75B9A /* NeoScan_Physician */ = { 13B07FAE1A68108700A75B9A /* NeoScan_Radiologist */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB51A68108700A75B9A /* Images.xcassets */,
@ -82,14 +82,14 @@
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
); );
name = NeoScan_Physician; name = NeoScan_Radiologist;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
2D16E6871FA4F8E400B85C8A /* Frameworks */ = { 2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
5DCACB8F33CDC322A6C60F78 /* libPods-NeoScan_Physician.a */, 5DCACB8F33CDC322A6C60F78 /* libPods-NeoScan_Radiologist.a */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -104,7 +104,7 @@
83CBB9F61A601CBA00E9B192 = { 83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
13B07FAE1A68108700A75B9A /* NeoScan_Physician */, 13B07FAE1A68108700A75B9A /* NeoScan_Radiologist */,
832341AE1AAA6A7D00B99B32 /* Libraries */, 832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */, 83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */, 2D16E6871FA4F8E400B85C8A /* Frameworks */,
@ -119,7 +119,7 @@
83CBBA001A601CBA00E9B192 /* Products */ = { 83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
13B07F961A680F5B00A75B9A /* NeoScan_Physician.app */, 13B07F961A680F5B00A75B9A /* NeoScan_Radiologist.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -127,8 +127,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = { BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3B4392A12AC88292D35C810B /* Pods-NeoScan_Physician.debug.xcconfig */, 3B4392A12AC88292D35C810B /* Pods-NeoScan_Radiologist.debug.xcconfig */,
5709B34CF0A7D63546082F79 /* Pods-NeoScan_Physician.release.xcconfig */, 5709B34CF0A7D63546082F79 /* Pods-NeoScan_Radiologist.release.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@ -152,9 +152,9 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
13B07F861A680F5B00A75B9A /* NeoScan_Physician */ = { 13B07F861A680F5B00A75B9A /* NeoScan_Radiologist */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NeoScan_Physician" */; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NeoScan_Radiologist" */;
buildPhases = ( buildPhases = (
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */, 13B07F871A680F5B00A75B9A /* Sources */,
@ -168,9 +168,9 @@
); );
dependencies = ( dependencies = (
); );
name = NeoScan_Physician; name = NeoScan_Radiologist;
productName = NeoScan_Physician; productName = NeoScan_Radiologist;
productReference = 13B07F961A680F5B00A75B9A /* NeoScan_Physician.app */; productReference = 13B07F961A680F5B00A75B9A /* NeoScan_Radiologist.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -186,7 +186,7 @@
}; };
}; };
}; };
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "NeoScan_Physician" */; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "NeoScan_Radiologist" */;
compatibilityVersion = "Xcode 12.0"; compatibilityVersion = "Xcode 12.0";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
@ -199,7 +199,7 @@
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
13B07F861A680F5B00A75B9A /* NeoScan_Physician */, 13B07F861A680F5B00A75B9A /* NeoScan_Radiologist */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -254,15 +254,15 @@
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
@ -280,7 +280,7 @@
outputFileListPaths = ( outputFileListPaths = (
); );
outputPaths = ( outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-NeoScan_Physician-checkManifestLockResult.txt", "$(DERIVED_FILE_DIR)/Pods-NeoScan_Radiologist-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@ -293,15 +293,15 @@
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Physician/Pods-NeoScan_Physician-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeoScan_Radiologist/Pods-NeoScan_Radiologist-resources.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
@ -320,7 +320,7 @@
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = { 00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* NeoScan_Physician */; target = 13B07F861A680F5B00A75B9A /* NeoScan_Radiologist */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
}; };
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
@ -328,13 +328,13 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = { 13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-NeoScan_Physician.debug.xcconfig */; baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-NeoScan_Radiologist.debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = NeoScan_Physician/Info.plist; INFOPLIST_FILE = NeoScan_Radiologist/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -347,7 +347,7 @@
"-lc++", "-lc++",
); );
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = NeoScan_Physician; PRODUCT_NAME = NeoScan_Radiologist;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -356,12 +356,12 @@
}; };
13B07F951A680F5B00A75B9A /* Release */ = { 13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-NeoScan_Physician.release.xcconfig */; baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-NeoScan_Radiologist.release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = NeoScan_Physician/Info.plist; INFOPLIST_FILE = NeoScan_Radiologist/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -374,7 +374,7 @@
"-lc++", "-lc++",
); );
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = NeoScan_Physician; PRODUCT_NAME = NeoScan_Radiologist;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
@ -522,7 +522,7 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NeoScan_Physician" */ = { 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "NeoScan_Radiologist" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */, 13B07F941A680F5B00A75B9A /* Debug */,
@ -531,7 +531,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "NeoScan_Physician" */ = { 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "NeoScan_Radiologist" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */, 83CBBA201A601CBA00E9B192 /* Debug */,

View File

@ -15,9 +15,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "NeoScan_Physician.app" BuildableName = "NeoScan_Radiologist.app"
BlueprintName = "NeoScan_Physician" BlueprintName = "NeoScan_Radiologist"
ReferencedContainer = "container:NeoScan_Physician.xcodeproj"> ReferencedContainer = "container:NeoScan_Radiologist.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
@ -35,7 +35,7 @@
BlueprintIdentifier = "00E356ED1AD99517003FC87E" BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "NeoScan_PhysicianTests.xctest" BuildableName = "NeoScan_PhysicianTests.xctest"
BlueprintName = "NeoScan_PhysicianTests" BlueprintName = "NeoScan_PhysicianTests"
ReferencedContainer = "container:NeoScan_Physician.xcodeproj"> ReferencedContainer = "container:NeoScan_Radiologist.xcodeproj">
</BuildableReference> </BuildableReference>
</TestableReference> </TestableReference>
</Testables> </Testables>
@ -55,9 +55,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "NeoScan_Physician.app" BuildableName = "NeoScan_Radiologist.app"
BlueprintName = "NeoScan_Physician" BlueprintName = "NeoScan_Radiologist"
ReferencedContainer = "container:NeoScan_Physician.xcodeproj"> ReferencedContainer = "container:NeoScan_Radiologist.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</LaunchAction> </LaunchAction>
@ -72,9 +72,9 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A" BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "NeoScan_Physician.app" BuildableName = "NeoScan_Radiologist.app"
BlueprintName = "NeoScan_Physician" BlueprintName = "NeoScan_Radiologist"
ReferencedContainer = "container:NeoScan_Physician.xcodeproj"> ReferencedContainer = "container:NeoScan_Radiologist.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</ProfileAction> </ProfileAction>

View File

@ -24,7 +24,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window = UIWindow(frame: UIScreen.main.bounds) window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative( factory.startReactNative(
withModuleName: "NeoScan_Physician", withModuleName: "NeoScan_Radiologist",
in: window, in: window,
launchOptions: launchOptions launchOptions: launchOptions
) )

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>NeoScan_Physician</string> <string>NeoScan_Radiologist</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@ -16,7 +16,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="NeoScan_Physician" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="NeoScan_Radiologist" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="202" width="375" height="43"/> <rect key="frame" x="0.0" y="202" width="375" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>

View File

@ -14,7 +14,7 @@ if linkage != nil
use_frameworks! :linkage => linkage.to_sym use_frameworks! :linkage => linkage.to_sym
end end
target 'NeoScan_Physician' do target 'NeoScan_Radiologist' do
config = use_native_modules! config = use_native_modules!
use_react_native!( use_react_native!(

4
package-lock.json generated
View File

@ -1,11 +1,11 @@
{ {
"name": "NeoScan_Physician", "name": "NeoScan_Radiologist",
"version": "0.0.1", "version": "0.0.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "NeoScan_Physician", "name": "NeoScan_Radiologist",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@react-native-async-storage/async-storage": "^2.1.0", "@react-native-async-storage/async-storage": "^2.1.0",

View File

@ -1,5 +1,5 @@
{ {
"name": "NeoScan_Physician", "name": "NeoScan_Radiologist",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {