WellNuo/app/beneficiaries/[id]/dashboard.tsx
Sergei 915664d4cc Add scheme files, beneficiaries module, dashboard improvements
Changes:
- Add wellnuoSheme/ folder with project documentation
- Rename patients -> beneficiaries (proper WellNuo terminology)
- Add BeneficiaryContext for state management
- Update API service with WellNuo endpoints
- Add dashboard screen for beneficiary overview
- Update navigation and layout

Scheme files include:
- API documentation with credentials
- Project description
- System analysis
- UX flow
- Legal documents (privacy, terms, support)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 13:38:38 -08:00

264 lines
7.6 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
import { WebView } from 'react-native-webview';
import { Ionicons } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useLocalSearchParams, router } from 'expo-router';
import { useBeneficiary } from '@/contexts/BeneficiaryContext';
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
import { FullScreenError } from '@/components/ui/ErrorMessage';
const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard';
export default function BeneficiaryDashboardScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const { currentBeneficiary, setCurrentBeneficiary } = useBeneficiary();
const webViewRef = useRef<WebView>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [canGoBack, setCanGoBack] = useState(false);
const beneficiaryName = currentBeneficiary?.name || 'Dashboard';
const handleRefresh = () => {
setError(null);
setIsLoading(true);
webViewRef.current?.reload();
};
const handleWebViewBack = () => {
if (canGoBack) {
webViewRef.current?.goBack();
}
};
const handleNavigationStateChange = (navState: any) => {
setCanGoBack(navState.canGoBack);
};
const handleError = () => {
setError('Failed to load dashboard. Please check your internet connection.');
setIsLoading(false);
};
const handleGoBack = () => {
router.back();
};
if (error) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={handleGoBack}>
<Ionicons name="arrow-back" size={24} color={AppColors.textPrimary} />
</TouchableOpacity>
<Text style={styles.headerTitle}>{beneficiaryName}</Text>
<View style={styles.placeholder} />
</View>
<FullScreenError message={error} onRetry={handleRefresh} />
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container} edges={['top']}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={handleGoBack}>
<Ionicons name="arrow-back" size={24} color={AppColors.textPrimary} />
</TouchableOpacity>
<View style={styles.headerCenter}>
{currentBeneficiary && (
<View style={styles.avatarSmall}>
<Text style={styles.avatarText}>
{currentBeneficiary.name.charAt(0).toUpperCase()}
</Text>
</View>
)}
<View>
<Text style={styles.headerTitle}>{beneficiaryName}</Text>
{currentBeneficiary?.relationship && (
<Text style={styles.headerSubtitle}>{currentBeneficiary.relationship}</Text>
)}
</View>
</View>
<View style={styles.headerActions}>
{canGoBack && (
<TouchableOpacity style={styles.actionButton} onPress={handleWebViewBack}>
<Ionicons name="chevron-back" size={22} color={AppColors.primary} />
</TouchableOpacity>
)}
<TouchableOpacity style={styles.actionButton} onPress={handleRefresh}>
<Ionicons name="refresh" size={22} color={AppColors.primary} />
</TouchableOpacity>
</View>
</View>
{/* WebView */}
<View style={styles.webViewContainer}>
<WebView
ref={webViewRef}
source={{ uri: DASHBOARD_URL }}
style={styles.webView}
onLoadStart={() => setIsLoading(true)}
onLoadEnd={() => setIsLoading(false)}
onError={handleError}
onHttpError={handleError}
onNavigationStateChange={handleNavigationStateChange}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
scalesPageToFit={true}
allowsBackForwardNavigationGestures={true}
renderLoading={() => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={AppColors.primary} />
<Text style={styles.loadingText}>Loading dashboard...</Text>
</View>
)}
/>
{isLoading && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color={AppColors.primary} />
</View>
)}
</View>
{/* Bottom Quick Actions */}
<View style={styles.bottomBar}>
<TouchableOpacity
style={styles.quickAction}
onPress={() => {
if (currentBeneficiary) {
setCurrentBeneficiary(currentBeneficiary);
}
router.push('/(tabs)/chat');
}}
>
<Ionicons name="chatbubble-ellipses" size={24} color={AppColors.primary} />
<Text style={styles.quickActionText}>Ask Julia</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quickAction}
onPress={() => router.push(`/beneficiaries/${id}`)}
>
<Ionicons name="person" size={24} color={AppColors.primary} />
<Text style={styles.quickActionText}>Details</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.background,
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: Spacing.md,
paddingVertical: Spacing.sm,
backgroundColor: AppColors.background,
borderBottomWidth: 1,
borderBottomColor: AppColors.border,
},
backButton: {
padding: Spacing.xs,
},
headerCenter: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginLeft: Spacing.sm,
},
avatarSmall: {
width: 36,
height: 36,
borderRadius: BorderRadius.full,
backgroundColor: AppColors.primaryLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: Spacing.sm,
},
avatarText: {
fontSize: FontSizes.base,
fontWeight: '600',
color: AppColors.white,
},
headerTitle: {
fontSize: FontSizes.lg,
fontWeight: '700',
color: AppColors.textPrimary,
},
headerSubtitle: {
fontSize: FontSizes.xs,
color: AppColors.textSecondary,
},
headerActions: {
flexDirection: 'row',
alignItems: 'center',
},
actionButton: {
padding: Spacing.xs,
marginLeft: Spacing.xs,
},
placeholder: {
width: 32,
},
webViewContainer: {
flex: 1,
},
webView: {
flex: 1,
},
loadingContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: AppColors.background,
},
loadingOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255,255,255,0.8)',
},
loadingText: {
marginTop: Spacing.md,
fontSize: FontSizes.base,
color: AppColors.textSecondary,
},
bottomBar: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingVertical: Spacing.sm,
paddingHorizontal: Spacing.lg,
backgroundColor: AppColors.background,
borderTopWidth: 1,
borderTopColor: AppColors.border,
},
quickAction: {
alignItems: 'center',
padding: Spacing.sm,
},
quickActionText: {
fontSize: FontSizes.xs,
color: AppColors.primary,
marginTop: Spacing.xs,
},
});