WellNuo/components/MockDashboard.tsx
Sergei d453126c89 feat: Room location picker + robster credentials
- Backend: Update Legacy API credentials to robster/rob2
- Frontend: ROOM_LOCATIONS with icons and legacyCode mapping
- Device Settings: Modal picker for room selection
- api.ts: Bidirectional conversion (code ↔ name)
- Various UI/UX improvements across screens

PRD-DEPLOYMENT.md completed (Score: 9/10)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 15:22:40 -08:00

432 lines
12 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
RefreshControl,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { AppColors, Spacing, BorderRadius, FontSizes, FontWeights } from '@/constants/theme';
interface MockDashboardProps {
beneficiaryName: string;
}
// Mock data generator - creates realistic-looking data
const generateMockData = () => {
const now = new Date();
const hours = now.getHours();
// Generate activities based on time of day
const activities = [
{
id: 1,
type: 'walking',
icon: 'walk' as const,
title: 'Movement',
value: `${Math.floor(Math.random() * 2000 + 500)} steps`,
time: `${Math.floor(Math.random() * 30 + 10)} min ago`,
color: AppColors.success,
},
{
id: 2,
type: 'hydration',
icon: 'water' as const,
title: 'Hydration',
value: `${Math.floor(Math.random() * 4 + 2)} glasses`,
time: `${Math.floor(Math.random() * 60 + 20)} min ago`,
color: '#3B82F6',
},
{
id: 3,
type: 'bathroom',
icon: 'home' as const,
title: 'Bathroom',
value: `${Math.floor(Math.random() * 3 + 2)} visits`,
time: `${Math.floor(Math.random() * 120 + 30)} min ago`,
color: '#8B5CF6',
},
{
id: 4,
type: 'sleep',
icon: 'moon' as const,
title: 'Sleep',
value: hours < 10 ? `${(Math.random() * 2 + 6).toFixed(1)} hrs` : 'N/A',
time: hours < 10 ? 'Last night' : 'Tracking tonight',
color: '#6366F1',
},
{
id: 5,
type: 'meals',
icon: 'restaurant' as const,
title: 'Kitchen Activity',
value: `${Math.floor(Math.random() * 2 + 1)} visits`,
time: `${Math.floor(Math.random() * 180 + 60)} min ago`,
color: '#F59E0B',
},
];
return {
wellnessScore: Math.floor(Math.random() * 20 + 75), // 75-95
temperature: {
living: (Math.random() * 4 + 68).toFixed(1),
bedroom: (Math.random() * 4 + 66).toFixed(1),
},
lastActivity: {
location: ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][Math.floor(Math.random() * 4)],
time: `${Math.floor(Math.random() * 15 + 1)} min ago`,
},
activities,
alerts: Math.random() > 0.8 ? 1 : 0, // 20% chance of alert
};
};
export default function MockDashboard({ beneficiaryName }: MockDashboardProps) {
const [data, setData] = useState(generateMockData());
const [refreshing, setRefreshing] = useState(false);
const onRefresh = () => {
setRefreshing(true);
setTimeout(() => {
setData(generateMockData());
setRefreshing(false);
}, 1000);
};
// Refresh data every 5 minutes
useEffect(() => {
const interval = setInterval(() => {
setData(generateMockData());
}, 5 * 60 * 1000);
return () => clearInterval(interval);
}, []);
const getWellnessColor = (score: number) => {
if (score >= 80) return AppColors.success;
if (score >= 60) return AppColors.warning;
return AppColors.error;
};
return (
<ScrollView
style={styles.container}
contentContainerStyle={styles.content}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{/* Mock Data Banner */}
<View style={styles.mockBanner}>
<Ionicons name="flask" size={16} color={AppColors.warning} />
<Text style={styles.mockBannerText}>
Demo Data - Connect sensors for real activity
</Text>
</View>
{/* Wellness Score Card */}
<View style={styles.card}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>Wellness Score</Text>
<View style={[styles.badge, { backgroundColor: getWellnessColor(data.wellnessScore) + '20' }]}>
<Text style={[styles.badgeText, { color: getWellnessColor(data.wellnessScore) }]}>
{data.wellnessScore >= 80 ? 'Good' : data.wellnessScore >= 60 ? 'Fair' : 'Low'}
</Text>
</View>
</View>
<View style={styles.scoreContainer}>
<Text style={[styles.scoreValue, { color: getWellnessColor(data.wellnessScore) }]}>
{data.wellnessScore}%
</Text>
<View style={styles.scoreBar}>
<View
style={[
styles.scoreProgress,
{
width: `${data.wellnessScore}%`,
backgroundColor: getWellnessColor(data.wellnessScore)
}
]}
/>
</View>
</View>
</View>
{/* Current Status */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Current Status</Text>
<View style={styles.statusRow}>
<View style={styles.statusItem}>
<Ionicons name="location" size={24} color={AppColors.primary} />
<View style={styles.statusTextContainer}>
<Text style={styles.statusLabel}>Location</Text>
<Text style={styles.statusValue}>{data.lastActivity.location}</Text>
</View>
</View>
<View style={styles.statusItem}>
<Ionicons name="time" size={24} color={AppColors.primary} />
<View style={styles.statusTextContainer}>
<Text style={styles.statusLabel}>Last Activity</Text>
<Text style={styles.statusValue}>{data.lastActivity.time}</Text>
</View>
</View>
</View>
</View>
{/* Temperature */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Home Temperature</Text>
<View style={styles.tempRow}>
<View style={styles.tempItem}>
<Ionicons name="sunny" size={28} color="#F59E0B" />
<Text style={styles.tempValue}>{data.temperature.living}°F</Text>
<Text style={styles.tempLabel}>Living Room</Text>
</View>
<View style={styles.tempDivider} />
<View style={styles.tempItem}>
<Ionicons name="moon" size={28} color="#6366F1" />
<Text style={styles.tempValue}>{data.temperature.bedroom}°F</Text>
<Text style={styles.tempLabel}>Bedroom</Text>
</View>
</View>
</View>
{/* Activities */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Today's Activities</Text>
<View style={styles.activitiesGrid}>
{data.activities.map((activity) => (
<View key={activity.id} style={styles.activityCard}>
<View style={[styles.activityIcon, { backgroundColor: activity.color + '20' }]}>
<Ionicons name={activity.icon} size={22} color={activity.color} />
</View>
<Text style={styles.activityTitle}>{activity.title}</Text>
<Text style={styles.activityValue}>{activity.value}</Text>
<Text style={styles.activityTime}>{activity.time}</Text>
</View>
))}
</View>
</View>
{/* Alerts Section */}
{data.alerts > 0 && (
<View style={[styles.card, styles.alertCard]}>
<View style={styles.alertHeader}>
<Ionicons name="alert-circle" size={24} color={AppColors.warning} />
<Text style={styles.alertTitle}>Attention Needed</Text>
</View>
<Text style={styles.alertText}>
Lower than usual activity detected. Consider checking in.
</Text>
</View>
)}
{/* Info Footer */}
<View style={styles.infoFooter}>
<Ionicons name="information-circle" size={18} color={AppColors.textMuted} />
<Text style={styles.infoText}>
This is demo data. Once sensors are installed and connected,
you'll see real activity data from your loved one's home.
</Text>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.background,
},
content: {
padding: Spacing.md,
paddingBottom: Spacing.xxl,
},
mockBanner: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: `${AppColors.warning}15`,
paddingVertical: Spacing.sm,
paddingHorizontal: Spacing.md,
borderRadius: BorderRadius.md,
marginBottom: Spacing.md,
gap: Spacing.xs,
},
mockBannerText: {
fontSize: FontSizes.sm,
color: AppColors.warning,
fontWeight: FontWeights.medium,
},
card: {
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.lg,
padding: Spacing.lg,
marginBottom: Spacing.md,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 3,
elevation: 1,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: Spacing.md,
},
cardTitle: {
fontSize: FontSizes.lg,
fontWeight: FontWeights.semibold,
color: AppColors.textPrimary,
marginBottom: Spacing.sm,
},
badge: {
paddingHorizontal: Spacing.sm,
paddingVertical: Spacing.xs,
borderRadius: BorderRadius.full,
},
badgeText: {
fontSize: FontSizes.xs,
fontWeight: FontWeights.semibold,
},
scoreContainer: {
alignItems: 'center',
},
scoreValue: {
fontSize: 48,
fontWeight: FontWeights.bold,
marginBottom: Spacing.sm,
},
scoreBar: {
width: '100%',
height: 8,
backgroundColor: AppColors.border,
borderRadius: BorderRadius.full,
overflow: 'hidden',
},
scoreProgress: {
height: '100%',
borderRadius: BorderRadius.full,
},
statusRow: {
flexDirection: 'row',
justifyContent: 'space-around',
marginTop: Spacing.sm,
},
statusItem: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.sm,
},
statusTextContainer: {
alignItems: 'flex-start',
},
statusLabel: {
fontSize: FontSizes.xs,
color: AppColors.textMuted,
},
statusValue: {
fontSize: FontSizes.base,
fontWeight: FontWeights.medium,
color: AppColors.textPrimary,
},
tempRow: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
marginTop: Spacing.sm,
},
tempItem: {
alignItems: 'center',
flex: 1,
},
tempDivider: {
width: 1,
height: 50,
backgroundColor: AppColors.border,
},
tempValue: {
fontSize: FontSizes.xl,
fontWeight: FontWeights.bold,
color: AppColors.textPrimary,
marginTop: Spacing.xs,
},
tempLabel: {
fontSize: FontSizes.xs,
color: AppColors.textMuted,
marginTop: Spacing.xs,
},
activitiesGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: Spacing.sm,
marginTop: Spacing.sm,
},
activityCard: {
width: '48%',
backgroundColor: AppColors.background,
borderRadius: BorderRadius.md,
padding: Spacing.md,
alignItems: 'center',
},
activityIcon: {
width: 44,
height: 44,
borderRadius: BorderRadius.full,
justifyContent: 'center',
alignItems: 'center',
marginBottom: Spacing.xs,
},
activityTitle: {
fontSize: FontSizes.sm,
fontWeight: FontWeights.medium,
color: AppColors.textPrimary,
marginBottom: Spacing.xs,
},
activityValue: {
fontSize: FontSizes.lg,
fontWeight: FontWeights.bold,
color: AppColors.textPrimary,
},
activityTime: {
fontSize: FontSizes.xs,
color: AppColors.textMuted,
marginTop: Spacing.xs,
},
alertCard: {
backgroundColor: `${AppColors.warning}10`,
borderWidth: 1,
borderColor: `${AppColors.warning}30`,
},
alertHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.sm,
marginBottom: Spacing.sm,
},
alertTitle: {
fontSize: FontSizes.base,
fontWeight: FontWeights.semibold,
color: AppColors.warning,
},
alertText: {
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
lineHeight: 20,
},
infoFooter: {
flexDirection: 'row',
alignItems: 'flex-start',
gap: Spacing.sm,
marginTop: Spacing.md,
paddingHorizontal: Spacing.sm,
},
infoText: {
flex: 1,
fontSize: FontSizes.xs,
color: AppColors.textMuted,
lineHeight: 18,
},
});