navigation isue resolved
This commit is contained in:
parent
41de1bbb25
commit
0a95550230
@ -126,11 +126,14 @@ export const PredictionCard: React.FC<PredictionCardProps> = ({
|
|||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.patientInfo}>
|
<View style={styles.patientInfo}>
|
||||||
<Text style={styles.patientName}>
|
<Text style={styles.patientName} numberOfLines={1}>
|
||||||
{prediction.patientdetails.Name || 'Unknown Patient'}
|
{prediction.patientdetails.Name || 'Unknown Patient'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.patientDetails}>
|
<Text style={styles.patientDetails} numberOfLines={1} ellipsizeMode="head">
|
||||||
{prediction.patientdetails.PatID} • {prediction.patientdetails.PatAge} • {prediction.patientdetails.PatSex}
|
{prediction.patientdetails.PatID}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.patientDetailsSecondary}>
|
||||||
|
{prediction.patientdetails.PatAge} • {prediction.patientdetails.PatSex}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -255,7 +258,12 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: theme.borderRadius.large,
|
borderRadius: theme.borderRadius.large,
|
||||||
padding: theme.spacing.md,
|
padding: theme.spacing.md,
|
||||||
marginBottom: theme.spacing.md,
|
marginBottom: theme.spacing.md,
|
||||||
...theme.shadows.primary,
|
// Custom shadow properties to ensure visibility
|
||||||
|
shadowColor: '#000000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 3,
|
||||||
},
|
},
|
||||||
|
|
||||||
header: {
|
header: {
|
||||||
@ -278,6 +286,12 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
|
|
||||||
patientDetails: {
|
patientDetails: {
|
||||||
|
fontSize: theme.typography.fontSize.bodySmall,
|
||||||
|
fontFamily: theme.typography.fontFamily.medium,
|
||||||
|
color: theme.colors.textPrimary,
|
||||||
|
marginBottom: theme.spacing.xs,
|
||||||
|
},
|
||||||
|
patientDetailsSecondary: {
|
||||||
fontSize: theme.typography.fontSize.bodySmall,
|
fontSize: theme.typography.fontSize.bodySmall,
|
||||||
fontFamily: theme.typography.fontFamily.regular,
|
fontFamily: theme.typography.fontFamily.regular,
|
||||||
color: theme.colors.textSecondary,
|
color: theme.colors.textSecondary,
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@ -44,8 +44,14 @@ interface PredictionsListProps {
|
|||||||
* - Loading states
|
* - Loading states
|
||||||
* - Error handling
|
* - Error handling
|
||||||
* - Empty states
|
* - Empty states
|
||||||
|
*
|
||||||
|
* Performance Optimizations:
|
||||||
|
* - Both datasets (with/without feedback) are pre-loaded upfront
|
||||||
|
* - Tab switching is instant - no filtering needed
|
||||||
|
* - React.memo prevents unnecessary re-renders
|
||||||
|
* - useCallback optimizes event handlers
|
||||||
*/
|
*/
|
||||||
export const PredictionsList: React.FC<PredictionsListProps> = ({
|
export const PredictionsList: React.FC<PredictionsListProps> = React.memo(({
|
||||||
onPredictionPress,
|
onPredictionPress,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@ -57,6 +63,14 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
refreshPredictions,
|
refreshPredictions,
|
||||||
} = usePredictions();
|
} = usePredictions();
|
||||||
|
|
||||||
|
// Performance optimization: Memoize feedback count calculation
|
||||||
|
const feedbackCount = useMemo(() => {
|
||||||
|
if (activeTab === 'with-feedback') {
|
||||||
|
return currentPredictions.filter(p => p.feedbacks?.length > 0).length;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}, [activeTab, currentPredictions]);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// UTILITY FUNCTIONS
|
// UTILITY FUNCTIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@ -66,18 +80,18 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
*
|
*
|
||||||
* Purpose: Switch between radiologist feedback status tabs
|
* Purpose: Switch between radiologist feedback status tabs
|
||||||
*/
|
*/
|
||||||
const handleTabSwitch = (tab: 'with-feedback' | 'without-feedback') => {
|
const handleTabSwitch = useCallback((tab: 'with-feedback' | 'without-feedback') => {
|
||||||
switchTab(tab);
|
switchTab(tab);
|
||||||
};
|
}, [switchTab]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle Prediction Press
|
* Handle Prediction Press
|
||||||
*
|
*
|
||||||
* Purpose: Handle when a prediction card is pressed
|
* Purpose: Handle when a prediction card is pressed
|
||||||
*/
|
*/
|
||||||
const handlePredictionPress = (prediction: PredictionData) => {
|
const handlePredictionPress = useCallback((prediction: PredictionData) => {
|
||||||
onPredictionPress(prediction);
|
onPredictionPress(prediction);
|
||||||
};
|
}, [onPredictionPress]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render Tab Button
|
* Render Tab Button
|
||||||
@ -150,15 +164,15 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
// RENDER
|
// RENDER
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Debug logging to see current state
|
// Performance optimization: Only log when needed (development mode)
|
||||||
console.log('🔍 PredictionsList render debug:');
|
// Note: Tab switching is now instant due to pre-loaded datasets
|
||||||
console.log('Active tab:', activeTab);
|
if (__DEV__ && currentPredictions.length > 0) {
|
||||||
console.log('Current predictions count:', currentPredictions.length);
|
console.log('🔍 PredictionsList render debug:', {
|
||||||
console.log('Current predictions sample:', currentPredictions.slice(0, 2).map(p => ({
|
activeTab,
|
||||||
id: p.id,
|
count: currentPredictions.length,
|
||||||
has_feedback: p.has_provided_feedback,
|
performance: 'Instant tab switching - datasets pre-loaded'
|
||||||
feedbacks_count: p.feedbacks?.length || 0
|
});
|
||||||
})));
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@ -184,7 +198,7 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
{currentPredictions.length} prediction{currentPredictions.length !== 1 ? 's' : ''} found
|
{currentPredictions.length} prediction{currentPredictions.length !== 1 ? 's' : ''} found
|
||||||
{activeTab === 'with-feedback' && (
|
{activeTab === 'with-feedback' && (
|
||||||
<Text style={styles.feedbackCount}>
|
<Text style={styles.feedbackCount}>
|
||||||
{' • '}{currentPredictions.filter(p => p.feedbacks && p.feedbacks.length > 0).length} with feedback
|
{' • '}{feedbackCount} with feedback
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
@ -192,6 +206,7 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Horizontal Scrolling Predictions */}
|
{/* Horizontal Scrolling Predictions */}
|
||||||
|
<View style={styles.listWrapper}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={currentPredictions}
|
data={currentPredictions}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
@ -204,7 +219,7 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
)}
|
)}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
contentContainerStyle={styles.listContainer}
|
contentContainerStyle={styles.listContainer}
|
||||||
showsHorizontalScrollIndicator={true}
|
showsHorizontalScrollIndicator={false}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
scrollEnabled={true}
|
scrollEnabled={true}
|
||||||
@ -218,12 +233,13 @@ export const PredictionsList: React.FC<PredictionsListProps> = ({
|
|||||||
}
|
}
|
||||||
ListEmptyComponent={renderEmptyState}
|
ListEmptyComponent={renderEmptyState}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// STYLES
|
// STYLES
|
||||||
@ -271,10 +287,19 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
listWrapper: {
|
||||||
|
// flex: 1,
|
||||||
|
minHeight: 0, // Prevent flex overflow
|
||||||
|
marginBottom: theme.spacing.md, // Add bottom margin for shadow visibility
|
||||||
},
|
},
|
||||||
|
|
||||||
listContainer: {
|
listContainer: {
|
||||||
padding: theme.spacing.md,
|
padding: 2,
|
||||||
|
paddingBottom: theme.spacing.xxl, // Increased bottom padding for shadow visibility
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -366,7 +391,9 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
predictionCardWrapper: {
|
predictionCardWrapper: {
|
||||||
marginRight: theme.spacing.md,
|
marginRight: theme.spacing.md,
|
||||||
|
marginBottom: theme.spacing.md, // Add bottom margin for shadow visibility
|
||||||
width: 280, // Uniform width for all cards
|
width: 280, // Uniform width for all cards
|
||||||
|
// Height will be determined by content naturally
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -53,12 +53,14 @@ export const usePredictions = () => {
|
|||||||
const activeTab = useAppSelector(selectActiveTab);
|
const activeTab = useAppSelector(selectActiveTab);
|
||||||
const searchQuery = useAppSelector(selectSearchQuery);
|
const searchQuery = useAppSelector(selectSearchQuery);
|
||||||
|
|
||||||
|
// Performance optimization: Load both datasets upfront
|
||||||
const predictionsWithFeedback = useAppSelector(selectPredictionsWithFeedback);
|
const predictionsWithFeedback = useAppSelector(selectPredictionsWithFeedback);
|
||||||
const predictionsWithoutFeedback = useAppSelector(selectPredictionsWithoutFeedback);
|
const predictionsWithoutFeedback = useAppSelector(selectPredictionsWithoutFeedback);
|
||||||
|
|
||||||
const isLoading = useAppSelector(selectIsLoading);
|
const isLoading = useAppSelector(selectIsLoading);
|
||||||
const error = useAppSelector(selectError);
|
const error = useAppSelector(selectError);
|
||||||
|
|
||||||
|
// Performance optimization: Direct dataset selection - no filtering needed
|
||||||
const currentPredictions = useAppSelector(selectCurrentPredictions);
|
const currentPredictions = useAppSelector(selectCurrentPredictions);
|
||||||
const currentLoadingState = useAppSelector(selectCurrentLoadingState);
|
const currentLoadingState = useAppSelector(selectCurrentLoadingState);
|
||||||
const currentError = useAppSelector(selectCurrentError);
|
const currentError = useAppSelector(selectCurrentError);
|
||||||
@ -74,6 +76,7 @@ export const usePredictions = () => {
|
|||||||
* Switch Active Tab
|
* Switch Active Tab
|
||||||
*
|
*
|
||||||
* Purpose: Change between feedback tabs
|
* Purpose: Change between feedback tabs
|
||||||
|
* Performance: Instant switching - no filtering needed as both datasets are pre-loaded
|
||||||
*/
|
*/
|
||||||
const switchTab = useCallback((tab: PredictionTabType) => {
|
const switchTab = useCallback((tab: PredictionTabType) => {
|
||||||
dispatch(setActiveTab(tab));
|
dispatch(setActiveTab(tab));
|
||||||
@ -132,12 +135,14 @@ export const usePredictions = () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-fetch data when component mounts or token changes
|
* Auto-fetch data when component mounts or token changes
|
||||||
|
* Performance optimization: Load both datasets upfront to eliminate tab switching delays
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authToken) return;
|
if (!authToken) return;
|
||||||
|
|
||||||
// Only fetch if we don't have any predictions yet
|
// Performance optimization: Always fetch if either dataset is empty
|
||||||
if (predictionsWithFeedback.length === 0 && predictionsWithoutFeedback.length === 0) {
|
// This ensures both datasets are loaded upfront for instant tab switching
|
||||||
|
if (predictionsWithFeedback.length === 0 || predictionsWithoutFeedback.length === 0) {
|
||||||
dispatch(fetchAllPredictions(authToken));
|
dispatch(fetchAllPredictions(authToken));
|
||||||
}
|
}
|
||||||
}, [authToken, predictionsWithFeedback.length, predictionsWithoutFeedback.length, dispatch]);
|
}, [authToken, predictionsWithFeedback.length, predictionsWithoutFeedback.length, dispatch]);
|
||||||
|
|||||||
@ -218,9 +218,16 @@ export const selectError = (state: { predictions: PredictionsState }) => state.p
|
|||||||
|
|
||||||
export const selectCurrentPredictions = (state: { predictions: PredictionsState }) => {
|
export const selectCurrentPredictions = (state: { predictions: PredictionsState }) => {
|
||||||
const { activeTab, predictionsWithFeedback, predictionsWithoutFeedback } = state.predictions;
|
const { activeTab, predictionsWithFeedback, predictionsWithoutFeedback } = state.predictions;
|
||||||
|
// Performance optimization: Direct dataset selection instead of filtering
|
||||||
return activeTab === 'with-feedback' ? predictionsWithFeedback : predictionsWithoutFeedback;
|
return activeTab === 'with-feedback' ? predictionsWithFeedback : predictionsWithoutFeedback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Performance optimization: Pre-computed selectors for instant tab switching
|
||||||
|
export const selectPredictionsForTab = (state: { predictions: PredictionsState }, tab: PredictionTabType) => {
|
||||||
|
const { predictionsWithFeedback, predictionsWithoutFeedback } = state.predictions;
|
||||||
|
return tab === 'with-feedback' ? predictionsWithFeedback : predictionsWithoutFeedback;
|
||||||
|
};
|
||||||
|
|
||||||
export const selectCurrentLoadingState = (state: { predictions: PredictionsState }) => {
|
export const selectCurrentLoadingState = (state: { predictions: PredictionsState }) => {
|
||||||
return state.predictions.isLoading;
|
return state.predictions.isLoading;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -638,9 +638,8 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
// Navigate to FeedbackDetailScreen with required parameters
|
// Navigate to FeedbackDetailScreen with required parameters
|
||||||
|
|
||||||
|
|
||||||
navigation.navigate('Patients', {
|
navigation.navigate('FeedbackDetail',
|
||||||
screen: 'FeedbackDetail',
|
{
|
||||||
params: {
|
|
||||||
patientId: prediction.patid,
|
patientId: prediction.patid,
|
||||||
patientName: prediction.patientdetails.Name || 'Unknown Patient',
|
patientName: prediction.patientdetails.Name || 'Unknown Patient',
|
||||||
seriesNumber: prediction.prediction.processing_info.filename || 'Unknown Series',
|
seriesNumber: prediction.prediction.processing_info.filename || 'Unknown Series',
|
||||||
@ -685,7 +684,7 @@ export const DashboardScreen: React.FC<DashboardScreenProps> = ({
|
|||||||
console.log('Feedback submitted, refreshing dashboard...');
|
console.log('Feedback submitted, refreshing dashboard...');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
console.log('Navigation successful to FeedbackDetailScreen');
|
console.log('Navigation successful to FeedbackDetailScreen');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export const shadows = {
|
|||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
shadowColor: colors.primary,
|
shadowColor: colors.primary,
|
||||||
shadowOffset: { width: 0, height: 4 },
|
shadowOffset: { width: 0, height: 2 },
|
||||||
shadowOpacity: 0.3,
|
shadowOpacity: 0.3,
|
||||||
shadowRadius: 8,
|
shadowRadius: 8,
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user