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

503 lines
14 KiB
TypeScript

/*
* File: PatientsScreen.tsx
* Description: Main patients screen with search, filtering, and patient list
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useEffect, useCallback } from 'react';
import {
View,
Text,
TouchableOpacity,
FlatList,
RefreshControl,
SafeAreaView,
StyleSheet,
Alert,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import { theme } from '../../../theme/theme';
// Components
import PatientCard from '../components/PatientCard';
import SearchBar from '../components/SearchBar';
import FilterTabs from '../components/FilterTabs';
import LoadingState from '../components/LoadingState';
import EmptyState from '../components/EmptyState';
// Redux
import {
fetchPatients,
setSearchQuery,
setFilter,
} from '../redux/patientCareSlice';
import {
selectPatients,
selectFilteredPatients,
selectPatientsLoading,
selectIsRefreshing,
selectPatientsError,
selectSearchQuery,
selectSelectedFilter,
selectPatientCounts,
} from '../redux/patientCareSelectors';
// Types
import { PatientData } from '../redux/patientCareSlice';
import { selectUser } from '../../Auth/redux/authSelectors';
// ============================================================================
// INTERFACES
// ============================================================================
// ============================================================================
// PATIENTS SCREEN COMPONENT
// ============================================================================
/**
* PatientsScreen Component
*
* Purpose: Main screen for displaying and managing patient list
*
* Features:
* - Real-time patient data fetching
* - Search functionality with real-time filtering
* - Filter tabs (All, Processed, Pending, Error)
* - Sort options (Date, Name, Processed)
* - Pull-to-refresh functionality
* - Patient cards with vital information
* - Navigation to patient details
* - Loading and error states
* - Empty state handling
* - Modern ER-focused UI design
*/
const PatientsScreen: React.FC = () => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const dispatch = useAppDispatch();
const navigation = useNavigation();
// Redux state
const patients = useAppSelector(selectPatients);
const filteredPatients = useAppSelector(selectFilteredPatients);
const isLoading = useAppSelector(selectPatientsLoading);
const isRefreshing = useAppSelector(selectIsRefreshing);
const error = useAppSelector(selectPatientsError);
const searchQuery = useAppSelector(selectSearchQuery);
const selectedFilter = useAppSelector(selectSelectedFilter);
const patientCounts = useAppSelector(selectPatientCounts);
// Auth state
const user = useAppSelector(selectUser);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Fetch Patients on Mount
*
* Purpose: Load patients when component mounts
*/
useEffect(() => {
if (user?.access_token) {
dispatch(fetchPatients(user.access_token));
}
}, [dispatch, user?.access_token]);
/**
* Clear Error on Unmount
*
* Purpose: Clean up error state when component unmounts
*/
useEffect(() => {
return () => {
// No clearError action in this file, so this effect is removed.
};
}, [dispatch]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Refresh
*
* Purpose: Handle pull-to-refresh functionality
*/
const handleRefresh = useCallback(() => {
if (user?.access_token) {
dispatch(fetchPatients(user.access_token));
}
}, [dispatch, user?.access_token]);
/**
* Handle Search
*
* Purpose: Handle search query changes
*
* @param query - Search query string
*/
const handleSearch = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Handle Filter Change
*
* Purpose: Update the selected filter and refresh the list
*/
const handleFilterChange = useCallback((filter: 'all' | 'processed' | 'pending' | 'error') => {
dispatch(setFilter(filter));
}, [dispatch]);
/**
* Handle Patient Press
*
* Purpose: Navigate to patient details when a patient card is pressed
*/
const handlePatientPress = useCallback((patient: PatientData) => {
(navigation as any).navigate('PatientDetails', {
patientId: patient.patid,
patientName: patient.patient_info.name,
});
}, [navigation]);
/**
* Handle Emergency Alert
*
* Purpose: Show emergency alert for critical patients
*/
const handleEmergencyAlert = useCallback((patient: PatientData) => {
Alert.alert(
'Emergency Alert',
`Patient ${patient.patient_info.name} (ID: ${patient.patid}) requires immediate attention!\n\nStatus: ${patient.patient_info.report_status}\nPriority: ${patient.patient_info.status}`,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'View Details', onPress: () => handlePatientPress(patient) },
]
);
}, [handlePatientPress]);
// ============================================================================
// RENDER HELPERS
// ============================================================================
/**
* Render Patient Card
*
* Purpose: Render individual patient card component
*/
const renderPatientCard = useCallback(({ item }: { item: PatientData }) => (
<PatientCard
patient={item}
onPress={() => handlePatientPress(item)}
onEmergencyPress={() => handleEmergencyAlert(item)}
/>
), [handlePatientPress, handleEmergencyAlert]);
/**
* Render Header
*
* Purpose: Render the screen header with title and action buttons
*/
const renderHeader = () => (
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.headerTitle}>Patients</Text>
<Text style={styles.headerSubtitle}>
{filteredPatients.length} of {patients?.length || 0} patients
</Text>
</View>
{/* <View style={styles.headerRight}>
<TouchableOpacity
style={styles.actionButton}
onPress={() => {
// TODO: Implement sort modal
}}
>
<Text style={styles.actionButtonText}>Sort</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => {
// TODO: Implement filter modal
}}
>
<Text style={styles.actionButtonText}>Filter</Text>
</TouchableOpacity>
</View> */}
</View>
);
/**
* Render Empty State
*
* Purpose: Render empty state when no patients found
*/
const renderEmptyState = () => (
<EmptyState
title="No Patients Found"
subtitle={searchQuery.trim() ?
`No patients match "${searchQuery}"` :
"No patients available at the moment"
}
iconName="users"
onRetry={handleRefresh}
retryText="Refresh"
/>
);
// ============================================================================
// MAIN RENDER
// ============================================================================
if (error && !isLoading) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.errorContainer}>
<Text style={styles.errorTitle}>Error Loading Patients</Text>
<Text style={styles.errorMessage}>{error}</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={handleRefresh}
activeOpacity={0.7}
>
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
{/* Header */}
{renderHeader()}
{/* Search and Filters */}
<View style={styles.searchAndFilters}>
<SearchBar
value={searchQuery}
onChangeText={handleSearch}
placeholder="Search patients, ID, institution..."
/>
<FilterTabs
selectedFilter={selectedFilter}
onFilterChange={handleFilterChange}
patientCounts={patientCounts}
/>
</View>
{/* Loading State */}
{isLoading && patients.length === 0 && (
<View style={styles.centerContainer}>
<LoadingState />
</View>
)}
{/* Error State */}
{error && patients.length === 0 && (
<View style={styles.centerContainer}>
<EmptyState
iconName="alert-circle"
title="Error Loading Patients"
subtitle={error}
retryText="Retry"
onRetry={handleRefresh}
/>
</View>
)}
{/* Empty State */}
{!isLoading && !error && patients.length === 0 && (
<View style={styles.centerContainer}>
<EmptyState
iconName="users"
title="No Patients Found"
subtitle="There are no patients in the system yet."
retryText="Refresh"
onRetry={handleRefresh}
/>
</View>
)}
{/* No Results State */}
{!isLoading && !error && patients.length > 0 && filteredPatients.length === 0 && (
<View style={styles.centerContainer}>
<EmptyState
iconName="search"
title="No Results Found"
subtitle={`No patients match your search "${searchQuery}" and filter "${selectedFilter}"`}
retryText="Clear Search"
onRetry={() => {
handleSearch('');
handleFilterChange('all');
}}
/>
</View>
)}
{/* Patient List */}
{!isLoading && !error && filteredPatients.length > 0 && (
<FlatList
data={filteredPatients}
renderItem={renderPatientCard}
keyExtractor={(item) => item.patid}
contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
/>
)}
{/* TODO: Implement sort and filter modals for enhanced functionality */}
{/* Note: Patient data will be loaded from API when fetchPatients is called */}
{/* Currently using mock data from Redux slice for development */}
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
// Container Styles
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
// Header Styles
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
backgroundColor: theme.colors.background,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
marginBottom: theme.spacing.md,
},
headerLeft: {
flex: 1,
},
headerRight: {
flexDirection: 'row',
gap: theme.spacing.sm,
},
headerTitle: {
fontSize: 24,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
},
headerSubtitle: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
},
actionButton: {
backgroundColor: theme.colors.backgroundAlt,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: 8,
borderWidth: 1,
borderColor: theme.colors.border,
},
actionButtonText: {
color: theme.colors.textSecondary,
fontSize: 14,
fontFamily: theme.typography.fontFamily.medium,
},
// Search and Filters
searchAndFilters: {
paddingHorizontal: theme.spacing.md,
paddingBottom: theme.spacing.sm,
backgroundColor: theme.colors.background,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
// Center Container for States
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.md,
},
// List Styles
listContainer: {
paddingBottom: theme.spacing.lg,
},
listFooter: {
paddingVertical: theme.spacing.md,
alignItems: 'center',
},
footerText: {
fontSize: 14,
color: theme.colors.textMuted,
fontFamily: theme.typography.fontFamily.regular,
},
// Error State Styles
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.xl,
},
errorTitle: {
fontSize: 20,
color: theme.colors.error,
marginBottom: theme.spacing.sm,
fontFamily: theme.typography.fontFamily.bold,
textAlign: 'center',
},
errorMessage: {
fontSize: 16,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.lg,
fontFamily: theme.typography.fontFamily.regular,
textAlign: 'center',
},
retryButton: {
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
borderRadius: 8,
minWidth: 120,
alignItems: 'center',
},
retryButtonText: {
color: theme.colors.background,
fontSize: 16,
fontFamily: theme.typography.fontFamily.medium,
},
});
export default PatientsScreen;
/*
* End of File: PatientsScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/