import NumberKeyboard from '@/components/NumberKeyboard'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { WeightRecordCard } from '@/components/weight/WeightRecordCard'; import { Colors } from '@/constants/Colors'; import { getTabBarBottomPadding } from '@/constants/TabBar'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { deleteWeightRecord, fetchWeightHistory, updateUserProfile, updateWeightRecord, WeightHistoryItem } from '@/store/userSlice'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import { LinearGradient } from 'expo-linear-gradient'; import { router } from 'expo-router'; import React, { useCallback, useEffect, useState } from 'react'; import { Alert, Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; export default function WeightRecordsPage() { const safeAreaTop = useSafeAreaTop() const dispatch = useAppDispatch(); const userProfile = useAppSelector((s) => s.user.profile); const weightHistory = useAppSelector((s) => s.user.weightHistory); const [showWeightPicker, setShowWeightPicker] = useState(false); const [pickerType, setPickerType] = useState<'current' | 'initial' | 'target' | 'edit'>('current'); const [inputWeight, setInputWeight] = useState(''); const [editingRecord, setEditingRecord] = useState(null); const colorScheme = useColorScheme(); const themeColors = Colors[colorScheme ?? 'light']; console.log('userProfile:', userProfile); const loadWeightHistory = useCallback(async () => { try { await dispatch(fetchWeightHistory() as any); } catch (error) { console.error('加载体重历史失败:', error); } }, [dispatch]); useEffect(() => { loadWeightHistory(); }, [loadWeightHistory]); const handleGoBack = () => { router.back(); }; const initializeInput = (weight: number) => { setInputWeight(weight.toString()); }; const handleAddWeight = () => { setPickerType('current'); const weight = userProfile?.weight ? parseFloat(userProfile.weight) : 70.0; initializeInput(weight); setShowWeightPicker(true); }; const handleEditInitialWeight = () => { setPickerType('initial'); const initialWeight = userProfile?.initialWeight || userProfile?.weight || '70.0'; initializeInput(parseFloat(initialWeight)); setShowWeightPicker(true); }; const handleEditTargetWeight = () => { setPickerType('target'); const targetWeight = userProfile?.targetWeight || '60.0'; initializeInput(parseFloat(targetWeight)); setShowWeightPicker(true); }; const handleEditWeightRecord = (record: WeightHistoryItem) => { setPickerType('edit'); setEditingRecord(record); initializeInput(parseFloat(record.weight)); setShowWeightPicker(true); }; const handleDeleteWeightRecord = async (id: string) => { try { await dispatch(deleteWeightRecord(id) as any); await loadWeightHistory(); } catch (error) { console.error('删除体重记录失败:', error); Alert.alert('错误', '删除体重记录失败,请重试'); } }; const handleWeightSave = async () => { const weight = parseFloat(inputWeight); if (isNaN(weight) || weight <= 0 || weight > 500) { alert('请输入有效的体重值(0-500kg)'); return; } try { if (pickerType === 'current') { // Update current weight in profile and add weight record await dispatch(updateUserProfile({ weight: weight }) as any); } else if (pickerType === 'initial') { // Update initial weight in profile console.log('更新初始体重'); await dispatch(updateUserProfile({ initialWeight: weight }) as any); } else if (pickerType === 'target') { // Update target weight in profile await dispatch(updateUserProfile({ targetWeight: weight }) as any); } else if (pickerType === 'edit' && editingRecord) { await dispatch(updateWeightRecord({ id: editingRecord.id, weight }) as any); } setShowWeightPicker(false); setInputWeight(''); setEditingRecord(null); await loadWeightHistory(); } catch (error) { console.error('保存体重失败:', error); Alert.alert('错误', '保存体重失败,请重试'); } }; const handleNumberPress = (number: string) => { setInputWeight(prev => { // 防止输入多个0开头 if (prev === '0' && number === '0') return prev; // 如果当前是0,输入非0数字时替换 if (prev === '0' && number !== '0') return number; return prev + number; }); }; const handleDeletePress = () => { setInputWeight(prev => prev.slice(0, -1)); }; const handleDecimalPress = () => { setInputWeight(prev => { if (prev.includes('.')) return prev; // 如果没有输入任何数字,自动添加0 if (!prev) return '0.'; return prev + '.'; }); }; // Process weight history data const sortedHistory = [...weightHistory] .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); // Group by month const groupedHistory = sortedHistory.reduce((acc, item) => { const monthKey = dayjs(item.createdAt).format('YYYY年MM月'); if (!acc[monthKey]) { acc[monthKey] = []; } acc[monthKey].push(item); return acc; }, {} as Record); // Calculate statistics const currentWeight = userProfile?.weight ? parseFloat(userProfile.weight) : 0; const initialWeight = userProfile?.initialWeight ? parseFloat(userProfile.initialWeight) : (sortedHistory.length > 0 ? parseFloat(sortedHistory[sortedHistory.length - 1].weight) : currentWeight); const targetWeight = userProfile?.targetWeight ? parseFloat(userProfile.targetWeight) : 60.0; const totalWeightLoss = initialWeight - currentWeight; return ( {/* 背景渐变 */} } /> {/* Weight Statistics */} {totalWeightLoss.toFixed(1)}kg 累计减重 {currentWeight.toFixed(1)}kg 当前体重 {initialWeight.toFixed(1)}kg 初始体重 {targetWeight.toFixed(1)}kg 目标体重 {/* Monthly Records */} {Object.keys(groupedHistory).length > 0 ? ( Object.entries(groupedHistory).map(([month, records]) => ( {/* Month Header Card */} {/* {dayjs(month, 'YYYY年MM月').format('MM')} {dayjs(month, 'YYYY年MM月').format('YYYY年')} 累计减重:{totalWeightLoss.toFixed(1)}kg 日均减重:{avgWeightLoss.toFixed(1)}kg */} {/* Individual Record Cards */} {records.map((record, recordIndex) => { // Calculate weight change from previous record const prevRecord = recordIndex < records.length - 1 ? records[recordIndex + 1] : null; const weightChange = prevRecord ? parseFloat(record.weight) - parseFloat(prevRecord.weight) : 0; return ( ); })} )) ) : ( 暂无体重记录 点击右上角添加按钮开始记录 )} {/* Weight Input Modal */} setShowWeightPicker(false)} > setShowWeightPicker(false)} /> {/* Header */} setShowWeightPicker(false)}> {pickerType === 'current' && '记录体重'} {pickerType === 'initial' && '编辑初始体重'} {pickerType === 'target' && '编辑目标体重'} {pickerType === 'edit' && '编辑体重记录'} {/* Weight Display Section */} {inputWeight || '输入体重'} kg {/* Weight Range Hint */} 请输入 0-500 之间的数值,支持小数 {/* Quick Selection */} 快速选择 {[50, 60, 70, 80, 90].map((weight) => ( setInputWeight(weight.toString())} > {weight}kg ))} {/* Custom Number Keyboard */} {/* Save Button */} 确定 ); } const styles = StyleSheet.create({ container: { flex: 1, }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingTop: 60, paddingHorizontal: 20, paddingBottom: 10, }, backButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.9)', alignItems: 'center', justifyContent: 'center', }, addButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.9)', alignItems: 'center', justifyContent: 'center', }, content: { flex: 1, paddingHorizontal: 20, }, contentContainer: { flexGrow: 1, }, statsContainer: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 20, marginLeft: 20, marginRight: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 3, }, statsRow: { flexDirection: 'row', justifyContent: 'space-between', }, statItem: { flex: 1, flexDirection: 'column', alignItems: 'center', }, statValue: { fontSize: 16, fontWeight: '800', color: '#192126', marginBottom: 4, }, statLabelContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, statLabel: { fontSize: 12, color: '#687076', marginRight: 4, }, editIcon: { padding: 2, borderRadius: 8, backgroundColor: 'rgba(255, 149, 0, 0.1)', }, monthContainer: { marginBottom: 20, }, monthHeaderCard: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 12, elevation: 3, }, monthTitleRow: { flexDirection: 'row', alignItems: 'baseline', marginBottom: 12, }, monthNumber: { fontSize: 48, fontWeight: '800', color: '#192126', lineHeight: 48, }, monthText: { fontSize: 16, fontWeight: '600', color: '#192126', marginLeft: 4, marginRight: 8, }, yearText: { fontSize: 16, fontWeight: '500', color: '#687076', flex: 1, }, expandIcon: { padding: 4, }, monthStatsText: { fontSize: 14, color: '#687076', lineHeight: 20, }, statsBold: { fontWeight: '700', color: '#192126', }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', minHeight: 300, }, emptyContent: { alignItems: 'center', }, emptyText: { fontSize: 16, fontWeight: '700', color: '#192126', marginBottom: 8, }, emptySubtext: { fontSize: 14, color: '#687076', }, // Modal Styles modalContainer: { flex: 1, }, modalBackdrop: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.35)', }, modalSheet: { position: 'absolute', left: 0, right: 0, bottom: 0, borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '85%', minHeight: 500, }, modalHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 10, }, modalTitle: { fontSize: 18, fontWeight: '600', }, modalContent: { flex: 1, paddingHorizontal: 20, }, inputSection: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, marginBottom: 12, }, weightInputContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, weightIcon: { width: 32, height: 32, borderRadius: 16, backgroundColor: '#F0F9FF', alignItems: 'center', justifyContent: 'center', marginRight: 12, }, inputWrapper: { flex: 1, flexDirection: 'row', alignItems: 'center', borderBottomWidth: 2, borderBottomColor: '#E5E7EB', paddingBottom: 6, }, weightDisplay: { flex: 1, fontSize: 24, fontWeight: '600', textAlign: 'center', paddingVertical: 4, }, unitLabel: { fontSize: 18, fontWeight: '500', marginLeft: 8, }, hintText: { fontSize: 12, textAlign: 'center', marginTop: 4, }, quickSelectionSection: { paddingHorizontal: 4, marginBottom: 20, }, quickSelectionTitle: { fontSize: 16, fontWeight: '600', marginBottom: 12, textAlign: 'center', }, quickButtons: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', gap: 8, }, quickButton: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 18, backgroundColor: '#F3F4F6', borderWidth: 1, borderColor: '#E5E7EB', minWidth: 60, alignItems: 'center', }, quickButtonSelected: { backgroundColor: '#6366F1', borderColor: '#6366F1', }, quickButtonText: { fontSize: 13, fontWeight: '500', color: '#6B7280', }, quickButtonTextSelected: { color: '#FFFFFF', fontWeight: '600', }, modalFooter: { paddingHorizontal: 20, paddingTop: 16, paddingBottom: 25, }, saveButton: { backgroundColor: '#6366F1', borderRadius: 16, paddingVertical: 16, alignItems: 'center', }, saveButtonText: { color: '#FFFFFF', fontSize: 18, fontWeight: '600', }, });