/* * 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, useState, useCallback } from 'react'; import { View, Text, StyleSheet, ScrollView, RefreshControl, TouchableOpacity, StatusBar, Alert, FlatList, Dimensions, } 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 patient care functionality import { fetchPatients, setSearchQuery, setFilter, setSort, clearError } from '../redux/patientCareSlice'; // Import patient care selectors import { selectPatients, selectPatientsLoading, selectPatientsError, selectIsRefreshing, selectSearchQuery, selectSelectedFilter, selectSortBy, selectFilteredPatients, } from '../redux/patientCareSelectors'; // Import auth selectors import { selectUser } from '../../Auth/redux/authSelectors'; // Import components import PatientCard from '../components/PatientCard'; import SearchBar from '../components/SearchBar'; import FilterTabs from '../components/FilterTabs'; import EmptyState from '../components/EmptyState'; import LoadingState from '../components/LoadingState'; // Import types import { MedicalCase, PatientDetails, Series } from '../../../shared/types'; // Get screen dimensions const { width: screenWidth } = Dimensions.get('window'); // ============================================================================ // INTERFACES // ============================================================================ interface PatientsScreenProps { navigation: any; } // ============================================================================ // 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, Active, Critical, Discharged) * - Sort options (Priority, Name, Date) * - 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 = ({ navigation }) => { // ============================================================================ // STATE MANAGEMENT // ============================================================================ const dispatch = useAppDispatch(); // 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 sortBy = useAppSelector(selectSortBy); const user = useAppSelector(selectUser); // Local state const [showSortModal, setShowSortModal] = useState(false); // ============================================================================ // LIFECYCLE METHODS // ============================================================================ /** * Component Mount Effect * * Purpose: Initialize screen and fetch patient data */ useEffect(() => { // Fetch patients on mount handleFetchPatients(); // Set up navigation focus listener for real-time updates const unsubscribe = navigation.addListener('focus', () => { handleRefresh(); }); return unsubscribe; }, [navigation]); /** * Error Handling Effect * * Purpose: Display error alerts and clear errors */ useEffect(() => { if (error) { Alert.alert( 'Error', error, [ { text: 'Retry', onPress: handleFetchPatients, }, { text: 'OK', onPress: () => dispatch(clearError()), }, ] ); } }, [error]); // ============================================================================ // EVENT HANDLERS // ============================================================================ /** * Handle Fetch Patients * * Purpose: Fetch patients from API */ const handleFetchPatients = useCallback(() => { if (user?.access_token) { dispatch(fetchPatients(user.access_token)); } }, [dispatch, user?.access_token]); /** * Handle Refresh * * Purpose: Pull-to-refresh functionality */ const handleRefresh = useCallback(() => { handleFetchPatients(); }, [handleFetchPatients]); /** * Handle Search * * Purpose: Handle search input changes * * @param query - Search query string */ const handleSearch = useCallback((query: string) => { dispatch(setSearchQuery(query)); }, [dispatch]); /** * Handle Filter Change * * Purpose: Handle filter tab selection * * @param filter - Selected filter option */ const handleFilterChange = useCallback((filter: 'all' | 'Critical' | 'Routine' | 'Emergency') => { dispatch(setFilter(filter)); }, [dispatch]); /** * Handle Sort Change * * Purpose: Handle sort option selection * * @param sortOption - Selected sort option */ const handleSortChange = useCallback((sortOption: 'date' | 'name' | 'age') => { dispatch(setSort({ by: sortOption, order: 'desc' })); setShowSortModal(false); }, [dispatch]); /** * Handle Patient Press * * Purpose: Navigate to patient details screen * * @param patient - Selected patient */ const handlePatientPress = useCallback((patient: MedicalCase) => { // Helper function to parse JSON strings safely const parseJsonSafely = (jsonString: string | object) => { if (typeof jsonString === 'object') { return jsonString; } if (typeof jsonString === 'string') { try { return JSON.parse(jsonString); } catch (error) { console.warn('Failed to parse JSON:', error); return {}; } } return {}; }; const patientDetails = parseJsonSafely(patient.patientdetails); const patientData = patientDetails.patientdetails || patientDetails; navigation.navigate('PatientDetails', { patientId: patient.id, patientName: patientData.Name || 'Unknown Patient', medicalCase: patient, }); }, [navigation]); /** * Handle Emergency Alert * * Purpose: Handle emergency alert for critical patients * * @param patient - Patient with emergency */ const handleEmergencyAlert = useCallback((patient: MedicalCase) => { // Helper function to parse JSON strings safely const parseJsonSafely = (jsonString: string | object) => { if (typeof jsonString === 'object') { return jsonString; } if (typeof jsonString === 'string') { try { return JSON.parse(jsonString); } catch (error) { console.warn('Failed to parse JSON:', error); return {}; } } return {}; }; const patientDetails = parseJsonSafely(patient.patientdetails); const patientData = patientDetails.patientdetails || patientDetails; Alert.alert( 'Emergency Alert', `Critical status for ${patientData.Name || 'Unknown Patient'}\nID: ${patientData.PatID || 'N/A'}`, [ { text: 'View Details', onPress: () => handlePatientPress(patient), }, { text: 'Call Physician', onPress: () => { // TODO: Implement physician calling functionality Alert.alert('Calling', `Calling attending physician...`); }, }, { text: 'Cancel', style: 'cancel', }, ] ); }, [handlePatientPress]); // ============================================================================ // RENDER HELPERS // ============================================================================ /** * Render Patient Item * * Purpose: Render individual patient card * * @param item - Patient data with render info */ const renderPatientItem = ({ item }: { item: MedicalCase }) => ( handlePatientPress(item)} onEmergencyPress={() => handleEmergencyAlert(item)} /> ); /** * Render Empty State * * Purpose: Render empty state when no patients found */ const renderEmptyState = () => { if (isLoading) return null; return ( ); }; /** * Render Loading State * * Purpose: Render loading state during initial fetch */ if (isLoading && patients.length === 0) { return ( ); } // ============================================================================ // MAIN RENDER // ============================================================================ return ( {/* Fixed Header */} navigation.goBack()} > Patients Emergency Department { // TODO: Implement notifications screen navigation.navigate('Notifications'); }} > {/* Notification badge */} 3 {/* Fixed Search and Filter Section */} {/* Search Bar */} setShowSortModal(true)} /> {/* Filter Tabs */} p.type === 'Critical').length, Routine: patients.filter((p: MedicalCase) => p.type === 'Routine').length, Emergency: patients.filter((p: MedicalCase) => p.type === 'Emergency').length, }} /> {/* Results Summary */} {filteredPatients.length} patient{filteredPatients.length !== 1 ? 's' : ''} found Sorted by {sortBy} {/* Scrollable Patient List Only */} index.toString()} ListEmptyComponent={renderEmptyState} contentContainerStyle={[ styles.listContent, filteredPatients.length === 0 && styles.emptyListContent ]} showsVerticalScrollIndicator={false} refreshControl={ } // Performance optimizations removeClippedSubviews={true} maxToRenderPerBatch={10} windowSize={10} initialNumToRender={8} getItemLayout={(data, index) => ({ length: 120, // Approximate height of PatientCard offset: 120 * index, index, })} /> ); }; // ============================================================================ // STYLES // ============================================================================ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.colors.background, }, // Header Styles header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: theme.spacing.md, paddingVertical: theme.spacing.sm, backgroundColor: theme.colors.background, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, headerLeft: { flexDirection: 'row', alignItems: 'center', flex: 1, }, backButton: { marginRight: theme.spacing.sm, padding: theme.spacing.xs, }, headerTitle: { fontSize: 24, fontWeight: 'bold', color: theme.colors.textPrimary, fontFamily: theme.typography.fontFamily.bold, }, headerSubtitle: { fontSize: 14, color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily.bold, }, headerRight: { flexDirection: 'row', alignItems: 'center', }, headerButton: { padding: theme.spacing.sm, marginLeft: theme.spacing.xs, position: 'relative', }, notificationBadge: { position: 'absolute', top: 6, right: 6, backgroundColor: theme.colors.error, borderRadius: 8, width: 16, height: 16, justifyContent: 'center', alignItems: 'center', }, badgeText: { color: theme.colors.background, fontSize: 10, fontWeight: 'bold', }, // Fixed Section Styles fixedSection: { paddingHorizontal: theme.spacing.md, paddingTop: theme.spacing.sm, paddingBottom: theme.spacing.md, backgroundColor: theme.colors.background, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, searchContainer: { marginBottom: theme.spacing.md, }, filterContainer: { marginBottom: theme.spacing.sm, }, // List Styles listContent: { paddingTop: theme.spacing.sm, paddingBottom: theme.spacing.xl, }, emptyListContent: { flexGrow: 1, }, // Results Summary resultsSummary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: theme.spacing.sm, paddingHorizontal: theme.spacing.sm, backgroundColor: theme.colors.backgroundAlt, borderRadius: 8, marginTop: theme.spacing.xs, }, resultsLeft: { flexDirection: 'row', alignItems: 'center', }, resultsText: { fontSize: 14, color: theme.colors.textPrimary, fontWeight: '500', marginLeft: theme.spacing.xs, }, sortInfo: { flexDirection: 'row', alignItems: 'center', }, sortText: { fontSize: 12, color: theme.colors.textSecondary, textTransform: 'capitalize', marginLeft: theme.spacing.xs, }, }); export default PatientsScreen; /* * End of File: PatientsScreen.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */