/* * File: PredictionsList.tsx * Description: Predictions list component with tabbed interface for feedback status * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity, FlatList, RefreshControl, } from 'react-native'; import Icon from 'react-native-vector-icons/Feather'; import { theme } from '../../../theme/theme'; import { PredictionCard } from './PredictionCard'; import { usePredictions } from '../hooks/usePredictions'; import type { PredictionData } from '../types/predictions'; // ============================================================================ // INTERFACES // ============================================================================ interface PredictionsListProps { onPredictionPress: (prediction: PredictionData) => void; } // ============================================================================ // PREDICTIONS LIST COMPONENT // ============================================================================ /** * PredictionsList Component * * Purpose: Display AI predictions organized by radiologist feedback status * * Features: * - Two tabs: Radiologist Reviewed and Pending Review * - Search functionality * - Pull-to-refresh * - Loading states * - Error handling * - Empty states */ export const PredictionsList: React.FC = ({ onPredictionPress, }) => { const { activeTab, currentPredictions, currentLoadingState, currentError, switchTab, refreshPredictions, } = usePredictions(); // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * Handle Tab Switch * * Purpose: Switch between radiologist feedback status tabs */ const handleTabSwitch = (tab: 'with-feedback' | 'without-feedback') => { switchTab(tab); }; /** * Handle Prediction Press * * Purpose: Handle when a prediction card is pressed */ const handlePredictionPress = (prediction: PredictionData) => { onPredictionPress(prediction); }; /** * Render Tab Button * * Purpose: Render individual radiologist feedback status tab button */ const renderTabButton = (tab: 'with-feedback' | 'without-feedback', label: string, icon: string) => { const isActive = activeTab === tab; return ( handleTabSwitch(tab)} activeOpacity={0.7} > {label} ); }; /** * Render Empty State * * Purpose: Render empty state when no predictions available */ const renderEmptyState = () => ( No Predictions Found {activeTab === 'with-feedback' ? 'No predictions have been reviewed by radiologists yet.' : 'No predictions are waiting for radiologist review at the moment.' } ); /** * Render Error State * * Purpose: Render error state when API call fails */ const renderErrorState = () => ( Something went wrong {currentError || 'Failed to load predictions. Please try again.'} Retry ); // ============================================================================ // RENDER // ============================================================================ // Debug logging to see current state console.log('🔍 PredictionsList render debug:'); console.log('Active tab:', activeTab); console.log('Current predictions count:', currentPredictions.length); console.log('Current predictions sample:', currentPredictions.slice(0, 2).map(p => ({ id: p.id, has_feedback: p.has_provided_feedback, feedbacks_count: p.feedbacks?.length || 0 }))); return ( {/* Tab Navigation */} {renderTabButton('with-feedback', 'Radiologist Reviewed', 'message-circle')} {renderTabButton('without-feedback', 'Pending Review', 'message-square')} {/* Content Area */} {currentError ? ( renderErrorState() ) : ( <> {/* Fixed Header - Not part of scrolling */} {currentPredictions.length > 0 && ( {activeTab === 'with-feedback' ? 'Radiologist Reviewed Predictions' : 'Predictions Awaiting Review'} {currentPredictions.length} prediction{currentPredictions.length !== 1 ? 's' : ''} found {activeTab === 'with-feedback' && ( {' • '}{currentPredictions.filter(p => p.feedbacks && p.feedbacks.length > 0).length} with feedback )} )} {/* Horizontal Scrolling Predictions */} ( handlePredictionPress(item)} /> )} keyExtractor={(item) => item.id.toString()} contentContainerStyle={styles.listContainer} showsHorizontalScrollIndicator={true} showsVerticalScrollIndicator={false} horizontal={true} scrollEnabled={true} refreshControl={ } ListEmptyComponent={renderEmptyState} /> )} ); }; // ============================================================================ // STYLES // ============================================================================ const styles = StyleSheet.create({ container: { backgroundColor: theme.colors.background, flex: 1, }, tabContainer: { flexDirection: 'row', backgroundColor: theme.colors.background, paddingHorizontal: theme.spacing.md, paddingVertical: theme.spacing.sm, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, tabButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.sm, borderRadius: theme.borderRadius.medium, gap: theme.spacing.xs, }, activeTabButton: { backgroundColor: theme.colors.tertiary, }, tabButtonText: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.medium, color: theme.colors.textSecondary, }, activeTabButtonText: { color: theme.colors.primary, }, contentContainer: { flex: 1, }, listContainer: { padding: theme.spacing.md, alignItems: 'flex-start', }, listHeader: { marginBottom: theme.spacing.md, paddingBottom: theme.spacing.md, borderBottomWidth: 1, borderBottomColor: theme.colors.border, width: '100%', }, listHeaderTitle: { fontSize: theme.typography.fontSize.displaySmall, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginBottom: theme.spacing.xs, }, listHeaderSubtitle: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, }, feedbackCount: { color: theme.colors.primary, fontFamily: theme.typography.fontFamily.medium, }, emptyState: { justifyContent: 'center', alignItems: 'center', padding: theme.spacing.xxl, }, emptyStateTitle: { fontSize: theme.typography.fontSize.displayMedium, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginTop: theme.spacing.md, marginBottom: theme.spacing.sm, textAlign: 'center', }, emptyStateSubtitle: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, textAlign: 'center', }, errorState: { justifyContent: 'center', alignItems: 'center', padding: theme.spacing.xxl, }, errorStateTitle: { fontSize: theme.typography.fontSize.displayMedium, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginTop: theme.spacing.md, marginBottom: theme.spacing.sm, textAlign: 'center', }, errorStateSubtitle: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, textAlign: 'center', marginBottom: theme.spacing.lg, }, retryButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.primary, paddingHorizontal: theme.spacing.lg, paddingVertical: theme.spacing.md, borderRadius: theme.borderRadius.medium, gap: theme.spacing.sm, }, retryButtonText: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.medium, color: theme.colors.background, }, predictionCardWrapper: { marginRight: theme.spacing.md, width: 280, // Uniform width for all cards }, }); /* * End of File: PredictionsList.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */