NeoScan_Radiologist/app/modules/AIPrediction/screens/AIPredictionsScreen.tsx
2025-08-25 14:29:58 +05:30

748 lines
22 KiB
TypeScript

/*
* File: AIPredictionsScreen.tsx
* Description: Main AI Predictions screen with data rendering and management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useEffect, useCallback, useState } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
RefreshControl,
TouchableOpacity,
Alert,
SafeAreaView,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
// Import Redux actions and selectors
import {
fetchAIPredictions,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
setCurrentPage,
clearAllFilters,
toggleShowFilters,
toggleCaseSelection,
clearSelectedCases,
updateCaseReview,
} from '../redux';
import {
selectPaginatedCases,
selectIsLoading,
selectError,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectShowFilters,
selectSelectedCaseIds,
selectCasesStatistics,
selectFilterCounts,
selectActiveFiltersCount,
selectCurrentPage,
selectTotalPages,
selectHasNextPage,
selectHasPreviousPage,
} from '../redux';
// Import components
import {
AIPredictionCard,
SearchBar,
FilterTabs,
LoadingState,
EmptyState,
StatsOverview,
} from '../components';
// Import types
import type { AIPredictionCase } from '../types';
// Import auth selector
import { selectUser } from '../../Auth/redux/authSelectors';
// ============================================================================
// INTERFACES
// ============================================================================
interface AIPredictionsScreenProps {
navigation: any;
route?: any;
}
// ============================================================================
// AI PREDICTIONS SCREEN COMPONENT
// ============================================================================
/**
* AIPredictionsScreen Component
*
* Purpose: Main screen for displaying and managing AI prediction cases
*
* Features:
* - Comprehensive AI predictions list
* - Real-time search and filtering
* - Statistics overview dashboard
* - Bulk case selection and actions
* - Pull-to-refresh functionality
* - Pagination support
* - Review status management
* - Modern card-based design
* - Error handling and retry
* - Loading states and empty states
* - Accessibility support
*/
const AIPredictionsScreen: React.FC<AIPredictionsScreenProps> = ({ navigation }) => {
// ============================================================================
// REDUX STATE
// ============================================================================
const dispatch = useAppDispatch();
// Auth state
const user :any = useAppSelector(selectUser);
// AI Prediction state
const cases = useAppSelector(selectPaginatedCases);
const isLoading = useAppSelector(selectIsLoading);
const error = useAppSelector(selectError);
const searchQuery = useAppSelector(selectSearchQuery);
const urgencyFilter = useAppSelector(selectUrgencyFilter);
const severityFilter = useAppSelector(selectSeverityFilter);
const categoryFilter = useAppSelector(selectCategoryFilter);
const showFilters = useAppSelector(selectShowFilters);
const selectedCaseIds = useAppSelector(selectSelectedCaseIds);
const statistics = useAppSelector(selectCasesStatistics);
const filterCounts = useAppSelector(selectFilterCounts);
const activeFiltersCount = useAppSelector(selectActiveFiltersCount);
const currentPage = useAppSelector(selectCurrentPage);
const totalPages = useAppSelector(selectTotalPages);
const hasNextPage = useAppSelector(selectHasNextPage);
const hasPreviousPage = useAppSelector(selectHasPreviousPage);
// ============================================================================
// LOCAL STATE
// ============================================================================
const [refreshing, setRefreshing] = useState(false);
const [showStats, setShowStats] = useState(true);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Load AI Predictions on Mount
*
* Purpose: Fetch AI predictions when component mounts
*/
console.log('user ===>', user);
useEffect(() => {
if (user?.access_token) {
loadAIPredictions();
}
}, [user?.access_token]);
/**
* Load AI Predictions on Filter Change
*
* Purpose: Reload data when filters change
*/
useEffect(() => {
if (user?.access_token) {
loadAIPredictions();
}
}, [urgencyFilter, severityFilter, categoryFilter, searchQuery, currentPage]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Load AI Predictions
*
* Purpose: Fetch AI predictions from API
*/
const loadAIPredictions = useCallback(async () => {
if (!user?.access_token) return;
try {
const params = {
page: currentPage,
limit: 20,
...(urgencyFilter !== 'all' && { urgency: urgencyFilter }),
...(severityFilter !== 'all' && { severity: severityFilter }),
...(categoryFilter !== 'all' && { category: categoryFilter }),
...(searchQuery.trim() && { search: searchQuery.trim() }),
};
await dispatch(fetchAIPredictions({
token: user.access_token,
params,
})).unwrap();
} catch (error) {
console.error('Failed to load AI predictions:', error);
// Error is handled by Redux state
}
}, [dispatch, user?.access_token, currentPage, urgencyFilter, severityFilter, categoryFilter, searchQuery]);
/**
* Handle Refresh
*
* Purpose: Handle pull-to-refresh
*/
const handleRefresh = useCallback(async () => {
setRefreshing(true);
await loadAIPredictions();
setRefreshing(false);
}, [loadAIPredictions]);
/**
* Handle Search
*
* Purpose: Handle search query change
*/
const handleSearch = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Handle Filter Changes
*
* Purpose: Handle filter option changes
*/
const handleUrgencyFilterChange = useCallback((filter: typeof urgencyFilter) => {
dispatch(setUrgencyFilter(filter));
}, [dispatch]);
const handleSeverityFilterChange = useCallback((filter: typeof severityFilter) => {
dispatch(setSeverityFilter(filter));
}, [dispatch]);
const handleCategoryFilterChange = useCallback((filter: typeof categoryFilter) => {
dispatch(setCategoryFilter(filter));
}, [dispatch]);
/**
* Handle Clear Filters
*
* Purpose: Clear all active filters
*/
const handleClearFilters = useCallback(() => {
dispatch(clearAllFilters());
}, [dispatch]);
/**
* Handle Toggle Filters
*
* Purpose: Toggle filter visibility
*/
const handleToggleFilters = useCallback(() => {
dispatch(toggleShowFilters());
}, [dispatch]);
/**
* Handle Case Press
*
* Purpose: Navigate to case details
*/
const handleCasePress = useCallback((predictionCase: AIPredictionCase) => {
navigation.navigate('AIPredictionDetails', { caseId: predictionCase.patid });
}, [navigation]);
/**
* Handle Case Review
*
* Purpose: Handle case review action
*/
const handleCaseReview = useCallback(async (caseId: string) => {
if (!user?.access_token) return;
try {
await dispatch(updateCaseReview({
caseId,
reviewData: {
review_status: 'reviewed',
reviewed_by: user.name || user.email || 'Current User',
},
token: user.access_token,
})).unwrap();
Alert.alert(
'Review Updated',
'Case has been marked as reviewed.',
[{ text: 'OK' }]
);
} catch (error) {
Alert.alert(
'Error',
'Failed to update case review. Please try again.',
[{ text: 'OK' }]
);
}
}, [dispatch, user]);
/**
* Handle Case Selection
*
* Purpose: Handle case selection for bulk operations
*/
const handleCaseSelection = useCallback((caseId: string) => {
dispatch(toggleCaseSelection(caseId));
}, [dispatch]);
/**
* Handle Bulk Actions
*
* Purpose: Handle bulk actions on selected cases
*/
const handleBulkReview = useCallback(() => {
if (selectedCaseIds.length === 0) return;
Alert.alert(
'Bulk Review',
`Mark ${selectedCaseIds.length} cases as reviewed?`,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Confirm',
onPress: async () => {
// Implement bulk review logic here
// For now, just clear selections
dispatch(clearSelectedCases());
},
},
]
);
}, [selectedCaseIds, dispatch]);
/**
* Handle Page Change
*
* Purpose: Handle pagination
*/
const handlePreviousPage = useCallback(() => {
if (hasPreviousPage) {
dispatch(setCurrentPage(currentPage - 1));
}
}, [dispatch, currentPage, hasPreviousPage]);
const handleNextPage = useCallback(() => {
if (hasNextPage) {
dispatch(setCurrentPage(currentPage + 1));
}
}, [dispatch, currentPage, hasNextPage]);
/**
* Handle Stats Press
*
* Purpose: Handle statistics card press
*/
const handleStatsPress = useCallback((statType: string) => {
// Navigate to detailed statistics or apply relevant filters
switch (statType) {
case 'critical':
dispatch(setUrgencyFilter('emergency'));
break;
case 'urgent':
dispatch(setUrgencyFilter('urgent'));
break;
case 'pending':
// Filter for pending reviews
break;
default:
break;
}
}, [dispatch]);
/**
* Handle Retry
*
* Purpose: Handle retry after error
*/
const handleRetry = useCallback(() => {
loadAIPredictions();
}, [loadAIPredictions]);
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* Render AI Prediction Case
*
* Purpose: Render individual AI prediction case card
*/
const renderPredictionCase = useCallback(({ item }: { item: AIPredictionCase }) => (
<AIPredictionCard
predictionCase={item}
onPress={handleCasePress}
onReview={handleCaseReview}
isSelected={selectedCaseIds.includes(item.patid)}
onToggleSelect={handleCaseSelection}
showReviewButton={true}
/>
), [handleCasePress, handleCaseReview, selectedCaseIds, handleCaseSelection]);
/**
* Render List Header
*
* Purpose: Render search, filters, and statistics
*/
const renderListHeader = useCallback(() => (
<View>
{/* Statistics Overview */}
{showStats && (
<StatsOverview
stats={{
totalCases: statistics.total,
criticalCases: statistics.critical,
urgentCases: 0, // Would need to be calculated from urgency filter
reviewedCases: statistics.reviewed,
pendingCases: statistics.pending,
averageConfidence: statistics.averageConfidence,
todaysCases: 0, // Would need to be calculated from today's data
weeklyTrend: 12.5, // Mock data
}}
onStatsPress={handleStatsPress}
/>
)}
{/* Search Bar */}
<SearchBar
value={searchQuery}
onChangeText={handleSearch}
placeholder="Search by patient ID, finding, location..."
/>
{/* Filter Controls */}
<View style={styles.filterControls}>
<TouchableOpacity
style={[styles.filterToggle, showFilters && styles.filterToggleActive]}
onPress={handleToggleFilters}
accessibilityRole="button"
accessibilityLabel="Toggle filters"
>
<Icon name="filter" size={18} color={showFilters ? theme.colors.background : theme.colors.primary} />
<Text style={[styles.filterToggleText, showFilters && styles.filterToggleActiveText]}>
Filters
</Text>
{activeFiltersCount > 0 && (
<View style={styles.filterBadge}>
<Text style={styles.filterBadgeText}>{activeFiltersCount}</Text>
</View>
)}
</TouchableOpacity>
{selectedCaseIds.length > 0 && (
<TouchableOpacity
style={styles.bulkActionButton}
onPress={handleBulkReview}
accessibilityRole="button"
accessibilityLabel={`Bulk actions for ${selectedCaseIds.length} selected cases`}
>
<Icon name="check-circle" size={18} color={theme.colors.background} />
<Text style={styles.bulkActionText}>
Review {selectedCaseIds.length}
</Text>
</TouchableOpacity>
)}
</View>
{/* Filter Tabs */}
{showFilters && (
<FilterTabs
selectedUrgencyFilter={urgencyFilter}
selectedSeverityFilter={severityFilter}
selectedCategoryFilter={categoryFilter}
onUrgencyFilterChange={handleUrgencyFilterChange}
onSeverityFilterChange={handleSeverityFilterChange}
onCategoryFilterChange={handleCategoryFilterChange}
onClearFilters={handleClearFilters}
filterCounts={filterCounts}
activeFiltersCount={activeFiltersCount}
/>
)}
{/* Results Summary */}
<View style={styles.resultsSummary}>
<Text style={styles.resultsText}>
{statistics.total} predictions found
{activeFiltersCount > 0 && ` (${activeFiltersCount} filters applied)`}
</Text>
</View>
</View>
), [
showStats,
statistics,
handleStatsPress,
searchQuery,
handleSearch,
showFilters,
handleToggleFilters,
activeFiltersCount,
selectedCaseIds,
handleBulkReview,
urgencyFilter,
severityFilter,
categoryFilter,
handleUrgencyFilterChange,
handleSeverityFilterChange,
handleCategoryFilterChange,
handleClearFilters,
filterCounts,
]);
/**
* Render List Footer
*
* Purpose: Render pagination controls
*/
const renderListFooter = useCallback(() => {
if (totalPages <= 1) return null;
return (
<View style={styles.paginationContainer}>
<TouchableOpacity
style={[styles.paginationButton, !hasPreviousPage && styles.paginationButtonDisabled]}
onPress={handlePreviousPage}
disabled={!hasPreviousPage}
accessibilityRole="button"
accessibilityLabel="Previous page"
>
<Icon name="chevron-left" size={20} color={hasPreviousPage ? theme.colors.primary : theme.colors.textMuted} />
<Text style={[styles.paginationButtonText, !hasPreviousPage && styles.paginationButtonTextDisabled]}>
Previous
</Text>
</TouchableOpacity>
<Text style={styles.paginationInfo}>
Page {currentPage} of {totalPages}
</Text>
<TouchableOpacity
style={[styles.paginationButton, !hasNextPage && styles.paginationButtonDisabled]}
onPress={handleNextPage}
disabled={!hasNextPage}
accessibilityRole="button"
accessibilityLabel="Next page"
>
<Text style={[styles.paginationButtonText, !hasNextPage && styles.paginationButtonTextDisabled]}>
Next
</Text>
<Icon name="chevron-right" size={20} color={hasNextPage ? theme.colors.primary : theme.colors.textMuted} />
</TouchableOpacity>
</View>
);
}, [totalPages, currentPage, hasPreviousPage, hasNextPage, handlePreviousPage, handleNextPage]);
// ============================================================================
// RENDER
// ============================================================================
return (
<SafeAreaView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>AI Predictions</Text>
<TouchableOpacity
style={styles.headerButton}
onPress={() => setShowStats(!showStats)}
accessibilityRole="button"
accessibilityLabel="Toggle statistics"
>
<Icon name={showStats ? 'eye-off' : 'eye'} size={20} color={theme.colors.primary} />
</TouchableOpacity>
</View>
{/* Content */}
{error ? (
<EmptyState
title="Error Loading Predictions"
message={error}
iconName="alert-circle"
actionText="Retry"
onAction={handleRetry}
/>
) : isLoading && cases.length === 0 ? (
<LoadingState message="Loading AI predictions..." />
) : cases.length === 0 ? (
<EmptyState
title="No AI Predictions Found"
message="There are no AI prediction cases matching your current filters."
iconName="brain"
actionText="Clear Filters"
onAction={activeFiltersCount > 0 ? handleClearFilters : handleRefresh}
/>
) : (
<FlatList
data={cases}
renderItem={renderPredictionCase}
keyExtractor={(item) => item.patid}
ListHeaderComponent={renderListHeader}
ListFooterComponent={renderListFooter}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.listContent}
accessibilityRole="list"
/>
)}
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.background,
},
headerTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
headerButton: {
padding: theme.spacing.sm,
},
filterControls: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
filterToggle: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.primary,
gap: theme.spacing.sm,
},
filterToggleActive: {
backgroundColor: theme.colors.primary,
},
filterToggleText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
filterToggleActiveText: {
color: theme.colors.background,
},
filterBadge: {
backgroundColor: theme.colors.error,
borderRadius: 10,
minWidth: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
},
filterBadgeText: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.bold,
},
bulkActionButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
},
bulkActionText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.medium,
},
resultsSummary: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
resultsText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
},
listContent: {
paddingBottom: theme.spacing.xl,
},
paginationContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.lg,
marginTop: theme.spacing.lg,
},
paginationButton: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.primary,
gap: theme.spacing.xs,
},
paginationButtonDisabled: {
borderColor: theme.colors.textMuted,
opacity: 0.5,
},
paginationButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
paginationButtonTextDisabled: {
color: theme.colors.textMuted,
},
paginationInfo: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionsScreen;
/*
* End of File: AIPredictionsScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/