diff --git a/android/app/build.gradle b/android/app/build.gradle index 0c8fa09..e937186 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -71,6 +71,7 @@ def enableProguardInReleaseBuilds = false * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ +def enableSeparateBuildPerCPUArchitecture = true def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' android { @@ -79,6 +80,13 @@ android { compileSdk rootProject.ext.compileSdkVersion namespace "com.neoscan_physician" + splits { + abi { + enable true + include 'armeabi-v7a', 'arm64-v8a', 'x86' + universalApk false + } + } defaultConfig { applicationId "com.neoscan_physician" minSdkVersion rootProject.ext.minSdkVersion diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index a2f5908..1d5d02a 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 1b52399..1d5d02a 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index ff10afd..fed7c12 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 115a4c7..fed7c12 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index dcd3cd8..0d78f7e 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 459ca60..0d78f7e 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 8ca12fe..8ef759a 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 8e19b41..8ef759a 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b824ebd..b4c03d7 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 4c19a13..b4c03d7 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/playstore.png b/android/app/src/main/res/playstore.png new file mode 100644 index 0000000..bbab458 Binary files /dev/null and b/android/app/src/main/res/playstore.png differ diff --git a/android/gradle.properties b/android/gradle.properties index 5e24e3a..9fb1566 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -32,7 +32,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=true +newArchEnabled=false # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. diff --git a/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx b/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx index 51fbb3d..5786f7d 100644 --- a/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx +++ b/app/modules/Auth/components/signup/EmailAlreadyRegisteredModal.tsx @@ -163,7 +163,7 @@ const styles = StyleSheet.create({ textAlign: 'center', }, subtitle: { - fontSize: theme.typography.bodyMedium, + fontSize: theme.typography.fontSize.bodyMedium, color: theme.colors.textSecondary, textAlign: 'center', }, @@ -171,25 +171,25 @@ const styles = StyleSheet.create({ marginBottom: theme.spacing.xl, }, message: { - fontSize: theme.typography.bodyMedium, + fontSize: theme.typography.fontSize.bodyMedium, color: theme.colors.textPrimary, marginBottom: theme.spacing.md, + fontFamily: theme.typography.fontFamily.regular, }, optionsContainer: { marginLeft: theme.spacing.sm, }, optionText: { - fontSize: theme.typography.bodyMedium, + fontSize: theme.typography.fontSize.bodyMedium, color: theme.colors.textSecondary, marginBottom: theme.spacing.xs, + fontFamily: theme.typography.fontFamily.regular, }, actions: { - flexDirection: 'row', - justifyContent: 'space-between', + flexDirection: 'column', // Changed from 'row' to 'column' gap: theme.spacing.md, }, secondaryButton: { - flex: 1, backgroundColor: theme.colors.background, borderWidth: 1, borderColor: theme.colors.border, @@ -199,12 +199,11 @@ const styles = StyleSheet.create({ alignItems: 'center', }, secondaryButtonText: { - fontSize: theme.typography.bodyMedium, + fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, }, primaryButton: { - flex: 1, backgroundColor: theme.colors.primary, borderRadius: theme.borderRadius.medium, paddingVertical: theme.spacing.md, @@ -217,7 +216,7 @@ const styles = StyleSheet.create({ elevation: 3, }, primaryButtonText: { - fontSize: theme.typography.bodyMedium, + fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, }, diff --git a/app/modules/Auth/redux/authActions.ts b/app/modules/Auth/redux/authActions.ts index 977d6be..d603953 100644 --- a/app/modules/Auth/redux/authActions.ts +++ b/app/modules/Auth/redux/authActions.ts @@ -8,7 +8,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { logout, updateUserProfile } from './authSlice'; import { authAPI } from '../services/authAPI'; -import { showError, showSuccess } from '../../../shared/utils/toast'; +import { showError, showSuccess, showWarning } from '../../../shared/utils/toast'; /** * Thunk to login user @@ -31,6 +31,10 @@ export const login = createAsyncThunk( if (response.ok && response.data && response.data.data) { // Return the user data for the fulfilled case + if(response.data.data.user.dashboard_role !=='radiologist'){ + showWarning('You are not authorized to access this application') + return rejectWithValue('Not Authorized'); + } return {...response.data.data.user,access_token:response.data.data.access_token}; } else { const errorMessage = response.data?.message || response.problem || 'Unknown error'; diff --git a/app/modules/PatientCare/components/EmptyState.tsx b/app/modules/PatientCare/components/EmptyState.tsx index 94e003c..f30673d 100644 --- a/app/modules/PatientCare/components/EmptyState.tsx +++ b/app/modules/PatientCare/components/EmptyState.tsx @@ -114,16 +114,15 @@ const styles = StyleSheet.create({ }, title: { fontSize: 20, - fontWeight: 'bold', color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.bold, textAlign: 'center', marginBottom: theme.spacing.sm, }, subtitle: { fontSize: 16, color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.regular, textAlign: 'center', lineHeight: 24, marginBottom: theme.spacing.lg, @@ -146,9 +145,8 @@ const styles = StyleSheet.create({ }, retryText: { fontSize: 16, - fontWeight: '600', color: theme.colors.background, - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.bold, }, }); diff --git a/app/modules/PatientCare/components/FilterTabs.tsx b/app/modules/PatientCare/components/FilterTabs.tsx index 825c623..7fe0143 100644 --- a/app/modules/PatientCare/components/FilterTabs.tsx +++ b/app/modules/PatientCare/components/FilterTabs.tsx @@ -267,12 +267,11 @@ const styles = StyleSheet.create({ }, tabLabel: { fontSize: 14, - fontWeight: '500', - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.regular, flex: 1, }, tabLabelSelected: { - fontWeight: '600', + fontFamily: theme.typography.fontFamily.bold, }, // Count Badge Styles @@ -293,7 +292,7 @@ const styles = StyleSheet.create({ }, countText: { fontSize: 12, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, textAlign: 'center', }, countTextSelected: { diff --git a/app/modules/PatientCare/components/ImageViewer.tsx b/app/modules/PatientCare/components/ImageViewer.tsx index 3f4f634..3ea3669 100644 --- a/app/modules/PatientCare/components/ImageViewer.tsx +++ b/app/modules/PatientCare/components/ImageViewer.tsx @@ -358,7 +358,6 @@ const styles = StyleSheet.create({ }, patientName: { fontSize: 16, - fontWeight: 'bold', color: theme.colors.background, fontFamily: theme.typography.fontFamily.bold, }, @@ -448,7 +447,6 @@ const styles = StyleSheet.create({ zoomText: { color: theme.colors.background, fontSize: 12, - fontWeight: 'bold', fontFamily: theme.typography.fontFamily.bold, }, diff --git a/app/modules/PatientCare/components/LoadingState.tsx b/app/modules/PatientCare/components/LoadingState.tsx index 806ea7f..5c5b3b9 100644 --- a/app/modules/PatientCare/components/LoadingState.tsx +++ b/app/modules/PatientCare/components/LoadingState.tsx @@ -139,16 +139,15 @@ const styles = StyleSheet.create({ }, title: { fontSize: 20, - fontWeight: 'bold', color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.bold, textAlign: 'center', marginBottom: theme.spacing.sm, }, subtitle: { fontSize: 16, color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.regular, textAlign: 'center', lineHeight: 24, }, @@ -166,7 +165,7 @@ const styles = StyleSheet.create({ smallText: { fontSize: 14, color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.primary, + fontFamily: theme.typography.fontFamily.regular, }, }); diff --git a/app/modules/PatientCare/components/PatientCard.tsx b/app/modules/PatientCare/components/PatientCard.tsx index 889120b..8431994 100644 --- a/app/modules/PatientCare/components/PatientCard.tsx +++ b/app/modules/PatientCare/components/PatientCard.tsx @@ -342,7 +342,6 @@ const styles = StyleSheet.create({ }, patientName: { fontSize: 18, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, @@ -365,7 +364,7 @@ const styles = StyleSheet.create({ }, statusText: { fontSize: 10, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, marginLeft: 4, textTransform: 'uppercase', }, @@ -381,7 +380,7 @@ const styles = StyleSheet.create({ }, emergencyButtonText: { fontSize: 10, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, marginLeft: 4, }, @@ -407,16 +406,16 @@ const styles = StyleSheet.create({ color: theme.colors.textMuted, marginBottom: 2, textTransform: 'uppercase', - fontWeight: '500', + fontFamily: theme.typography.fontFamily.regular, }, infoValue: { fontSize: 14, - fontWeight: '600', + fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, textAlign: 'center', }, modalityText: { - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, }, // Institution Row @@ -446,14 +445,14 @@ const styles = StyleSheet.create({ }, seriesLabel: { fontSize: 12, - fontWeight: '500', + fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, marginLeft: 4, }, seriesText: { fontSize: 14, color: theme.colors.textPrimary, - fontWeight: '500', + fontFamily: theme.typography.fontFamily.regular, }, // Footer Section @@ -484,7 +483,7 @@ const styles = StyleSheet.create({ fontSize: 12, color: theme.colors.textSecondary, marginRight: theme.spacing.xs, - fontWeight: '500', + fontFamily: theme.typography.fontFamily.regular, }, }); diff --git a/app/modules/PatientCare/navigation/PatientCareStackNavigator.tsx b/app/modules/PatientCare/navigation/PatientCareStackNavigator.tsx index 483a035..6d7a68b 100644 --- a/app/modules/PatientCare/navigation/PatientCareStackNavigator.tsx +++ b/app/modules/PatientCare/navigation/PatientCareStackNavigator.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; // Import screens -import { PatientsScreen, PatientDetailsScreen } from '../screens'; +import { PatientsScreen, PatientDetailsScreen, SeriesDetailScreen } from '../screens'; // Import types import { PatientCareStackParamList } from './navigationTypes'; @@ -28,9 +28,10 @@ const Stack = createStackNavigator(); * Screens: * - PatientsScreen: Main patient list screen * - PatientDetailsScreen: Detailed patient information and DICOM images + * - SeriesDetailScreen: Detailed series information with predictions and feedback * * Navigation Flow: - * PatientsScreen → PatientDetailsScreen (with patient data) + * PatientsScreen → PatientDetailsScreen (with patient data) → SeriesDetailScreen (with series data) */ const PatientCareStackNavigator: React.FC = () => { return ( @@ -63,6 +64,17 @@ const PatientCareStackNavigator: React.FC = () => { gestureDirection: 'horizontal', }} /> + + {/* Series Detail Screen - Detailed series information with predictions and feedback */} + ); }; diff --git a/app/modules/PatientCare/navigation/navigationTypes.ts b/app/modules/PatientCare/navigation/navigationTypes.ts index 45588f6..4a92bef 100644 --- a/app/modules/PatientCare/navigation/navigationTypes.ts +++ b/app/modules/PatientCare/navigation/navigationTypes.ts @@ -24,6 +24,9 @@ export type PatientCareStackParamList = { // Patient Details Screen - Comprehensive patient information and DICOM images PatientDetails: PatientDetailsScreenParams; + + // Series Detail Screen - Detailed series information with predictions and feedback + SeriesDetail: SeriesDetailScreenParams; }; // ============================================================================ @@ -56,6 +59,30 @@ export interface PatientDetailsScreenParams { patientName?: string; } +/** + * SeriesDetailScreenParams + * + * Purpose: Parameters for the series 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 + * - onFeedbackSubmitted: Optional callback to refresh parent screen data + */ +export interface SeriesDetailScreenParams { + patientId: string; + patientName: string; + seriesNumber: string; + seriesData: any; + patientData: any; + // 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 // ============================================================================ @@ -91,6 +118,16 @@ export interface PatientDetailsScreenProps { }; } +/** + * SeriesDetailScreenProps - Props for SeriesDetailScreen component + */ +export interface SeriesDetailScreenProps { + navigation: PatientCareNavigationProp; + route: { + params: SeriesDetailScreenParams; + }; +} + // ============================================================================ // NAVIGATION UTILITY TYPES // ============================================================================ diff --git a/app/modules/PatientCare/screens/PatientDetailsScreen.tsx b/app/modules/PatientCare/screens/PatientDetailsScreen.tsx index 251b3b2..bc937c1 100644 --- a/app/modules/PatientCare/screens/PatientDetailsScreen.tsx +++ b/app/modules/PatientCare/screens/PatientDetailsScreen.tsx @@ -37,6 +37,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { patientAPI } from '../services/patientAPI'; import { selectUser } from '../../Auth/redux/authSelectors'; import { API_CONFIG } from '../../../shared/utils'; +import { PatientDetailsScreenProps } from '../navigation/navigationTypes'; // Get screen dimensions const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); @@ -45,15 +46,7 @@ const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); // INTERFACES // ============================================================================ -interface PatientDetailsScreenProps { - navigation: any; - route: { - params: { - patientId: string; - patientName?: string; - }; - }; -} + interface PatientInfo { name: string; @@ -149,21 +142,8 @@ const PatientDetailsScreen: React.FC = ({ navigation, const [showFullImage, setShowFullImage] = useState(false); const [activeTab, setActiveTab] = useState<'overview' | 'aiAnalysis' | 'history'>('overview'); - // Feedback state - const [showFeedbackModal, setShowFeedbackModal] = useState(false); - const [selectedSeriesForFeedback, setSelectedSeriesForFeedback] = useState(null); - const [selectedPrediction, setSelectedPrediction] = useState(null); - const [feedbackText, setFeedbackText] = useState(''); - const [isPositive, setIsPositive] = useState(null); - const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false); - - // Feedback result modal state - const [showFeedbackResultModal, setShowFeedbackResultModal] = useState(false); - const [feedbackResult, setFeedbackResult] = useState<{ - type: 'success' | 'error'; - title: string; - message: string; - } | null>(null); + // Navigation state + const [selectedSeriesForDetail, setSelectedSeriesForDetail] = useState(null); // ============================================================================ // DATA FETCHING @@ -348,115 +328,29 @@ const PatientDetailsScreen: React.FC = ({ navigation, }, [navigation]); // ============================================================================ - // FEEDBACK HANDLERS + // NAVIGATION HANDLERS // ============================================================================ /** - * Handle Open Feedback Modal + * Handle Navigate to Series Detail * - * Purpose: Open feedback modal for a specific series and prediction + * Purpose: Navigate to detailed series view * - * @param series - Series data for feedback - * @param prediction - Prediction data for feedback + * @param series - Series data to view in detail */ - const handleOpenFeedback = useCallback((series: SeriesSummary, prediction: Prediction) => { - setSelectedSeriesForFeedback(series); - setSelectedPrediction(prediction); - setFeedbackText(''); - setIsPositive(null); - setShowFeedbackModal(true); - }, []); - - /** - * Handle Submit Feedback - * - * Purpose: Submit feedback to API - */ - const handleSubmitFeedback = useCallback(async () => { - if ( !selectedPrediction || !feedbackText.trim() || isPositive === null) { - setFeedbackResult({ - type: 'error', - title: 'Validation Error', - message: 'Please provide all required feedback information' - }); - setShowFeedbackResultModal(true); - return; - } - - try { - setIsSubmittingFeedback(true); - - if (!patientData?.patid) { - throw new Error('Patient ID not available'); - } - - const feedbackPayload = { - patid: patientData.patid, - prediction_id: selectedPrediction.id, - feedback_text: feedbackText.trim(), - is_positive: isPositive - }; - - console.log('Submitting feedback payload:', feedbackPayload); - - // Call the actual API - const response = await patientAPI.submitFeedback(feedbackPayload, user?.access_token); - console.log('update response', response); - - if (!response.ok) { - throw new Error(response.problem || 'Failed to submit feedback'); - } - - // Show success message - setFeedbackResult({ - type: 'success', - title: 'Feedback Submitted', - message: 'Your feedback has been recorded successfully.' - }); - setShowFeedbackResultModal(true); - } catch (error: any) { - setFeedbackResult({ - type: 'error', - title: 'Error', - message: error.message || 'Failed to submit feedback. Please try again.' - }); - setShowFeedbackResultModal(true); - } finally { - setIsSubmittingFeedback(false); - } - }, [selectedSeriesForFeedback, selectedPrediction, feedbackText, isPositive, patientData?.patid]); - - /** - * Handle Close Feedback Modal - * - * Purpose: Close feedback modal and reset state - */ - const handleCloseFeedback = useCallback(() => { - setShowFeedbackModal(false); - setSelectedSeriesForFeedback(null); - setSelectedPrediction(null); - setFeedbackText(''); - setIsPositive(null); - }, []); - - /** - * Handle Feedback Result Modal Close - * - * Purpose: Close feedback result modal and reset form if success - */ - const handleFeedbackResultClose = useCallback(() => { - setShowFeedbackResultModal(false); - setFeedbackResult(null); + const handleNavigateToSeriesDetail = useCallback((series: SeriesSummary) => { + if (!patientData) return; - // If it was a success, also close the feedback modal and reset form - if (feedbackResult?.type === 'success') { - setShowFeedbackModal(false); - setSelectedSeriesForFeedback(null); - setSelectedPrediction(null); - setFeedbackText(''); - setIsPositive(null); - } - }, [feedbackResult?.type]); + navigation.navigate('SeriesDetail', { + patientId: patientData.patid, + patientName: patientData.patient_info.name, + seriesNumber: series.series_num, + seriesData: series, + patientData: patientData, + // Pass the refresh function as callback so parent screen can update when feedback is submitted + onFeedbackSubmitted: fetchPatientData + }); + }, [navigation, patientData, fetchPatientData]); // ============================================================================ // UTILITY FUNCTIONS @@ -810,12 +704,12 @@ const PatientDetailsScreen: React.FC = ({ navigation, Series {series.series_num}: {series.series_description} handleOpenFeedback(series, seriesPredictions[0])} + style={styles.seriesDetailButton} + onPress={() => handleNavigateToSeriesDetail(series)} activeOpacity={0.7} > - - Feedback + + Series Details @@ -1098,150 +992,8 @@ const PatientDetailsScreen: React.FC = ({ navigation, {activeTab === 'history' && renderHistoryTab()} - {/* Feedback Modal: Allows physicians to provide clinical feedback on AI predictions and DICOM images */} - {showFeedbackModal && selectedSeriesForFeedback && ( - - - - Provide Feedback - - - - - - {/* - - Series {selectedSeriesForFeedback.series_num}: {selectedSeriesForFeedback.series_description} - - - {selectedSeriesForFeedback.modality} • {selectedSeriesForFeedback.total_images} images - - */} - - {/* Series and Prediction Info */} - {selectedPrediction && ( - - - AI Prediction: {selectedPrediction.prediction.label} - - - Confidence: {(selectedPrediction.prediction.confidence_score * 100).toFixed(1)}% • - Type: {selectedPrediction.prediction.finding_type} - - - )} - - {/* Prediction Accuracy Selection */} - - Is this prediction accurate? - - {[ - { key: 'true', label: 'Yes (Positive)', color: theme.colors.success, icon: 'check-circle', value: true }, - { key: 'false', label: 'No (Negative)', color: theme.colors.error, icon: 'x-circle', value: false } - ].map((option) => ( - setIsPositive(option.value)} - style={[ - styles.predictionAccuracyButton, - isPositive === option.value && styles.predictionAccuracyButtonActive, - { borderColor: option.color } - ]} - > - - - {option.label} - - - ))} - - - - {/* Feedback Text Input */} - - Your Feedback - - - - - - - Cancel - - - Submit Feedback - - - - - )} - - {/* Feedback Result Modal: Shows success/error messages for feedback submission */} - {showFeedbackResultModal && feedbackResult && ( - - - - - - {feedbackResult.title} - - - - - - {feedbackResult.message} - - - - - - OK - - - - - )} ); }; @@ -1276,7 +1028,6 @@ const styles = StyleSheet.create({ }, headerTitleText: { fontSize: 18, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, @@ -1320,7 +1071,6 @@ const styles = StyleSheet.create({ }, patientName: { fontSize: 20, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: 4, @@ -1348,7 +1098,7 @@ const styles = StyleSheet.create({ }, statusText: { fontSize: 12, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, textTransform: 'uppercase', }, @@ -1368,7 +1118,7 @@ const styles = StyleSheet.create({ emergencyButtonText: { color: theme.colors.background, fontSize: 12, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, marginLeft: 8, textTransform: 'uppercase', }, @@ -1413,7 +1163,7 @@ const styles = StyleSheet.create({ tabCountText: { color: theme.colors.background, fontSize: 10, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, }, // Content Styles @@ -1430,7 +1180,6 @@ const styles = StyleSheet.create({ }, sectionTitle: { fontSize: 18, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.md, @@ -1490,11 +1239,10 @@ const styles = StyleSheet.create({ }, seriesTitle: { fontSize: 16, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, - feedbackButton: { + seriesDetailButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.backgroundAlt, @@ -1504,7 +1252,7 @@ const styles = StyleSheet.create({ borderWidth: 1, borderColor: theme.colors.border, }, - feedbackButtonText: { + seriesDetailButtonText: { fontSize: 12, color: theme.colors.primary, fontFamily: theme.typography.fontFamily.medium, @@ -1549,7 +1297,7 @@ const styles = StyleSheet.create({ imageNumber: { color: theme.colors.background, fontSize: 10, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, textAlign: 'center', }, @@ -1580,7 +1328,6 @@ const styles = StyleSheet.create({ }, emptyStateTitle: { fontSize: 18, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginTop: theme.spacing.md, @@ -1648,7 +1395,6 @@ const styles = StyleSheet.create({ }, predictionSeriesTitle: { fontSize: 16, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, @@ -1672,7 +1418,6 @@ const styles = StyleSheet.create({ }, predictionLabel: { fontSize: 14, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, @@ -1683,7 +1428,7 @@ const styles = StyleSheet.create({ }, urgencyText: { fontSize: 10, - fontWeight: 'bold', + fontFamily: theme.typography.fontFamily.bold, color: theme.colors.background, textTransform: 'uppercase', }, @@ -1737,7 +1482,6 @@ const styles = StyleSheet.create({ }, errorTitle: { fontSize: 18, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginTop: theme.spacing.md, @@ -1764,7 +1508,6 @@ const styles = StyleSheet.create({ retryButtonText: { color: theme.colors.background, fontSize: 16, - fontWeight: 'bold', fontFamily: theme.typography.fontFamily.bold, }, @@ -1781,7 +1524,6 @@ const styles = StyleSheet.create({ }, imageSectionTitle: { fontSize: 14, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, @@ -1791,7 +1533,6 @@ const styles = StyleSheet.create({ }, predictionsSectionTitle: { fontSize: 14, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginBottom: theme.spacing.sm, @@ -1835,286 +1576,12 @@ const styles = StyleSheet.create({ }, summaryValue: { fontSize: 18, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, marginTop: theme.spacing.xs, }, - // Feedback Modal Styles - modalOverlay: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: 'rgba(0, 0, 0, 0.7)', - justifyContent: 'center', - alignItems: 'center', - zIndex: 1000, - }, - feedbackModal: { - backgroundColor: theme.colors.background, - borderRadius: 12, - width: '90%', - maxWidth: 450, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 10, - elevation: 10, - }, - modalHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: theme.spacing.md, - borderBottomWidth: 1, - borderBottomColor: theme.colors.border, - }, - modalTitle: { - fontSize: 20, - fontWeight: 'bold', - color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.bold, - }, - closeButton: { - padding: theme.spacing.sm, - }, - modalContent: { - padding: theme.spacing.md, - }, - feedbackSeriesInfo: { - marginBottom: theme.spacing.md, - }, - feedbackSeriesTitle: { - fontSize: 16, - fontWeight: 'bold', - color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.bold, - marginBottom: theme.spacing.xs, - }, - feedbackSeriesMeta: { - fontSize: 12, - color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.regular, - }, - feedbackSection: { - marginBottom: theme.spacing.md, - }, - feedbackSectionTitle: { - fontSize: 14, - fontWeight: 'bold', - color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.bold, - marginBottom: theme.spacing.sm, - }, - feedbackTypeContainer: { - flexDirection: 'row', - justifyContent: 'space-around', - marginBottom: theme.spacing.sm, - }, - feedbackTypeButton: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: theme.spacing.sm, - paddingHorizontal: theme.spacing.md, - borderRadius: 12, - borderWidth: 1, - borderColor: theme.colors.border, - }, - feedbackTypeButtonActive: { - borderColor: theme.colors.primary, - backgroundColor: theme.colors.primary, - }, - feedbackTypeButtonText: { - fontSize: 14, - color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.medium, - marginLeft: theme.spacing.sm, - }, - feedbackTypeButtonTextActive: { - color: theme.colors.background, - fontFamily: theme.typography.fontFamily.bold, - }, - priorityContainer: { - flexDirection: 'row', - justifyContent: 'space-around', - marginBottom: theme.spacing.sm, - }, - priorityButton: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: theme.spacing.sm, - paddingHorizontal: theme.spacing.md, - borderRadius: 12, - borderWidth: 1, - borderColor: theme.colors.border, - }, - priorityButtonActive: { - borderColor: theme.colors.primary, - backgroundColor: theme.colors.primary, - }, - priorityIndicator: { - width: 10, - height: 10, - borderRadius: 5, - marginRight: theme.spacing.sm, - }, - priorityButtonText: { - fontSize: 14, - color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.medium, - }, - priorityButtonTextActive: { - color: theme.colors.background, - fontFamily: theme.typography.fontFamily.bold, - }, - feedbackTextInput: { - borderWidth: 1, - borderColor: theme.colors.border, - borderRadius: 8, - padding: theme.spacing.md, - fontSize: 14, - color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.regular, - minHeight: 100, - textAlignVertical: 'top', - }, - modalFooter: { - flexDirection: 'row', - justifyContent: 'space-around', - padding: theme.spacing.md, - borderTopWidth: 1, - borderTopColor: theme.colors.border, - }, - - // Feedback Prediction Info Styles - feedbackPredictionInfo: { - // marginTop: theme.spacing.sm, - paddingTop: theme.spacing.md, - marginBottom: theme.spacing.sm, - // borderTopWidth: 1, - // borderTopColor: theme.colors.border, - }, - feedbackPredictionTitle: { - fontSize: 14, - fontWeight: 'bold', - color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.bold, - marginBottom: theme.spacing.xs, - }, - feedbackPredictionMeta: { - fontSize: 12, - color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.regular, - }, - - // Prediction Accuracy Selection Styles - predictionAccuracyContainer: { - flexDirection: 'row', - justifyContent: 'space-around', - marginBottom: theme.spacing.sm, - }, - predictionAccuracyButton: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: theme.spacing.sm, - paddingHorizontal: theme.spacing.md, - borderRadius: 12, - borderWidth: 1, - borderColor: theme.colors.border, - minWidth: 120, - justifyContent: 'center', - }, - predictionAccuracyButtonActive: { - borderColor: theme.colors.primary, - backgroundColor: theme.colors.primary, - }, - predictionAccuracyButtonText: { - fontSize: 14, - color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.medium, - marginLeft: theme.spacing.sm, - }, - predictionAccuracyButtonTextActive: { - color: theme.colors.background, - fontFamily: theme.typography.fontFamily.bold, - }, - cancelButton: { - paddingVertical: theme.spacing.md, - paddingHorizontal: theme.spacing.lg, - borderRadius: 8, - borderWidth: 1, - borderColor: theme.colors.border, - }, - cancelButtonText: { - fontSize: 16, - color: theme.colors.textSecondary, - fontFamily: theme.typography.fontFamily.medium, - }, - submitButton: { - paddingVertical: theme.spacing.md, - paddingHorizontal: theme.spacing.lg, - borderRadius: 8, - backgroundColor: theme.colors.primary, - shadowColor: theme.colors.primary, - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.3, - shadowRadius: 4, - elevation: 4, - }, - submitButtonDisabled: { - backgroundColor: theme.colors.textMuted, - opacity: 0.7, - }, - submitButtonText: { - color: theme.colors.background, - fontSize: 16, - fontWeight: 'bold', - fontFamily: theme.typography.fontFamily.bold, - }, - // Feedback Result Modal Styles - feedbackResultModal: { - backgroundColor: theme.colors.background, - borderRadius: 16, - padding: 0, - width: '90%', - maxWidth: 400, - shadowColor: '#000000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.15, - shadowRadius: 8, - elevation: 8, - }, - feedbackResultMessage: { - fontSize: 16, - color: theme.colors.textPrimary, - fontFamily: theme.typography.fontFamily.regular, - textAlign: 'center', - lineHeight: 24, - paddingHorizontal: theme.spacing.md, - }, - okButton: { - paddingVertical: theme.spacing.md, - paddingHorizontal: theme.spacing.xl, - borderRadius: 8, - backgroundColor: theme.colors.primary, - shadowColor: theme.colors.primary, - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.3, - shadowRadius: 4, - elevation: 4, - minWidth: 100, - alignItems: 'center', - }, - okButtonText: { - color: theme.colors.background, - fontSize: 16, - fontWeight: 'bold', - fontFamily: theme.typography.fontFamily.bold, - }, }); export default PatientDetailsScreen; diff --git a/app/modules/PatientCare/screens/PatientsScreen.tsx b/app/modules/PatientCare/screens/PatientsScreen.tsx index 85ce8e7..e58a606 100644 --- a/app/modules/PatientCare/screens/PatientsScreen.tsx +++ b/app/modules/PatientCare/screens/PatientsScreen.tsx @@ -215,7 +215,7 @@ const PatientsScreen: React.FC = () => { - + {/* { @@ -233,7 +233,7 @@ const PatientsScreen: React.FC = () => { > Filter - + */} ); @@ -365,13 +365,6 @@ const PatientsScreen: React.FC = () => { tintColor={theme.colors.primary} /> } - ListFooterComponent={ - - - Showing {filteredPatients.length} of {patients.length} patients - - - } /> )} @@ -404,6 +397,7 @@ const styles = StyleSheet.create({ backgroundColor: theme.colors.background, borderBottomWidth: 1, borderBottomColor: theme.colors.border, + marginBottom: theme.spacing.md, }, headerLeft: { flex: 1, @@ -414,7 +408,6 @@ const styles = StyleSheet.create({ }, headerTitle: { fontSize: 24, - fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, @@ -434,7 +427,6 @@ const styles = StyleSheet.create({ actionButtonText: { color: theme.colors.textSecondary, fontSize: 14, - fontWeight: '600', fontFamily: theme.typography.fontFamily.medium, }, @@ -478,7 +470,6 @@ const styles = StyleSheet.create({ }, errorTitle: { fontSize: 20, - fontWeight: 'bold', color: theme.colors.error, marginBottom: theme.spacing.sm, fontFamily: theme.typography.fontFamily.bold, @@ -502,7 +493,6 @@ const styles = StyleSheet.create({ retryButtonText: { color: theme.colors.background, fontSize: 16, - fontWeight: '600', fontFamily: theme.typography.fontFamily.medium, }, }); diff --git a/app/modules/PatientCare/screens/SeriesDetailScreen.tsx b/app/modules/PatientCare/screens/SeriesDetailScreen.tsx new file mode 100644 index 0000000..c72f19a --- /dev/null +++ b/app/modules/PatientCare/screens/SeriesDetailScreen.tsx @@ -0,0 +1,2846 @@ +/* + * File: SeriesDetailScreen.tsx + * Description: Detailed series information screen with predictions and feedback + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + * + * Features: + * - Complete series information and metadata + * - AI predictions and findings for the series + * - Feedback history from physicians + * - Floating feedback button for new feedback + * - Responsive design for different screen sizes + * - Integration with patient data and feedback system + * - Tabbed interface for better organization + */ + +import React, { useEffect, useState, useCallback } from 'react'; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, + StatusBar, + Alert, + Dimensions, + Image, + FlatList, + RefreshControl, + TextInput, +} 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 { patientAPI } from '../services/patientAPI'; +import { selectUser } from '../../Auth/redux/authSelectors'; +import { API_CONFIG } from '../../../shared/utils'; +import { SeriesDetailScreenProps } from '../navigation/navigationTypes'; + +// Get screen dimensions +const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); + +// ============================================================================ +// INTERFACES +// ============================================================================ + +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 Prediction { + id: number; + file_path: string; + prediction: { + label: string; + finding_type: string; + clinical_urgency: string; + confidence_score: number; + detailed_results: any; + finding_category: string; + primary_severity: string; + anatomical_location: string; + // Hemorrhage type properties + epidural?: boolean; + subdural?: boolean; + intraparenchymal?: boolean; + subarachnoid?: boolean; + intraventricular?: boolean; + epidural_percentage?: number; + subdural_percentage?: number; + intraparenchymal_percentage?: number; + subarachnoid_percentage?: number; + intraventricular_percentage?: number; + midline_shift?: number; + }; + processed_at: string; + preview: string; +} + +// Tab types +type TabType = 'series' | 'ai' | 'feedback'; + +// ============================================================================ +// SERIES DETAIL SCREEN COMPONENT +// ============================================================================ + +/** + * SeriesDetailScreen Component + * + * Purpose: Detailed view of a specific DICOM series with predictions and feedback + * + * Features: + * - Complete series information and metadata + * - AI predictions and findings display + * - Feedback history from physicians + * - Floating feedback button for new feedback + * - Responsive design for different screen sizes + * - Integration with patient data and feedback system + * - Tabbed interface for better organization + */ +const SeriesDetailScreen: React.FC = ({ navigation, route }) => { + // ============================================================================ + // STATE MANAGEMENT + // ============================================================================ + + const dispatch = useAppDispatch(); + + // Route parameters + const { patientId, patientName, seriesNumber, seriesData, patientData, 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(null); + + // Tab state + const [activeTab, setActiveTab] = useState('series'); + + // Local patient data state for real-time updates + const [localPatientData, setLocalPatientData] = useState(patientData); + + // Feedback state + const [showFeedbackModal, setShowFeedbackModal] = useState(false); + const [selectedPrediction, setSelectedPrediction] = useState(null); + const [feedbackText, setFeedbackText] = useState(''); + const [isPositive, setIsPositive] = useState(null); + const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false); + + // Feedback result modal state + const [showFeedbackResultModal, setShowFeedbackResultModal] = useState(false); + const [feedbackResult, setFeedbackResult] = useState<{ + type: 'success' | 'error'; + title: string; + message: string; + } | null>(null); + + // Track newly added feedback for visual indication + const [newFeedbackIds, setNewFeedbackIds] = useState>(new Set()); + + // ============================================================================ + // EFFECTS + // ============================================================================ + + /** + * Component Mount Effect + * + * Purpose: Set navigation title and initialize screen + */ + useEffect(() => { + navigation.setOptions({ + title: `Series ${seriesNumber}`, + headerShown: false, + }); + }, [navigation, seriesNumber]); + + /** + * Sync Local Patient Data Effect + * + * Purpose: Keep local patient data in sync with route params + */ + useEffect(() => { + setLocalPatientData(patientData); + }, [patientData]); + + // ============================================================================ + // EVENT HANDLERS + // ============================================================================ + + /** + * Refresh Patient Data + * + * Purpose: Fetch updated patient data including new feedback + */ + const refreshPatientData = useCallback(async () => { + if (!user?.access_token) return; + + try { + const response: any = await patientAPI.getPatientDetailsById(patientId, user.access_token); + + if (response.ok && response.data && response.data.data) { + // Update the local patient data with fresh data from API + // This will include the newly submitted feedback + const updatedPatientData = response.data.data; + setLocalPatientData(updatedPatientData); + + // Also update the route params for consistency + route.params.patientData = updatedPatientData; + + setError(null); + } + } catch (err: any) { + console.log('Error refreshing patient data:', err.message); + // Don't show error to user for background refresh + } + }, [patientId, user?.access_token, route.params]); + + /** + * 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); + // Refresh patient data to get latest information + await refreshPatientData(); + setIsRefreshing(false); + }, [refreshPatientData]); + + /** + * Handle Open Feedback Modal + * + * Purpose: Open feedback modal for a specific prediction + * + * @param prediction - Prediction data for feedback + */ + const handleOpenFeedback = useCallback((prediction: Prediction) => { + setSelectedPrediction(prediction); + setFeedbackText(''); + setIsPositive(null); + setShowFeedbackModal(true); + }, []); + + /** + * Handle Submit Feedback + * + * Purpose: Submit feedback to API + */ + const handleSubmitFeedback = useCallback(async () => { + if (!selectedPrediction || !feedbackText.trim() || isPositive === null) { + setFeedbackResult({ + type: 'error', + title: 'Validation Error', + message: 'Please provide all required feedback information' + }); + setShowFeedbackResultModal(true); + return; + } + + try { + setIsSubmittingFeedback(true); + + if (!patientId) { + throw new Error('Patient ID not available'); + } + + const feedbackPayload = { + patid: patientId, + prediction_id: selectedPrediction.id, + feedback_text: feedbackText.trim(), + is_positive: isPositive + }; + + console.log('Submitting feedback payload:', feedbackPayload); + + // Call the actual API + const response = await patientAPI.submitFeedback(feedbackPayload, user?.access_token); + console.log('Feedback response:', response); + + if (!response.ok) { + throw new Error(response.problem || 'Failed to submit feedback'); + } + + // Show success message + setFeedbackResult({ + type: 'success', + title: 'Feedback Submitted', + message: 'Your feedback has been recorded successfully.' + }); + setShowFeedbackResultModal(true); + + // Track this feedback as newly added for visual indication + const newFeedbackId = `new_${Date.now()}`; + setNewFeedbackIds(prev => new Set(prev).add(newFeedbackId)); + + // Refresh patient data to get updated feedback + await refreshPatientData(); + + // Notify parent screen to refresh its data as well + // This ensures PatientDetailsScreen shows updated information when user navigates back + if (onFeedbackSubmitted) { + onFeedbackSubmitted(); + } + } catch (error: any) { + setFeedbackResult({ + type: 'error', + title: 'Error', + message: error.message || 'Failed to submit feedback. Please try again.' + }); + setShowFeedbackResultModal(true); + } finally { + setIsSubmittingFeedback(false); + } + }, [selectedPrediction, feedbackText, isPositive, patientId, user?.access_token, refreshPatientData, onFeedbackSubmitted]); + + /** + * Handle Close Feedback Modal + * + * Purpose: Close feedback modal and reset state + */ + const handleCloseFeedback = useCallback(() => { + setShowFeedbackModal(false); + setSelectedPrediction(null); + setFeedbackText(''); + setIsPositive(null); + }, []); + + /** + * Handle Feedback Result Modal Close + * + * Purpose: Close feedback result modal and reset form if success + */ + const handleFeedbackResultClose = useCallback(() => { + setShowFeedbackResultModal(false); + setFeedbackResult(null); + + // If it was a success, also close the feedback modal and reset form + if (feedbackResult?.type === 'success') { + setShowFeedbackModal(false); + setSelectedPrediction(null); + setFeedbackText(''); + setIsPositive(null); + } + }, [feedbackResult?.type]); + + /** + * Auto-close Success Modal Effect + * + * Purpose: Automatically close success modal after 2 seconds + */ + useEffect(() => { + if (feedbackResult?.type === 'success') { + const timer = setTimeout(() => { + handleFeedbackResultClose(); + }, 2000); + + return () => clearTimeout(timer); + } + }, [feedbackResult?.type, handleFeedbackResultClose]); + + /** + * Clear New Feedback Badges Effect + * + * Purpose: Clear "New" badges after 30 seconds to avoid UI clutter + */ + useEffect(() => { + if (newFeedbackIds.size > 0) { + const timer = setTimeout(() => { + setNewFeedbackIds(new Set()); + }, 30000); // 30 seconds + + return () => clearTimeout(timer); + } + }, [newFeedbackIds.size]); + + // ============================================================================ + // UTILITY FUNCTIONS + // ============================================================================ + + /** + * Get Clinical Urgency Color + * + * Purpose: Get appropriate color for clinical urgency + * + * @param urgency - Clinical urgency level + */ + const getUrgencyColor = (urgency: string) => { + switch (urgency.toLowerCase()) { + case 'urgent': + return theme.colors.error; + case 'semi-urgent': + return theme.colors.warning; + case 'non-urgent': + return theme.colors.success; + default: + return theme.colors.info; + } + }; + + /** + * Get Feedback Type Color + * + * Purpose: Get appropriate color for feedback type + * + * @param feedbackType - Feedback type (positive/negative) + */ + const getFeedbackTypeColor = (feedbackType: string) => { + switch (feedbackType.toLowerCase()) { + case 'positive': + return theme.colors.success; + case 'negative': + return theme.colors.error; + default: + return theme.colors.info; + } + }; + + /** + * Get Percentage Color + * + * Purpose: Get appropriate color based on percentage value + * + * @param percentage - Percentage value (0-100) + */ + const getPercentageColor = (percentage: number) => { + if (percentage >= 70) { + return theme.colors.error; // High detection - Red + } else if (percentage >= 40) { + return theme.colors.warning; // Medium detection - Orange + } else if (percentage >= 10) { + return theme.colors.info; // Low detection - Blue + } else { + return theme.colors.success; // No detection - Green + } + }; + + /** + * Get Percentage Status + * + * Purpose: Get status text based on percentage value + * + * @param percentage - Percentage value (0-100) + */ + const getPercentageStatus = (percentage: number) => { + if (percentage >= 70) { + return 'HIGH'; + } else if (percentage >= 40) { + return 'MEDIUM'; + } else if (percentage >= 10) { + return 'LOW'; + } else { + return 'NONE'; + } + }; + + /** + * Get Percentage Value + * + * Purpose: Extract percentage value from prediction data with flexible field naming + * + * @param prediction - Prediction object + * @param type - Hemorrhage type (epidural, subdural, etc.) + */ + const getPercentageValue = (prediction: any, type: string): number => { + // First, try to get from detailed_results.hemorrhage_detection + if (prediction.detailed_results?.hemorrhage_detection) { + const hemorrhageData = prediction.detailed_results.hemorrhage_detection; + + // Map our types to the actual field names in the data + const fieldMapping: { [key: string]: string } = { + 'epidural': 'Epidural', + 'subdural': 'Subdural', + 'subarachnoid': 'Subarachnoid', + 'intraparenchymal': 'Intraparenchymal', + 'intraventricular': 'Intraventricular', + 'midline_shift': 'Midline shift' + }; + + const actualFieldName = fieldMapping[type]; + if (actualFieldName && hemorrhageData[actualFieldName] !== undefined) { + const value = hemorrhageData[actualFieldName]; + + // Convert decimal (0-1) to percentage (0-100) + if (typeof value === 'number' && value >= 0 && value <= 1) { + return value * 100; + } + + // If it's already a percentage, return as is + if (typeof value === 'number' && value >= 0 && value <= 100) { + return value; + } + + // Handle string values + if (typeof value === 'string') { + const numValue = parseFloat(value); + if (!isNaN(numValue)) { + if (numValue >= 0 && numValue <= 1) { + return numValue * 100; + } + if (numValue >= 0 && numValue <= 100) { + return numValue; + } + } + } + } + } + + // Fallback: try different possible field names for the percentage + const possibleFields = [ + `${type}_percentage`, + `${type}_score`, + `${type}_value`, + `${type}_detection`, + `${type}_probability`, + type + ]; + + for (const field of possibleFields) { + if (prediction[field] !== undefined && prediction[field] !== null) { + const value = prediction[field]; + + // Handle different data types + if (typeof value === 'number') { + // If it's already a percentage (0-100), return as is + if (value >= 0 && value <= 100) { + return value; + } + // If it's a decimal (0-1), convert to percentage + if (value >= 0 && value <= 1) { + return value * 100; + } + // If it's a large number, assume it's already a percentage + return value; + } + + // Handle string values + if (typeof value === 'string') { + const numValue = parseFloat(value); + if (!isNaN(numValue)) { + // If it's already a percentage (0-100), return as is + if (numValue >= 0 && numValue <= 100) { + return numValue; + } + // If it's a decimal (0-1), convert to percentage + if (numValue >= 0 && numValue <= 1) { + return numValue * 100; + } + // If it's a large number, assume it's already a percentage + return numValue; + } + } + + // Handle boolean values (convert to 0% or 100%) + if (typeof value === 'boolean') { + return value ? 100 : 0; + } + } + } + + // If no valid percentage found, return 0 + return 0; + }; + + /** + * Get Series Predictions + * + * Purpose: Get predictions for the current series + */ + const getSeriesPredictions = () => { + if (!localPatientData?.predictions_by_series) return []; + return localPatientData.predictions_by_series[seriesNumber] || []; + }; + + /** + * Get Series Feedback + * + * Purpose: Get feedback for the current series + */ + const getSeriesFeedback = () => { + if (!localPatientData?.feedback_by_series) return []; + return localPatientData.feedback_by_series[seriesNumber] || []; + }; + + /** + * Check if Feedback is New + * + * Purpose: Check if feedback was recently added for visual indication + * + * @param feedbackId - Feedback ID to check + */ + const isFeedbackNew = (feedbackId: string) => { + return newFeedbackIds.has(feedbackId); + }; + + // ============================================================================ + // RENDER HELPERS + // ============================================================================ + + /** + * Render Tab Navigation + * + * Purpose: Render tab navigation for switching between different sections + */ + const renderTabNavigation = () => { + const tabs = [ + { id: 'series' as TabType, label: 'Series Info', icon: 'info' }, + { id: 'ai' as TabType, label: 'AI Analysis', icon: 'activity' }, + { id: 'feedback' as TabType, label: 'Feedback', icon: 'message-circle' } + ]; + + return ( + + {tabs.map((tab) => ( + setActiveTab(tab.id)} + activeOpacity={0.7} + > + + + {tab.label} + + {/* Active Tab Indicator */} + {activeTab === tab.id && ( + + )} + + ))} + + ); + }; + + /** + * Render Series Header + * + * Purpose: Render series identification and basic information + */ + const renderSeriesHeader = () => { + if (!seriesData) return null; + + return ( + + + + + Series {seriesData.series_num} + + + {seriesData.series_description || 'No description available'} + + + + + {seriesData.total_images} images + + + + {seriesData.modality} modality + + {seriesData.body_part && ( + + + {seriesData.body_part} + + )} + + + + + + + Processed + + {seriesData.study_date && ( + + {new Date(seriesData.study_date).toLocaleDateString()} + + )} + + + + ); + }; + + /** + * Render Series Details + * + * Purpose: Render detailed series information in a comprehensive format + */ + const renderSeriesDetails = () => { + if (!seriesData) return null; + + return ( + + Series Information + + {/* Series Summary Bar */} + + + Series Number + + {seriesData.series_num} + + + + Total Images + + {seriesData.total_images} + + + + Modality + + {seriesData.modality} + + + + + {/* Detailed Series Information Card */} + + + Series Details + Complete series metadata and information + + + + + + Series Number + {seriesData.series_num} + + + Series Description + + {seriesData.series_description || 'No description available'} + + + + + + + Total Images + {seriesData.total_images} + + + Modality + {seriesData.modality} + + + + + + Patient ID + {patientId} + + + Patient Name + {patientName} + + + + {seriesData.body_part && ( + + + Body Part + {seriesData.body_part} + + + Study Date + + {seriesData.study_date ? new Date(seriesData.study_date).toLocaleDateString() : 'Not specified'} + + + + )} + + {seriesData.institution_name && ( + + + Institution + {seriesData.institution_name} + + + Manufacturer + {seriesData.manufacturer || 'Not specified'} + + + )} + + + {/* Processing Information */} + + Processing Information + + + File Type + DICOM + + + Multiframe + + {seriesData.total_images > 1 ? 'Yes' : 'No'} + + + + Frames + {seriesData.total_images} + + + Status + + Processed + + + + + + + ); + }; + + /** + * Render AI Predictions + * + * Purpose: Render AI predictions and findings for the series + */ + const renderAIPredictions = () => { + const predictions = getSeriesPredictions(); + + // Debug: Log the prediction data structure + + if (predictions.length === 0) { + return ( + + AI Analysis Results + + + No AI Predictions + + No AI predictions are available for this series yet + + + + ); + } + + // Calculate summary metrics + const totalPredictions = predictions.length; + const highPriorityCount = predictions.filter((p: Prediction) => + p.prediction.clinical_urgency?.toLowerCase() === 'urgent' + ).length; + const avgConfidence = predictions.reduce((sum: number, p: Prediction) => + sum + (p.prediction.confidence_score || 0), 0 + ) / totalPredictions; + + return ( + + AI Analysis Results + + {/* AI Summary Bar */} + + + Total Predictions + + {totalPredictions} predictions found + + + + High Priority + + + {highPriorityCount > 0 ? `${highPriorityCount} urgent` : 'None'} + + + + + Avg Confidence + + + {(avgConfidence * 100).toFixed(1)}% + + + + + + {/* AI Predictions Card */} + + + AI Predictions Analysis + Medical scan analysis results + + + {predictions.map((prediction: Prediction) => ( + + + {prediction.prediction.label} + + + {prediction.prediction.clinical_urgency} + + + + + + + Finding Type: + + {prediction.prediction.finding_type} + + + + Confidence: + + {(prediction.prediction.confidence_score * 100).toFixed(1)}% + + + + Category: + + {prediction.prediction.finding_category} + + + + Severity: + + {prediction.prediction.primary_severity} + + + + Location: + + {prediction.prediction.anatomical_location} + + + + + + + {/* Show specific hemorrhage types if they exist */} + {prediction.prediction.epidural !== undefined && ( + + Epidural Hemorrhage: + + {prediction.prediction.epidural ? 'Detected' : 'Not Detected'} + + + )} + + {prediction.prediction.subdural !== undefined && ( + + Subdural Hemorrhage: + + {prediction.prediction.subdural ? 'Detected' : 'Not Detected'} + + + )} + + {prediction.prediction.intraparenchymal !== undefined && ( + + Intraparenchymal Hemorrhage: + + {prediction.prediction.intraparenchymal ? 'Detected' : 'Not Detected'} + + + )} + + {prediction.prediction.subarachnoid !== undefined && ( + + Subarachnoid Hemorrhage: + + {prediction.prediction.subarachnoid ? 'Detected' : 'Not Detected'} + + + )} + + {prediction.prediction.intraventricular !== undefined && ( + + Intraventricular Hemorrhage: + + {prediction.prediction.intraventricular ? 'Detected' : 'Not Detected'} + + + )} + + {/* Additional Findings from detailed_results */} + {prediction.prediction.detailed_results && ( + + Additional Analysis Results + + {/* Stroke Detection */} + {prediction.prediction.detailed_results.stroke_detection && ( + + Stroke Detection: + + + Normal: {((prediction.prediction.detailed_results.stroke_detection.Normal || 0) * 100).toFixed(1)}% + + + Stroke: {((prediction.prediction.detailed_results.stroke_detection.Stroke || 0) * 100).toFixed(1)}% + + + + )} + + {/* Binary Hemorrhage */} + {prediction.prediction.detailed_results.binary_hemorrhage && ( + + Hemorrhage Detection: + + + Normal: {((prediction.prediction.detailed_results.binary_hemorrhage.Normal || 0) * 100).toFixed(1)}% + + + Hemorrhage: {((prediction.prediction.detailed_results.binary_hemorrhage.Hemorrhage || 0) * 100).toFixed(1)}% + + + + )} + + )} + + {/* Visual Indicators Section - Show detailed findings with percentage indicators */} + + Detailed Findings Analysis + + {/* Compact Hemorrhage Type Percentage Indicators with Progress Bars */} + + {/* Epidural */} + + + + Epidural + + {getPercentageValue(prediction.prediction, 'epidural').toFixed(1)}% + + + + + + + + {/* Subdural */} + + + + Subdural + + {getPercentageValue(prediction.prediction, 'subdural').toFixed(1)}% + + + + + + + + {/* Intraparenchymal */} + + + + Intraparenchymal + + {getPercentageValue(prediction.prediction, 'intraparenchymal').toFixed(1)}% + + + + + + + + {/* Subarachnoid */} + + + + Subarachnoid + + {getPercentageValue(prediction.prediction, 'subarachnoid').toFixed(1)}% + + + + + + + + {/* Intraventricular */} + + + + Intraventricular + + {getPercentageValue(prediction.prediction, 'intraventricular').toFixed(1)}% + + + + + + + + {/* Midline Shift */} + + + + Midline Shift + + {getPercentageValue(prediction.prediction, 'midline_shift').toFixed(1)}% + + + + 50 ? theme.colors.error : theme.colors.warning + } + ]} + /> + + + + + {/* Summary Indicator with Overall Percentage */} + + Overall Assessment + + {/* Overall Percentage Calculation */} + {(() => { + const percentages = [ + getPercentageValue(prediction.prediction, 'epidural'), + getPercentageValue(prediction.prediction, 'subdural'), + getPercentageValue(prediction.prediction, 'intraparenchymal'), + getPercentageValue(prediction.prediction, 'subarachnoid'), + getPercentageValue(prediction.prediction, 'intraventricular'), + getPercentageValue(prediction.prediction, 'midline_shift') + ]; + const maxPercentage = Math.max(...percentages); + const hasHemorrhage = maxPercentage > 10; // Consider >10% as detected + + return ( + + Highest Detection: + {maxPercentage.toFixed(1)}% + + + + + {hasHemorrhage ? 'HEMORRHAGE DETECTED' : 'NO HEMORRHAGE'} + + + + ); + })()} + + + + + Processed: {new Date(prediction.processed_at).toLocaleDateString()} + + + ))} + + + ); + }; + + /** + * Render Series Images + * + * Purpose: Render DICOM image previews for the series + */ + const renderSeriesImages = () => { + const predictions = getSeriesPredictions(); + + if (predictions.length === 0) { + return ( + + DICOM Images + + + No Images Available + + No DICOM images are available for this series + + + + ); + } + + // Calculate image summary metrics + const totalImages = predictions.length; + const imagesWithPreview = predictions.filter((p: Prediction) => p.preview).length; + const imagesWithoutPreview = totalImages - imagesWithPreview; + + return ( + + DICOM Images + + {/* Images Summary Bar */} + + + Total Images + + {totalImages} + + + + With Preview + + {imagesWithPreview} + + + + Format + + DICOM + + + + + {/* Images Display Card */} + + + Image Gallery + Series image previews and metadata + + + + {predictions.map((prediction: Prediction, index: number) => ( + + {prediction.preview ? ( + + ) : ( + + + No Preview + + )} + Image {index + 1} + + {prediction.prediction.label} + + + ))} + + + + ); + }; + + /** + * Render Feedback History + * + * Purpose: Render feedback history from physicians + */ + const renderFeedbackHistory = () => { + const feedback = getSeriesFeedback(); + + if (isRefreshing) { + return ( + + Feedback History + + + Refreshing feedback... + + + ); + } + + if (feedback.length === 0) { + return ( + + Feedback History + + + No Feedback Yet + + Be the first to provide feedback on this series + + + + ); + } + + // Calculate feedback summary metrics + const totalFeedback = feedback.length; + const positiveFeedback = feedback.filter((f: Feedback) => f.is_positive).length; + const negativeFeedback = feedback.filter((f: Feedback) => !f.is_positive).length; + + return ( + + Feedback History + + {/* Feedback Summary Bar */} + + + Total Feedback + + {totalFeedback} entries + + + + Positive + + {positiveFeedback} + + + + Negative + + {negativeFeedback} + + + + + {/* Feedback History Card */} + + + Clinical Feedback + Radiologist insights and corrections + + + {feedback.map((feedbackItem: Feedback) => ( + + + + + + {feedbackItem.feedback_type} + + + {isFeedbackNew(feedbackItem.feedback_id) && ( + + NEW + + )} + + + {new Date(feedbackItem.created_at).toLocaleDateString()} + + + + {feedbackItem.feedback_text} + + + {feedbackItem.email} + + Prediction ID: {feedbackItem.prediction_id} + + + + ))} + + + ); + }; + + // ============================================================================ + // MAIN RENDER + // ============================================================================ + + return ( + + + + {/* Header */} + + + + + + Series {seriesNumber} + {patientName} + + + + + + + {/* Tab Navigation */} + {renderTabNavigation()} + + {/* Content */} + + } + > + {/* Series Header */} + {renderSeriesHeader()} + + {/* Tab Content */} + {activeTab === 'series' && ( + <> + {/* Series Details */} + {renderSeriesDetails()} + + {/* Series Images */} + {renderSeriesImages()} + + )} + + {activeTab === 'ai' && ( + <> + {/* AI Predictions */} + {renderAIPredictions()} + + )} + + {activeTab === 'feedback' && ( + <> + {/* Feedback History */} + {renderFeedbackHistory()} + + )} + + + {/* Floating Feedback Button - Show on all tabs */} + { + const predictions = getSeriesPredictions(); + if (predictions.length > 0) { + handleOpenFeedback(predictions[0]); + } else { + Alert.alert('No Predictions', 'No AI predictions available for feedback'); + } + }} + activeOpacity={0.8} + > + + + + {/* Feedback Modal */} + {showFeedbackModal && selectedPrediction && ( + + + + Provide Feedback + + + + + + + {/* Prediction Info */} + + + AI Prediction: {selectedPrediction.prediction.label} + + + Confidence: {(selectedPrediction.prediction.confidence_score * 100).toFixed(1)}% • + Type: {selectedPrediction.prediction.finding_type} + + + + {/* Prediction Accuracy Selection */} + + Is this prediction accurate? + + {[ + { key: 'true', label: 'Yes (Positive)', color: theme.colors.success, icon: 'check-circle', value: true }, + { key: 'false', label: 'No (Negative)', color: theme.colors.error, icon: 'x-circle', value: false } + ].map((option) => ( + setIsPositive(option.value)} + style={[ + styles.predictionAccuracyButton, + isPositive === option.value && styles.predictionAccuracyButtonActive, + { borderColor: option.color } + ]} + > + + + {option.label} + + + ))} + + + + {/* Feedback Text Input */} + + Your Feedback + + + + + + + Cancel + + + {isSubmittingFeedback ? ( + + + Submitting... + + ) : ( + Submit Feedback + )} + + + + + )} + + {/* Feedback Result Modal */} + {showFeedbackResultModal && feedbackResult && ( + + + + + + {feedbackResult.title} + + + + + + {feedbackResult.message} + + + + + + OK + + + + + )} + + ); +}; + +// ============================================================================ +// STYLES +// ============================================================================ + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + // Header Styles + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.sm, + backgroundColor: theme.colors.background, + borderBottomWidth: 1, + borderBottomColor: theme.colors.border, + }, + backButton: { + padding: theme.spacing.sm, + marginRight: theme.spacing.sm, + }, + headerTitle: { + flex: 1, + alignItems: 'center', + }, + headerTitleText: { + fontSize: 18, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + headerSubtitleText: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + refreshButton: { + padding: theme.spacing.sm, + marginLeft: theme.spacing.sm, + }, + + // Content Styles + content: { + flex: 1, + }, + + // Section Styles + section: { + marginBottom: theme.spacing.lg, + paddingHorizontal: theme.spacing.md, + }, + sectionTitle: { + fontSize: 18, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.md, + }, + + // Series Header Styles + seriesHeader: { + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.lg, + backgroundColor: theme.colors.background, + borderBottomWidth: 1, + borderBottomColor: theme.colors.border, + }, + seriesHeaderContent: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + seriesHeaderLeft: { + flex: 1, + }, + seriesHeaderRight: { + alignItems: 'flex-end', + }, + seriesTitle: { + fontSize: 24, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.xs, + }, + seriesDescription: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + marginBottom: theme.spacing.sm, + }, + seriesMetaRow: { + flexDirection: 'row', + flexWrap: 'wrap', + marginTop: theme.spacing.xs, + }, + metaItem: { + flexDirection: 'row', + alignItems: 'center', + marginRight: theme.spacing.md, + marginBottom: theme.spacing.xs, + }, + metaText: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + marginLeft: theme.spacing.sm, + }, + studyDate: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + seriesStatusBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: theme.colors.success, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.xs, + borderRadius: 12, + }, + seriesStatusText: { + fontSize: 12, + color: theme.colors.background, + fontFamily: theme.typography.fontFamily.bold, + marginLeft: theme.spacing.sm, + }, + + // Series Details Styles + seriesDetails: { + backgroundColor: theme.colors.backgroundAlt, + padding: theme.spacing.md, + borderRadius: 8, + }, + seriesDetailItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: theme.spacing.sm, + }, + seriesDetailLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + flex: 1, + }, + seriesDetailValue: { + fontSize: 12, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + flex: 2, + textAlign: 'right', + }, + + // Prediction Styles + predictionCard: { + backgroundColor: theme.colors.backgroundAlt, + borderRadius: 8, + padding: theme.spacing.sm, + marginBottom: theme.spacing.sm, + // shadowColor: '#000', + // shadowOffset: { width: 0, height: 2 }, + // shadowOpacity: 0.1, + // shadowRadius: 4, + // elevation: 2, + }, + predictionHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + predictionLabel: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + flex: 1, + }, + urgencyBadge: { + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 12, + }, + urgencyText: { + fontSize: 10, + color: theme.colors.background, + textTransform: 'uppercase', + fontFamily: theme.typography.fontFamily.medium, + }, + predictionDetails: { + marginBottom: theme.spacing.sm, + }, + predictionDetailItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: theme.spacing.xs, + }, + predictionDetailLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + flex: 1, + }, + predictionDetailValue: { + fontSize: 12, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + flex: 2, + textAlign: 'right', + }, + predictionTimestamp: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + textAlign: 'right', + }, + + // Image Styles + imageList: { + paddingRight: theme.spacing.md, + }, + imageContainer: { + alignItems: 'center', + marginRight: theme.spacing.md, + }, + seriesImage: { + width: 120, + height: 120, + borderRadius: 8, + marginBottom: theme.spacing.xs, + }, + noImagePlaceholder: { + width: 120, + height: 120, + borderRadius: 8, + backgroundColor: theme.colors.backgroundAlt, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 1, + borderColor: theme.colors.border, + borderStyle: 'dashed', + marginBottom: theme.spacing.xs, + }, + noImageText: { + fontSize: 10, + color: theme.colors.textMuted, + fontFamily: theme.typography.fontFamily.regular, + textAlign: 'center', + }, + imageLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + imagePredictionLabel: { + fontSize: 10, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + marginTop: theme.spacing.xs, + }, + + // Feedback Styles + 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, + }, + feedbackHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + feedbackHeaderLeft: { + flexDirection: 'row', + alignItems: 'center', + }, + feedbackTypeBadge: { + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 12, + }, + feedbackTypeText: { + fontSize: 10, + color: theme.colors.background, + textTransform: 'uppercase', + fontFamily: theme.typography.fontFamily.medium, + }, + newFeedbackBadge: { + backgroundColor: theme.colors.primary, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 12, + marginLeft: theme.spacing.sm, + }, + newFeedbackBadgeText: { + fontSize: 10, + color: theme.colors.background, + textTransform: 'uppercase', + fontFamily: theme.typography.fontFamily.bold, + }, + feedbackDate: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + feedbackText: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + lineHeight: 20, + marginBottom: theme.spacing.sm, + }, + feedbackFooter: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + feedbackEmail: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + feedbackPredictionId: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + + // Empty State Styles + emptyState: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: theme.spacing.xl, + }, + emptyStateTitle: { + fontSize: 18, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginTop: theme.spacing.md, + marginBottom: theme.spacing.sm, + }, + emptyStateSubtitle: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + textAlign: 'center', + lineHeight: 20, + }, + + // Floating Feedback Button + floatingFeedbackButton: { + position: 'absolute', + bottom: theme.spacing.lg, + right: theme.spacing.lg, + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: theme.colors.primary, + justifyContent: 'center', + alignItems: 'center', + shadowColor: theme.colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, + + // Modal Styles + modalOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + justifyContent: 'center', + alignItems: 'center', + zIndex: 1000, + }, + feedbackModal: { + backgroundColor: theme.colors.background, + borderRadius: 12, + width: '90%', + maxWidth: 450, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 10, + elevation: 10, + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: theme.spacing.md, + borderBottomWidth: 1, + borderBottomColor: theme.colors.border, + }, + modalTitle: { + fontSize: 20, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + closeButton: { + padding: theme.spacing.sm, + }, + modalContent: { + padding: theme.spacing.md, + }, + feedbackSection: { + marginBottom: theme.spacing.md, + }, + feedbackSectionTitle: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.sm, + }, + feedbackPredictionInfo: { + paddingTop: theme.spacing.md, + marginBottom: theme.spacing.sm, + }, + feedbackPredictionTitle: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.xs, + }, + feedbackPredictionMeta: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + predictionAccuracyContainer: { + flexDirection: 'row', + justifyContent: 'space-around', + marginBottom: theme.spacing.sm, + }, + predictionAccuracyButton: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: theme.spacing.sm, + paddingHorizontal: theme.spacing.md, + borderRadius: 12, + borderWidth: 1, + borderColor: theme.colors.border, + minWidth: 120, + justifyContent: 'center', + }, + predictionAccuracyButtonActive: { + borderColor: theme.colors.primary, + backgroundColor: theme.colors.primary, + }, + predictionAccuracyButtonText: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginLeft: theme.spacing.sm, + }, + predictionAccuracyButtonTextActive: { + color: theme.colors.background, + fontFamily: theme.typography.fontFamily.bold, + }, + feedbackTextInput: { + borderWidth: 1, + borderColor: theme.colors.border, + borderRadius: 8, + padding: theme.spacing.md, + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + minHeight: 100, + textAlignVertical: 'top', + }, + modalFooter: { + flexDirection: 'row', + justifyContent: 'space-around', + padding: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + cancelButton: { + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + borderRadius: 8, + borderWidth: 1, + borderColor: theme.colors.border, + }, + cancelButtonText: { + fontSize: 16, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + submitButton: { + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.lg, + borderRadius: 8, + backgroundColor: theme.colors.primary, + shadowColor: theme.colors.primary, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 4, + }, + submitButtonDisabled: { + backgroundColor: theme.colors.textMuted, + opacity: 0.7, + }, + submitButtonText: { + color: theme.colors.background, + fontSize: 16, + fontFamily: theme.typography.fontFamily.bold, + }, + submitButtonLoading: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + + // Feedback Result Modal Styles + feedbackResultModal: { + backgroundColor: theme.colors.background, + borderRadius: 16, + padding: 0, + width: '90%', + maxWidth: 400, + shadowColor: '#000000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 8, + }, + feedbackResultMessage: { + fontSize: 16, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + textAlign: 'center', + lineHeight: 24, + paddingHorizontal: theme.spacing.md, + }, + okButton: { + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.xl, + borderRadius: 8, + backgroundColor: theme.colors.primary, + shadowColor: theme.colors.primary, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 4, + minWidth: 100, + alignItems: 'center', + }, + okButtonText: { + color: theme.colors.background, + fontSize: 16, + fontFamily: theme.typography.fontFamily.bold, + }, + loadingState: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: theme.spacing.xl, + }, + loadingStateText: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + marginTop: theme.spacing.sm, + }, + // New styles for detailed series info + seriesSummaryBar: { + flexDirection: 'row', + justifyContent: 'space-around', + backgroundColor: theme.colors.backgroundAlt, + paddingVertical: theme.spacing.sm, + paddingHorizontal: theme.spacing.md, + borderRadius: 8, + marginBottom: theme.spacing.md, + }, + summaryItem: { + alignItems: 'center', + }, + summaryLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + summaryValueContainer: { + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.xs, + borderRadius: 12, + }, + summaryValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + seriesDetailsCard: { + backgroundColor: theme.colors.backgroundAlt, + borderRadius: 12, + padding: theme.spacing.md, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + cardTitle: { + fontSize: 18, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + cardSubtitle: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + }, + detailsGrid: { + marginBottom: theme.spacing.md, + }, + detailRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: theme.spacing.sm, + }, + detailColumn: { + flex: 1, + marginRight: theme.spacing.sm, + }, + detailLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + detailValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + }, + processingInfo: { + marginTop: theme.spacing.md, + paddingTop: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + processingTitle: { + fontSize: 16, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.sm, + }, + processingGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-around', + }, + processingItem: { + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + processingLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + processingValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + statusBadge: { + backgroundColor: theme.colors.success, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.xs, + borderRadius: 12, + }, + statusText: { + fontSize: 12, + color: theme.colors.background, + fontFamily: theme.typography.fontFamily.bold, + }, + // New styles for AI summary bar + aiSummaryBar: { + flexDirection: 'row', + justifyContent: 'space-around', + backgroundColor: theme.colors.backgroundAlt, + paddingVertical: theme.spacing.sm, + paddingHorizontal: theme.spacing.md, + borderRadius: 8, + marginBottom: theme.spacing.md, + }, + aiSummaryItem: { + alignItems: 'center', + }, + aiSummaryLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + aiSummaryValueContainer: { + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.xs, + borderRadius: 12, + }, + aiSummaryValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + // New styles for AI predictions card + aiPredictionsCard: { + backgroundColor: theme.colors.backgroundAlt, + borderRadius: 12, + padding: theme.spacing.md, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + // New styles for detailed results section + detailedResultsSection: { + marginTop: theme.spacing.md, + paddingTop: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + detailedResultsTitle: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.sm, + }, + detailedResultsGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-around', + }, + detailedResultItem: { + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + detailedResultLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + detailedResultValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + }, + // New styles for additional findings + additionalFindingItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: theme.spacing.sm, + paddingTop: theme.spacing.sm, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + additionalFindingLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + }, + additionalFindingValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.regular, + }, + // New styles for additional findings section + additionalFindingsSection: { + marginTop: theme.spacing.md, + paddingTop: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + additionalFindingsTitle: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.sm, + }, + additionalFindingValues: { + alignItems: 'flex-end', + }, + // New styles for images summary bar + imagesSummaryBar: { + flexDirection: 'row', + justifyContent: 'space-around', + backgroundColor: theme.colors.backgroundAlt, + paddingVertical: theme.spacing.sm, + paddingHorizontal: theme.spacing.md, + borderRadius: 8, + marginBottom: theme.spacing.md, + }, + imagesSummaryItem: { + alignItems: 'center', + }, + imagesSummaryLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + imagesSummaryValueContainer: { + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.xs, + borderRadius: 12, + }, + imagesSummaryValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + // New styles for images display card + imagesDisplayCard: { + backgroundColor: theme.colors.backgroundAlt, + borderRadius: 12, + padding: theme.spacing.md, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + // New styles for feedback summary bar + feedbackSummaryBar: { + flexDirection: 'row', + justifyContent: 'space-around', + backgroundColor: theme.colors.backgroundAlt, + paddingVertical: theme.spacing.sm, + paddingHorizontal: theme.spacing.md, + borderRadius: 8, + marginBottom: theme.spacing.md, + }, + feedbackSummaryItem: { + alignItems: 'center', + }, + feedbackSummaryLabel: { + fontSize: 12, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + feedbackSummaryValueContainer: { + backgroundColor: theme.colors.background, + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.xs, + borderRadius: 12, + }, + feedbackSummaryValue: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + }, + // New styles for feedback history card + feedbackHistoryCard: { + backgroundColor: theme.colors.backgroundAlt, + borderRadius: 12, + padding: theme.spacing.sm, + paddingBottom: theme.spacing.md, + // shadowColor: '#000', + // shadowOffset: { width: 0, height: 2 }, + // shadowOpacity: 0.1, + // shadowRadius: 4, + // elevation: 2, + }, + // New styles for visual indicators section + visualIndicatorsSection: { + marginTop: theme.spacing.md, + paddingTop: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + visualIndicatorsTitle: { + fontSize: 16, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.md, + textAlign: 'center', + }, + // New styles for compact indicators + compactIndicatorsContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + marginBottom: theme.spacing.md, + // paddingHorizontal: theme.spacing.sm, + }, + compactIndicatorItem: { + width: '100%', + paddingVertical: theme.spacing.md, + paddingHorizontal: theme.spacing.md, + backgroundColor: theme.colors.background, + borderRadius: 12, + marginBottom: theme.spacing.sm, + borderWidth: 1, + borderColor: theme.colors.border, + // shadowColor: '#000', + // shadowOffset: { width: 0, height: 1 }, + // shadowOpacity: 0.05, + // shadowRadius: 2, + // elevation: 1, + }, + indicatorHeader: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: theme.spacing.sm, + }, + compactIndicatorTitle: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.medium, + flex: 1, + marginLeft: theme.spacing.sm, + }, + compactIndicatorPercentage: { + fontSize: 16, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginLeft: theme.spacing.sm, + }, + indicatorsGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-around', + marginBottom: theme.spacing.md, + }, + indicatorCard: { + backgroundColor: theme.colors.background, + borderRadius: 12, + padding: theme.spacing.md, + marginBottom: theme.spacing.sm, + minWidth: 140, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + indicatorTitle: { + fontSize: 12, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginLeft: theme.spacing.xs, + textAlign: 'center', + }, + indicatorStatus: { + paddingHorizontal: theme.spacing.sm, + paddingVertical: theme.spacing.xs, + borderRadius: 8, + minWidth: 100, + alignItems: 'center', + }, + indicatorStatusText: { + fontSize: 10, + color: theme.colors.background, + fontFamily: theme.typography.fontFamily.bold, + textTransform: 'uppercase', + textAlign: 'center', + }, + summaryIndicator: { + alignItems: 'center', + marginTop: theme.spacing.md, + }, + summaryIndicatorTitle: { + fontSize: 14, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.sm, + }, + summaryIndicatorStatus: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.sm, + borderRadius: 12, + minWidth: 200, + justifyContent: 'center', + }, + summaryIndicatorText: { + fontSize: 12, + color: theme.colors.background, + fontFamily: theme.typography.fontFamily.bold, + textTransform: 'uppercase', + marginLeft: theme.spacing.sm, + textAlign: 'center', + }, + percentageContainer: { + alignItems: 'center', + marginBottom: theme.spacing.sm, + }, + percentageValue: { + fontSize: 16, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.xs, + }, + progressBarContainer: { + width: '100%', + height: 6, + backgroundColor: theme.colors.backgroundAlt, + borderRadius: 3, + marginTop: theme.spacing.xs, + overflow: 'hidden', + }, + progressBar: { + height: '100%', + borderRadius: 3, + minWidth: 4, + }, + progressBarBackground: { + width: '100%', + height: '100%', + backgroundColor: theme.colors.background, + borderRadius: 4, + }, + progressBarFill: { + height: '100%', + borderRadius: 4, + minWidth: 4, // Ensure minimum width for very small percentages + }, + overallPercentageContainer: { + alignItems: 'center', + marginBottom: theme.spacing.md, + }, + overallPercentageLabel: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginBottom: theme.spacing.xs, + }, + overallPercentageValue: { + fontSize: 24, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.md, + }, + debugSection: { + marginTop: theme.spacing.md, + paddingTop: theme.spacing.md, + borderTopWidth: 1, + borderTopColor: theme.colors.border, + }, + debugTitle: { + fontSize: 16, + color: theme.colors.textPrimary, + fontFamily: theme.typography.fontFamily.bold, + marginBottom: theme.spacing.sm, + }, + debugText: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.regular, + textAlign: 'center', + paddingHorizontal: theme.spacing.md, + lineHeight: 20, + }, + tabContainer: { + flexDirection: 'row', + justifyContent: 'space-around', + paddingVertical: theme.spacing.sm, + paddingHorizontal: theme.spacing.md, + borderBottomWidth: 1, + borderBottomColor: theme.colors.border, + backgroundColor: theme.colors.background, + }, + tabButton: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: theme.spacing.md, + paddingVertical: theme.spacing.sm, + borderRadius: 12, + borderWidth: 1, + borderColor: theme.colors.border, + minWidth: 110, + justifyContent: 'center', + position: 'relative', + backgroundColor: theme.colors.backgroundAlt, + }, + tabButtonActive: { + borderColor: theme.colors.primary, + backgroundColor: theme.colors.primary, + shadowColor: theme.colors.primary, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, + }, + tabButtonText: { + fontSize: 14, + color: theme.colors.textSecondary, + fontFamily: theme.typography.fontFamily.medium, + marginLeft: theme.spacing.xs, + }, + tabButtonTextActive: { + color: theme.colors.background, + fontFamily: theme.typography.fontFamily.bold, + }, + tabIndicator: { + position: 'absolute', + bottom: -theme.spacing.sm, + left: 0, + right: 0, + height: 3, + backgroundColor: theme.colors.primary, + borderRadius: 2, + }, +}); + +export default SeriesDetailScreen; diff --git a/app/modules/PatientCare/screens/index.ts b/app/modules/PatientCare/screens/index.ts index 704f549..c78055d 100644 --- a/app/modules/PatientCare/screens/index.ts +++ b/app/modules/PatientCare/screens/index.ts @@ -7,6 +7,7 @@ export { default as PatientsScreen } from './PatientsScreen'; export { default as PatientDetailsScreen } from './PatientDetailsScreen'; +export { default as SeriesDetailScreen } from './SeriesDetailScreen'; /* * End of File: index.ts diff --git a/app/modules/Settings/components/SettingsHeader.tsx b/app/modules/Settings/components/SettingsHeader.tsx index 0318b76..3b6860a 100644 --- a/app/modules/Settings/components/SettingsHeader.tsx +++ b/app/modules/Settings/components/SettingsHeader.tsx @@ -66,11 +66,12 @@ const styles = StyleSheet.create({ container: { backgroundColor: theme.colors.background, paddingHorizontal: theme.spacing.md, - paddingVertical: theme.spacing.lg, + paddingVertical: theme.spacing.md, borderBottomColor: theme.colors.border, borderBottomWidth: 1, flexDirection: 'row', alignItems: 'center', + marginBottom: theme.spacing.md, }, // Back button styling diff --git a/app/modules/Settings/screens/SettingsScreen.tsx b/app/modules/Settings/screens/SettingsScreen.tsx index 291bfe6..ba9df8d 100644 --- a/app/modules/Settings/screens/SettingsScreen.tsx +++ b/app/modules/Settings/screens/SettingsScreen.tsx @@ -36,6 +36,7 @@ import { selectUserProfilePhoto, selectDashboardSettings } from '../../Auth/redux/authSelectors'; +import { API_CONFIG } from '../../../shared/utils'; /** * SettingsScreenProps Interface @@ -332,7 +333,7 @@ export const SettingsScreen: React.FC = ({ }; - +console.log('user', user) // ============================================================================ // MAIN RENDER // ============================================================================ @@ -363,7 +364,7 @@ export const SettingsScreen: React.FC = ({ {user.profile_photo_url ? ( @@ -479,6 +480,7 @@ const styles = StyleSheet.create({ width: 60, height: 60, borderRadius: 30, + backgroundColor:theme.colors.primary, }, fallbackAvatar: {