592 lines
15 KiB
TypeScript
592 lines
15 KiB
TypeScript
/*
|
|
* File: HospitalSelectionStep.tsx
|
|
* Description: Hospital selection step component for signup flow
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import React, { useState, useMemo } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
ScrollView,
|
|
FlatList,
|
|
ActivityIndicator,
|
|
TextInput,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
} from 'react-native';
|
|
import { theme } from '../../../../theme/theme';
|
|
import { HospitalSelectionStepProps, Hospital } from '../../types/signup';
|
|
import Icon from 'react-native-vector-icons/Feather';
|
|
|
|
// ============================================================================
|
|
// HOSPITAL SELECTION STEP COMPONENT
|
|
// ============================================================================
|
|
|
|
/**
|
|
* HospitalSelectionStep Component
|
|
*
|
|
* Purpose: Fifth step of signup flow - hospital selection
|
|
*
|
|
* Features:
|
|
* - Hospital list display from Redux state
|
|
* - Hospital selection with visual feedback
|
|
* - Search functionality for hospitals
|
|
* - Loading states and error handling
|
|
* - Continue button with validation (sticky bottom)
|
|
* - Back navigation with modern header
|
|
* - Scrollable hospital list
|
|
*/
|
|
const HospitalSelectionStep: React.FC<HospitalSelectionStepProps> = ({
|
|
onContinue,
|
|
onBack,
|
|
data,
|
|
isLoading,
|
|
hospitals,
|
|
hospitalLoading,
|
|
}) => {
|
|
// ============================================================================
|
|
// STATE MANAGEMENT
|
|
// ============================================================================
|
|
|
|
const [selectedHospitalId, setSelectedHospitalId] = useState(data.hospital_id || '');
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
// ============================================================================
|
|
// COMPUTED VALUES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Filtered Hospitals
|
|
*
|
|
* Purpose: Filter hospitals based on search query
|
|
*/
|
|
const filteredHospitals = useMemo(() => {
|
|
if (!hospitals) return [];
|
|
|
|
if (!searchQuery.trim()) return hospitals;
|
|
|
|
const query = searchQuery.toLowerCase();
|
|
return hospitals.filter(hospital =>
|
|
hospital.hospital_name?.toLowerCase().includes(query)
|
|
);
|
|
}, [hospitals, searchQuery]);
|
|
|
|
// ============================================================================
|
|
// HANDLERS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Handle Hospital Selection
|
|
*
|
|
* Purpose: Select a hospital
|
|
*/
|
|
const handleHospitalSelect = (hospitalId: string) => {
|
|
setSelectedHospitalId(hospitalId);
|
|
};
|
|
|
|
/**
|
|
* Handle Continue
|
|
*
|
|
* Purpose: Validate selection and proceed to next step
|
|
*/
|
|
const handleContinue = () => {
|
|
if (!selectedHospitalId) {
|
|
// Show error or alert
|
|
return;
|
|
}
|
|
onContinue(selectedHospitalId);
|
|
};
|
|
|
|
/**
|
|
* Handle Search Input Change
|
|
*
|
|
* Purpose: Update search query
|
|
*/
|
|
const handleSearchChange = (text: string) => {
|
|
setSearchQuery(text);
|
|
};
|
|
|
|
/**
|
|
* Clear Search
|
|
*
|
|
* Purpose: Clear search query
|
|
*/
|
|
const handleClearSearch = () => {
|
|
setSearchQuery('');
|
|
};
|
|
|
|
// ============================================================================
|
|
// RENDER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Render Hospital Item
|
|
*
|
|
* Purpose: Render individual hospital item
|
|
*/
|
|
const renderHospitalItem = ({ item }: { item: Hospital }) => {
|
|
const isSelected = selectedHospitalId === item.hospital_id;
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.hospitalItem,
|
|
isSelected && styles.hospitalItemSelected,
|
|
]}
|
|
onPress={() => handleHospitalSelect(item.hospital_id || '')}
|
|
disabled={isLoading}
|
|
>
|
|
<View style={styles.hospitalContent}>
|
|
<View style={styles.hospitalInfo}>
|
|
<Text style={[
|
|
styles.hospitalName,
|
|
isSelected && styles.hospitalNameSelected,
|
|
]}>
|
|
{item.hospital_name || 'Unknown Hospital'}
|
|
</Text>
|
|
</View>
|
|
|
|
{isSelected && (
|
|
<View style={styles.selectedIcon}>
|
|
<Icon name="check" size={20} color={theme.colors.background} />
|
|
</View>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Render Search Input
|
|
*
|
|
* Purpose: Render search input field
|
|
*/
|
|
const renderSearchInput = () => (
|
|
<View style={styles.searchContainer}>
|
|
<View style={styles.searchInputWrapper}>
|
|
<Icon name="search" size={20} color={theme.colors.textMuted} style={styles.searchIcon} />
|
|
<TextInput
|
|
style={styles.searchInput}
|
|
placeholder="Search hospitals..."
|
|
placeholderTextColor={theme.colors.textMuted}
|
|
value={searchQuery}
|
|
onChangeText={handleSearchChange}
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
editable={!hospitalLoading}
|
|
/>
|
|
{searchQuery.length > 0 && (
|
|
<TouchableOpacity onPress={handleClearSearch} style={styles.clearButton}>
|
|
<Icon name="x" size={18} color={theme.colors.textMuted} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
/**
|
|
* Render Loading State
|
|
*
|
|
* Purpose: Show loading indicator while fetching hospitals
|
|
*/
|
|
const renderLoadingState = () => (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
<Text style={styles.loadingText}>Loading hospitals...</Text>
|
|
</View>
|
|
);
|
|
|
|
/**
|
|
* Render Empty State
|
|
*
|
|
* Purpose: Show message when no hospitals are available
|
|
*/
|
|
const renderEmptyState = () => (
|
|
<View style={styles.emptyContainer}>
|
|
<Icon name="building" size={48} color={theme.colors.textMuted} />
|
|
<Text style={styles.emptyTitle}>
|
|
{searchQuery ? 'No Hospitals Found' : 'No Hospitals Available'}
|
|
</Text>
|
|
<Text style={styles.emptyText}>
|
|
{searchQuery
|
|
? 'Try adjusting your search terms or browse all hospitals.'
|
|
: 'There are no hospitals available at the moment. Please try again later.'
|
|
}
|
|
</Text>
|
|
</View>
|
|
);
|
|
|
|
/**
|
|
* Render Search Results Info
|
|
*
|
|
* Purpose: Show search results count
|
|
*/
|
|
const renderSearchResultsInfo = () => {
|
|
if (!searchQuery.trim()) return null;
|
|
|
|
return (
|
|
<View style={styles.searchResultsInfo}>
|
|
<Text style={styles.searchResultsText}>
|
|
{filteredHospitals.length} hospital{filteredHospitals.length !== 1 ? 's' : ''} found
|
|
</Text>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// RENDER
|
|
// ============================================================================
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Header */}
|
|
<View style={styles.header}>
|
|
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
|
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
|
|
</TouchableOpacity>
|
|
|
|
<View style={styles.headerContent}>
|
|
<Text style={styles.title}>Create Account</Text>
|
|
<Text style={styles.subtitle}>Step 5 of 5</Text>
|
|
</View>
|
|
|
|
<View style={styles.headerSpacer} />
|
|
</View>
|
|
|
|
{/* Content */}
|
|
<View style={styles.content}>
|
|
<Text style={styles.sectionTitle}>Select Your Hospital</Text>
|
|
<Text style={styles.description}>
|
|
Choose the hospital where you work or will be practicing.
|
|
</Text>
|
|
|
|
{/* Search Input */}
|
|
{renderSearchInput()}
|
|
|
|
{/* Search Results Info */}
|
|
{renderSearchResultsInfo()}
|
|
|
|
{/* Hospital List */}
|
|
<View style={styles.hospitalListContainer}>
|
|
{hospitalLoading ? (
|
|
renderLoadingState()
|
|
) : filteredHospitals.length > 0 ? (
|
|
<FlatList
|
|
data={filteredHospitals}
|
|
renderItem={renderHospitalItem}
|
|
keyExtractor={(item) => item.hospital_id || ''}
|
|
showsVerticalScrollIndicator={true}
|
|
contentContainerStyle={styles.hospitalList}
|
|
keyboardShouldPersistTaps="handled"
|
|
/>
|
|
) : (
|
|
renderEmptyState()
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Sticky Continue Button */}
|
|
<View style={styles.stickyButtonContainer}>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.continueButton,
|
|
(!selectedHospitalId || isLoading || hospitalLoading)
|
|
? styles.continueButtonDisabled
|
|
: null,
|
|
]}
|
|
onPress={handleContinue}
|
|
disabled={!selectedHospitalId || isLoading || hospitalLoading}
|
|
>
|
|
<Text style={[
|
|
styles.continueButtonText,
|
|
(!selectedHospitalId || isLoading || hospitalLoading)
|
|
? styles.continueButtonTextDisabled
|
|
: null,
|
|
]}>
|
|
{isLoading ? 'Creating Account...' : 'Create Account'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// STYLES
|
|
// ============================================================================
|
|
|
|
const styles = StyleSheet.create({
|
|
// Main container
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: theme.colors.background,
|
|
},
|
|
|
|
// Header section
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingTop: theme.spacing.xl,
|
|
paddingBottom: theme.spacing.lg,
|
|
paddingHorizontal: theme.spacing.lg,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: theme.colors.border,
|
|
},
|
|
|
|
// Back button
|
|
backButton: {
|
|
padding: theme.spacing.sm,
|
|
borderRadius: theme.borderRadius.medium,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
},
|
|
|
|
// Header content
|
|
headerContent: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
},
|
|
|
|
// Header spacer
|
|
headerSpacer: {
|
|
width: 40,
|
|
},
|
|
|
|
// Title
|
|
title: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Subtitle
|
|
subtitle: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
// Content section
|
|
content: {
|
|
flex: 1,
|
|
paddingHorizontal: theme.spacing.lg,
|
|
paddingTop: theme.spacing.lg,
|
|
},
|
|
|
|
// Section title
|
|
sectionTitle: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Description
|
|
description: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Search container
|
|
searchContainer: {
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Search input wrapper
|
|
searchInputWrapper: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
borderRadius: theme.borderRadius.medium,
|
|
paddingHorizontal: theme.spacing.md,
|
|
},
|
|
|
|
// Search icon
|
|
searchIcon: {
|
|
marginRight: theme.spacing.sm,
|
|
},
|
|
|
|
// Search input
|
|
searchInput: {
|
|
flex: 1,
|
|
paddingVertical: theme.spacing.md,
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Clear button
|
|
clearButton: {
|
|
padding: theme.spacing.xs,
|
|
marginLeft: theme.spacing.sm,
|
|
},
|
|
|
|
// Search results info
|
|
searchResultsInfo: {
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
// Search results text
|
|
searchResultsText: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
// Hospital list container
|
|
hospitalListContainer: {
|
|
flex: 1,
|
|
},
|
|
|
|
// Hospital list
|
|
hospitalList: {
|
|
paddingBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Hospital item
|
|
hospitalItem: {
|
|
backgroundColor: theme.colors.background,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.md,
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Hospital item selected
|
|
hospitalItemSelected: {
|
|
borderColor: theme.colors.primary,
|
|
backgroundColor: theme.colors.tertiary,
|
|
},
|
|
|
|
// Hospital content
|
|
hospitalContent: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
|
|
// Hospital info
|
|
hospitalInfo: {
|
|
flex: 1,
|
|
},
|
|
|
|
// Hospital name
|
|
hospitalName: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Hospital name selected
|
|
hospitalNameSelected: {
|
|
color: theme.colors.primary,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
},
|
|
|
|
// Selected icon
|
|
selectedIcon: {
|
|
width: 24,
|
|
height: 24,
|
|
borderRadius: 12,
|
|
backgroundColor: theme.colors.primary,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
|
|
// Loading container
|
|
loadingContainer: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: theme.spacing.xxl,
|
|
},
|
|
|
|
// Loading text
|
|
loadingText: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
marginTop: theme.spacing.md,
|
|
},
|
|
|
|
// Empty container
|
|
emptyContainer: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: theme.spacing.xxl,
|
|
},
|
|
|
|
// Empty title
|
|
emptyTitle: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginTop: theme.spacing.md,
|
|
marginBottom: theme.spacing.sm,
|
|
},
|
|
|
|
// Empty text
|
|
emptyText: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
textAlign: 'center',
|
|
paddingHorizontal: theme.spacing.lg,
|
|
},
|
|
|
|
// Sticky button container
|
|
stickyButtonContainer: {
|
|
paddingHorizontal: theme.spacing.lg,
|
|
paddingVertical: theme.spacing.lg,
|
|
backgroundColor: theme.colors.background,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
},
|
|
|
|
// Continue button
|
|
continueButton: {
|
|
backgroundColor: theme.colors.primary,
|
|
borderRadius: theme.borderRadius.medium,
|
|
paddingVertical: theme.spacing.md,
|
|
paddingHorizontal: theme.spacing.lg,
|
|
alignItems: 'center',
|
|
...theme.shadows.primary,
|
|
},
|
|
|
|
// Continue button disabled
|
|
continueButtonDisabled: {
|
|
backgroundColor: theme.colors.border,
|
|
opacity: 0.6,
|
|
},
|
|
|
|
// Continue button text
|
|
continueButtonText: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.background,
|
|
},
|
|
|
|
// Continue button text disabled
|
|
continueButtonTextDisabled: {
|
|
color: theme.colors.textMuted,
|
|
},
|
|
});
|
|
|
|
export default HospitalSelectionStep;
|
|
|
|
/*
|
|
* End of File: HospitalSelectionStep.tsx
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|