import React, { useState, useCallback, useEffect } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, Alert, ActivityIndicator, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { theme } from '../theme'; import { Event } from '../types'; import { eventApi } from '../api'; import EventModal from '../components/EventModal'; import EventCard from '../components/EventCard'; import { shouldShowEventOnDate, safeParseDate } from '../utils/recurrence'; const showError = (title: string, error: any) => { const message = error instanceof Error ? error.message : '加载事件失败'; Alert.alert(title, message); }; export default function TodayScreen() { const navigation = useNavigation(); const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [modalVisible, setModalVisible] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); const [saving, setSaving] = useState(false); const loadEvents = useCallback(async () => { try { setLoading(true); const today = new Date(); const allEvents = await eventApi.getEvents(today.getFullYear(), today.getMonth() + 1); const todayEvents = allEvents.filter(event => { if (event.deleted) return false; return shouldShowEventOnDate(event, today); }).sort((a, b) => safeParseDate(a.start).getTime() - safeParseDate(b.start).getTime()); setEvents(todayEvents); } catch (error) { showError('加载失败', error); } finally { setLoading(false); } }, []); useFocusEffect( useCallback(() => { loadEvents(); }, [loadEvents]) ); const handleAddEvent = () => { setSelectedEvent(null); setModalVisible(true); }; const handleEditEvent = (event: Event) => { setSelectedEvent(event); setModalVisible(true); }; const handleToggleComplete = async (event: Event) => { try { setSaving(true); await eventApi.updateEvent(event.id, { completed: !event.completed }); await loadEvents(); } catch (error) { showError('更新失败', error); } finally { setSaving(false); } }; const handleDeleteEvent = async (event: Event) => { Alert.alert( '确认删除', '确定要删除这个事件吗?', [ { text: '取消', style: 'cancel' }, { text: '删除', style: 'destructive', onPress: async () => { try { await eventApi.deleteEvent(event.id); await loadEvents(); } catch (error) { showError('删除失败', error); } }, }, ] ); }; const handleSaveEvent = async (eventData: Partial) => { try { setSaving(true); if (selectedEvent) { await eventApi.updateEvent(selectedEvent.id, eventData); } else { await eventApi.createEvent({ ...eventData, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), deleted: false, } as Event); } setModalVisible(false); await loadEvents(); } catch (error) { showError('保存失败', error); } finally { setSaving(false); } }; const getGreeting = () => { const hour = new Date().getHours(); if (hour < 12) return '早上好'; if (hour < 18) return '下午好'; return '晚上好'; }; const pendingEvents = events.filter(e => !e.completed); const completedEvents = events.filter(e => e.completed); return ( {getGreeting()} 👋 {new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', })} {pendingEvents.length} 待完成 {completedEvents.length} 已完成 {events.length} 共计 待完成 {loading ? ( ) : ( item.id} renderItem={({ item }) => ( )} contentContainerStyle={styles.list} showsVerticalScrollIndicator={false} ListEmptyComponent={ 今天没有待办事项 🎉 } /> )} setModalVisible(false)} onSave={handleSaveEvent} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.colors.background, padding: theme.spacing.md, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: theme.spacing.lg, }, headerLeft: { flex: 1, }, greeting: { ...theme.typography.h1, color: theme.colors.text, marginBottom: theme.spacing.xs, }, date: { ...theme.typography.bodySmall, color: theme.colors.textSecondary, }, addButton: { backgroundColor: theme.colors.primary, width: 56, height: 56, borderRadius: 28, justifyContent: 'center', alignItems: 'center', }, statsContainer: { flexDirection: 'row', gap: theme.spacing.sm, marginBottom: theme.spacing.lg, }, statCard: { flex: 1, backgroundColor: theme.colors.surface, padding: theme.spacing.md, borderRadius: theme.borderRadius.lg, alignItems: 'center', }, statNumber: { ...theme.typography.h2, color: theme.colors.primary, }, statLabel: { ...theme.typography.bodySmall, color: theme.colors.textSecondary, }, sectionTitle: { ...theme.typography.h3, color: theme.colors.text, marginBottom: theme.spacing.sm, }, list: { paddingBottom: 20, }, loader: { flex: 1, justifyContent: 'center', }, emptyContainer: { padding: theme.spacing.xl, alignItems: 'center', }, emptyText: { ...theme.typography.body, color: theme.colors.textSecondary, textAlign: 'center', }, });