Compare commits
No commits in common. "0a955502303c7d40506f2b61413309994bd85e39" and "d0fe2eaa3428a60068d2a988aad776eb81b1d790" have entirely different histories.
0a95550230
...
d0fe2eaa34
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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**
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 227 KiB |
@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Physician</string>
|
<string name="app_name">Radiologist</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -4,6 +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>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
9
android/app/src/main/res/xml/network_security_config.xml
Normal 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>
|
||||||
@ -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')
|
||||||
|
|||||||
4
app.json
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "NeoScan_Physician",
|
"name": "NeoScan_Radiologist",
|
||||||
"displayName": "NeoScan_Physician"
|
"displayName": "NeoScan_Radiologist"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { StatusBar } from 'react-native';
|
import { StatusBar } from 'react-native';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native';
|
import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native';
|
||||||
import { theme } from './theme/theme';
|
import { theme } from './theme/theme';
|
||||||
import { RootStackNavigator, setNavigationRef } from './navigation';
|
import { RootStackNavigator, setNavigationRef } from './navigation';
|
||||||
@ -184,6 +184,7 @@ function AppContent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
|
<SafeAreaView style={{flex:1}} edges={['top', 'left', 'right']} >
|
||||||
<NavigationContainer ref={navigationRef}>
|
<NavigationContainer ref={navigationRef}>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
barStyle="dark-content" // Dark text on light background
|
barStyle="dark-content" // Dark text on light background
|
||||||
@ -195,6 +196,7 @@ function AppContent() {
|
|||||||
bottomOffset={20}
|
bottomOffset={20}
|
||||||
/>
|
/>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
</SafeAreaView>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import {
|
|||||||
TouchableWithoutFeedback,
|
TouchableWithoutFeedback,
|
||||||
Alert,
|
Alert,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
StatusBar,
|
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
@ -541,7 +540,6 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
|
|
||||||
|
|
||||||
<TouchableWithoutFeedback onPress={closeAllDropdowns} disabled={!showSuggestionTypeDropdown && !showPriorityDropdown}>
|
<TouchableWithoutFeedback onPress={closeAllDropdowns} disabled={!showSuggestionTypeDropdown && !showPriorityDropdown}>
|
||||||
<View style={{flex:1}}>
|
<View style={{flex:1}}>
|
||||||
|
|||||||
@ -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,8 +551,7 @@ const AIPredictionsScreen: React.FC<AIPredictionsScreenProps> = ({ navigation })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
|
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.headerTitle}>AI Predictions</Text>
|
<Text style={styles.headerTitle}>AI Predictions</Text>
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
StatusBar,
|
|
||||||
Alert,
|
Alert,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
@ -226,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 = {
|
||||||
@ -409,10 +408,6 @@ const SignUpScreen: React.FC<SignUpScreenProps> = ({ navigation }) => {
|
|||||||
style={styles.container}
|
style={styles.container}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
>
|
>
|
||||||
<StatusBar
|
|
||||||
barStyle="dark-content"
|
|
||||||
backgroundColor={theme.colors.background}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Conditional Content Rendering */}
|
{/* Conditional Content Rendering */}
|
||||||
{currentStep === 'hospital' ? (
|
{currentStep === 'hospital' ? (
|
||||||
|
|||||||
@ -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.
|
|
||||||
*/
|
|
||||||
@ -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.
|
|
||||||
*/
|
|
||||||
@ -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';
|
|
||||||
@ -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.
|
|
||||||
*/
|
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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.
|
|
||||||
*/
|
|
||||||
@ -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',
|
||||||
|
|||||||
@ -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.
|
|
||||||
*/
|
|
||||||
@ -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.
|
|
||||||
*/
|
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,634 +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,
|
|
||||||
StatusBar,
|
|
||||||
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}>
|
|
||||||
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
|
|
||||||
|
|
||||||
{/* 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.
|
|
||||||
*/
|
|
||||||
@ -13,7 +13,6 @@ import {
|
|||||||
FlatList,
|
FlatList,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
StatusBar,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Alert,
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
@ -65,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
|
||||||
@ -152,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]);
|
||||||
|
|
||||||
@ -262,7 +261,6 @@ const PatientsScreen: React.FC = () => {
|
|||||||
if (error && !isLoading) {
|
if (error && !isLoading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
|
|
||||||
<View style={styles.errorContainer}>
|
<View style={styles.errorContainer}>
|
||||||
<Text style={styles.errorTitle}>Error Loading Patients</Text>
|
<Text style={styles.errorTitle}>Error Loading Patients</Text>
|
||||||
<Text style={styles.errorMessage}>{error}</Text>
|
<Text style={styles.errorMessage}>{error}</Text>
|
||||||
@ -280,7 +278,6 @@ const PatientsScreen: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
|
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
StatusBar,
|
|
||||||
Alert,
|
Alert,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Image,
|
Image,
|
||||||
@ -1535,7 +1534,6 @@ const SeriesDetailScreen: React.FC<SeriesDetailScreenProps> = ({ navigation, rou
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
|
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
@ -2434,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,
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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',
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 */,
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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"/>
|
||||||
|
|||||||
@ -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
@ -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",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "NeoScan_Physician",
|
"name": "NeoScan_Radiologist",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||