NeoScan_Physician/app/modules/Profile/screens/ProfileScreen.tsx
2025-07-22 17:36:29 +05:30

504 lines
14 KiB
TypeScript

/*
* File: ProfileScreen.tsx
* Description: Screen for displaying and managing user profile information, with editable name fields.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
Image,
ScrollView,
TouchableOpacity,
Alert,
Switch,
Modal, // Import Modal
TextInput, // Import TextInput
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { useProfile } from '../hooks/useProfile';
import {
Colors,
Spacing,
Typography,
Shadows,
BorderRadius,
} from '../../../../shared/src/theme';
import { Button } from '../../../../shared/src/components/Button';
import { useDispatch } from 'react-redux';
import { logout } from '../../Auth/redux/authSlice';
interface InfoRowProps {
icon: string;
label?: string;
value?: string;
onEdit?: () => void;
isNav?: boolean;
isLogout?: boolean;
}
const InfoRow: React.FC<InfoRowProps> = ({
icon,
label,
value,
onEdit,
isNav,
isLogout,
}) => (
<TouchableOpacity
style={[styles.infoCard, isLogout && styles.logoutCard]}
onPress={onEdit}
disabled={!onEdit}>
<Icon
name={icon}
size={22}
color={isLogout ? Colors.error : Colors.textSecondary}
style={styles.infoIcon}
/>
<Text
style={[
styles.infoValue,
!value && styles.infoLabel,
isLogout && styles.logoutText,
]}>
{value || label}
</Text>
{onEdit && !isNav && !isLogout && (
<Icon name="edit-2" size={20} color={Colors.textMuted} />
)}
{isNav && (
<Icon name="chevron-right" size={22} color={Colors.textMuted} />
)}
</TouchableOpacity>
);
const SectionHeader: React.FC<{ title: string; icon?: string }> = ({ title, icon }) => (
<View style={styles.sectionHeader}>
{icon && <Icon name={icon} size={18} color={Colors.primary} style={{ marginRight: 8 }} />}
<Text style={styles.sectionHeaderText}>{title}</Text>
</View>
);
// New component for individual notification toggles
const NotificationToggle: React.FC<{
label: string;
isEnabled: boolean;
onToggle: (value: boolean) => void;
}> = ({ label, isEnabled, onToggle }) => (
<View style={styles.notificationRow}>
<Text style={styles.notificationLabel}>{label}</Text>
<Switch
value={isEnabled}
onValueChange={onToggle}
trackColor={{ false: Colors.inactiveState, true: Colors.success }}
thumbColor={Colors.background}
/>
</View>
);
const ProfileScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
const initialUser = useProfile() || {
user_id: '9fe5cd7f-e563-4c7f-93ec-6de64e12b83e',
email: 'radiologist@gmail.com',
first_name: 'Aman',
last_name: 'Kumar',
display_name: 'Aman Kumar',
dashboard_role: 'radiologist',
hospital_id: '0240c6d9-415f-49c9-ae24-7eba4927f939',
dashboard_settings: {
theme: 'light',
language: 'en',
timezone: 'UTC',
},
notification_preferences: {
critical_alerts: {
sms: true,
push: true,
email: true,
in_app: true,
},
system_notifications: {
push: true,
email: true,
in_app: true,
},
},
onboarding_completed: false,
onboarding_step: 0,
onboarding_message: 'You need to complete onboarding (change password and upload profile photo).',
onboarded: false,
profile_photo_url: null,
theme_color: null,
accent_color: null,
};
// State for user data, notifications, and the edit modal
const [user, setUser] = useState(initialUser);
const [notifications, setNotifications] = useState(user.notification_preferences);
const [isModalVisible, setModalVisible] = useState(false);
const [editingField, setEditingField] = useState('');
const [currentValue, setCurrentValue] = useState('');
const dispatch=useDispatch();
const handleLogout = () => {
Alert.alert('Log Out', 'Are you sure you want to log out?', [
{ text: 'Cancel', style: 'cancel' },
{ text: 'OK', onPress: () => dispatch(logout()) },
]);
};
// Updated handleEdit to open the modal
const handleEdit = (field: string, value: string) => {
setEditingField(field);
setCurrentValue(value);
setModalVisible(true);
};
// New function to save changes from the modal
const handleSave = () => {
const updatedUser = { ...user };
if (editingField === 'First Name') {
updatedUser.first_name = currentValue;
} else if (editingField === 'Last Name') {
updatedUser.last_name = currentValue;
}
updatedUser.display_name = `${updatedUser.first_name} ${updatedUser.last_name}`;
setUser(updatedUser);
setModalVisible(false);
Alert.alert('Profile Updated', `${editingField} has been changed.`);
};
const handleNotificationToggle = (
category: 'critical_alerts' | 'system_notifications',
channel: string,
) => {
const newPrefs = { ...notifications };
// Asserting the channel type to fix TypeScript indexing error
(newPrefs[category] as any)[channel] = !(newPrefs[category] as any)[channel];
setNotifications(newPrefs);
// In a real app, you would dispatch an action here to save the new preferences.
Alert.alert(
'Preference Updated',
`${channel.toUpperCase()} for ${category.replace('_', ' ')} has been ${
(newPrefs[category] as any)[channel] ? 'enabled' : 'disabled'
}.`,
);
};
return (
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false}>
{/* Profile Section */}
<View style={styles.header}>
<View style={styles.avatarContainer}>
<Image
source={{ uri: user.profile_photo_url || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(user.display_name) }}
style={styles.avatar}
/>
<TouchableOpacity
style={styles.editIconContainer}
// onPress={() =>
// handleEdit('Profile Photo', user.profile_photo_url || '')
// }
>
<Icon name="edit-2" size={16} color={Colors.background} />
</TouchableOpacity>
</View>
<Text style={styles.name}>{user.display_name}</Text>
<Text style={styles.role}>{user.dashboard_role}</Text>
<Text style={styles.email}>{user.email}</Text>
</View>
{/* Onboarding Section */}
{!user.onboarding_completed && (
<View style={styles.onboardingSection}>
<SectionHeader title="Onboarding" icon="alert-circle" />
<Text style={styles.onboardingMessage}>{user.onboarding_message}</Text>
</View>
)}
{/* Personal Information Section - New */}
<View style={styles.infoSection}>
<SectionHeader title="Personal Information" icon="user" />
<InfoRow
icon="user"
label="First Name"
value={user.first_name}
onEdit={() => handleEdit('First Name', user.first_name)}
/>
<InfoRow
icon="user"
label="Last Name"
value={user.last_name}
onEdit={() => handleEdit('Last Name', user.last_name)}
/>
</View>
{/* Settings Section */}
{/* <View style={styles.infoSection}>
<SectionHeader title="Settings" icon="settings" />
<InfoRow icon="sun" label="Theme" value={user.dashboard_settings?.theme || '-'} />
<InfoRow icon="globe" label="Language" value={user.dashboard_settings?.language || '-'} />
<InfoRow icon="clock" label="Timezone" value={user.dashboard_settings?.timezone || '-'} />
</View> */}
{/* Notification Preferences Section - Updated with Toggles */}
<View style={styles.infoSection}>
<SectionHeader title="Notification Preferences" icon="bell" />
<View style={styles.notificationGroup}>
<Text style={styles.notificationCategory}>Critical Alerts</Text>
{Object.entries(notifications.critical_alerts).map(([channel, isEnabled]) => (
<NotificationToggle
key={`critical-${channel}`}
label={channel.toUpperCase()}
isEnabled={isEnabled}
onToggle={() => handleNotificationToggle('critical_alerts', channel)}
/>
))}
</View>
<View style={styles.notificationGroup}>
<Text style={styles.notificationCategory}>System Notifications</Text>
{Object.entries(notifications.system_notifications).map(
([channel, isEnabled]) => (
<NotificationToggle
key={`system-${channel}`}
label={channel.toUpperCase()}
isEnabled={isEnabled}
onToggle={() => handleNotificationToggle('system_notifications', channel)}
/>
),
)}
</View>
</View>
{/* Actions Section */}
<View style={styles.actionsSection}>
<InfoRow
icon="help-circle"
label="Support"
isNav
onEdit={() => navigation.navigate('Support')}
/>
<InfoRow icon="log-out" label="Log Out" isLogout onEdit={handleLogout} />
</View>
{/* Edit Modal */}
<Modal
transparent={true}
visible={isModalVisible}
onRequestClose={() => setModalVisible(false)}>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Edit {editingField}</Text>
<TextInput
style={styles.modalInput}
value={currentValue}
onChangeText={setCurrentValue}
autoFocus
/>
<View style={styles.modalActions}>
<Button title="Cancel" onPress={() => setModalVisible(false)} style={styles.modalButton} />
<Button title="Save" onPress={handleSave} style={styles.modalButton} />
</View>
</View>
</View>
</Modal>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.backgroundAlt,
},
contentContainer: {
paddingBottom: Spacing.xl,
},
header: {
alignItems: 'center',
paddingVertical: Spacing.xl,
paddingHorizontal: Spacing.lg,
},
avatarContainer: {
position: 'relative',
marginBottom: Spacing.md,
},
avatar: {
width: 100,
height: 100,
borderRadius: 50,
borderWidth: 3,
borderColor: Colors.primary,
},
editIconContainer: {
position: 'absolute',
bottom: 2,
right: 2,
backgroundColor: Colors.primary,
padding: Spacing.xs,
borderRadius: 15,
borderWidth: 2,
borderColor: Colors.background,
},
name: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.xl,
color: Colors.textPrimary,
textAlign: 'center',
},
role: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
textAlign: 'center',
marginBottom: Spacing.xs,
},
email: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textMuted,
textAlign: 'center',
marginBottom: Spacing.md,
},
onboardingSection: {
marginHorizontal: Spacing.lg,
marginBottom: Spacing.md,
backgroundColor: Colors.warning + '22',
borderRadius: BorderRadius.md,
padding: Spacing.md,
},
onboardingMessage: {
color: Colors.warning,
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.sm,
textAlign: 'center',
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: Spacing.xs,
marginTop: Spacing.md,
},
sectionHeaderText: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.primary,
},
infoSection: {
marginHorizontal: Spacing.lg,
},
actionsSection: {
marginHorizontal: Spacing.lg,
marginTop: Spacing.lg,
borderTopWidth: 1,
borderTopColor: Colors.border,
paddingTop: Spacing.lg,
},
infoCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: Colors.cardBackground,
padding: Spacing.md,
borderRadius: BorderRadius.md,
marginBottom: Spacing.md,
...Shadows.sm,
},
infoIcon: {
marginRight: Spacing.md,
},
infoLabel: {
flex: 1,
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
},
infoValue: {
flex: 1,
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
},
logoutCard: {
backgroundColor: 'transparent',
...Shadows.none,
},
logoutText: {
color: Colors.error,
fontFamily: Typography.fontFamily.bold,
},
notificationGroup: {
backgroundColor: Colors.cardBackground,
borderRadius: BorderRadius.md,
padding: Spacing.md,
marginBottom: Spacing.md,
...Shadows.sm,
},
notificationCategory: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.md,
color: Colors.textPrimary,
marginBottom: Spacing.sm,
},
notificationRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: Spacing.sm,
borderBottomWidth: 1,
borderBottomColor: Colors.border,
},
notificationLabel: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
},
modalContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
},
modalContent: {
width: '80%',
backgroundColor: Colors.cardBackground,
borderRadius: BorderRadius.md,
padding: Spacing.lg,
...Shadows.card,
},
modalTitle: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.textPrimary,
marginBottom: Spacing.md,
},
modalInput: {
borderWidth: 1,
borderColor: Colors.border,
borderRadius: BorderRadius.sm,
padding: Spacing.sm,
marginBottom: Spacing.lg,
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
},
modalActions: {
flexDirection: 'row',
justifyContent: 'flex-end',
},
modalButton: {
marginLeft: Spacing.sm,
},
});
export default ProfileScreen;
/*
* End of File: ProfileScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/