Files
digital-pilates/components/TodoCard.tsx
richarjiang 136c800084 feat: 新增目标管理功能及相关组件
- 创建目标管理演示页面,展示高保真的目标管理界面
- 实现待办事项卡片的横向滑动展示,支持时间筛选功能
- 新增时间轴组件,展示选中日期的具体任务
- 更新底部导航,添加目标管理和演示页面的路由
- 优化个人页面菜单项,提供目标管理的快速访问
- 编写目标管理功能实现文档,详细描述功能和组件架构
2025-08-22 09:31:35 +08:00

249 lines
6.1 KiB
TypeScript

import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { Ionicons } from '@expo/vector-icons';
import dayjs from 'dayjs';
import React from 'react';
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
export interface TodoItem {
id: string;
title: string;
description?: string;
time: string;
category: 'workout' | 'finance' | 'personal' | 'work' | 'health';
isCompleted?: boolean;
priority?: 'high' | 'medium' | 'low';
}
interface TodoCardProps {
item: TodoItem;
onPress?: (item: TodoItem) => void;
onToggleComplete?: (item: TodoItem) => void;
}
const { width: screenWidth } = Dimensions.get('window');
const CARD_WIDTH = (screenWidth - 60) * 0.65; // 显示1.5张卡片
const getCategoryIcon = (category: TodoItem['category']) => {
switch (category) {
case 'workout':
return 'fitness-outline';
case 'finance':
return 'card-outline';
case 'personal':
return 'person-outline';
case 'work':
return 'briefcase-outline';
case 'health':
return 'heart-outline';
default:
return 'checkmark-circle-outline';
}
};
const getCategoryColor = (category: TodoItem['category']) => {
switch (category) {
case 'workout':
return '#FF6B6B';
case 'finance':
return '#4ECDC4';
case 'personal':
return '#45B7D1';
case 'work':
return '#96CEB4';
case 'health':
return '#FFEAA7';
default:
return '#DDA0DD';
}
};
const getPriorityColor = (priority: TodoItem['priority']) => {
switch (priority) {
case 'high':
return '#FF4757';
case 'medium':
return '#FFA502';
case 'low':
return '#2ED573';
default:
return '#747D8C';
}
};
export function TodoCard({ item, onPress, onToggleComplete }: TodoCardProps) {
const theme = useColorScheme() ?? 'light';
const colorTokens = Colors[theme];
const categoryColor = getCategoryColor(item.category);
const categoryIcon = getCategoryIcon(item.category);
const priorityColor = getPriorityColor(item.priority);
const timeFormatted = dayjs(item.time).format('HH:mm');
const isToday = dayjs(item.time).isSame(dayjs(), 'day');
return (
<TouchableOpacity
style={[styles.container, { backgroundColor: colorTokens.card }]}
onPress={() => onPress?.(item)}
activeOpacity={0.8}
>
{/* 顶部标签和优先级 */}
<View style={styles.header}>
<View style={[styles.categoryBadge, { backgroundColor: categoryColor }]}>
<Ionicons name={categoryIcon as any} size={12} color="#fff" />
<Text style={styles.categoryText}>{item.category}</Text>
</View>
{item.priority && (
<View style={[styles.priorityDot, { backgroundColor: priorityColor }]} />
)}
</View>
{/* 主要内容 */}
<View style={styles.content}>
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
{item.title}
</Text>
{item.description && (
<Text style={[styles.description, { color: colorTokens.textSecondary }]} numberOfLines={2}>
{item.description}
</Text>
)}
</View>
{/* 底部时间和完成状态 */}
<View style={styles.footer}>
<View style={styles.timeContainer}>
<Ionicons
name={isToday ? "time" : "calendar-outline"}
size={14}
color={colorTokens.textMuted}
/>
<Text style={[styles.timeText, { color: colorTokens.textMuted }]}>
{timeFormatted}
</Text>
</View>
<TouchableOpacity
style={[
styles.completeButton,
{
backgroundColor: item.isCompleted ? colorTokens.primary : 'transparent',
borderColor: item.isCompleted ? colorTokens.primary : colorTokens.border
}
]}
onPress={() => onToggleComplete?.(item)}
>
{item.isCompleted && (
<Ionicons name="checkmark" size={16} color={colorTokens.onPrimary} />
)}
</TouchableOpacity>
</View>
{/* 完成状态遮罩 */}
{item.isCompleted && (
<View style={styles.completedOverlay}>
<Ionicons name="checkmark-circle" size={24} color={colorTokens.primary} />
</View>
)}
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: {
width: CARD_WIDTH,
height: 140,
marginHorizontal: 8,
borderRadius: 20,
padding: 16,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 6,
position: 'relative',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
categoryBadge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
backgroundColor: '#4ECDC4',
},
categoryText: {
fontSize: 10,
fontWeight: '600',
color: '#fff',
marginLeft: 4,
textTransform: 'capitalize',
},
priorityDot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#FF4757',
},
content: {
flex: 1,
justifyContent: 'center',
},
title: {
fontSize: 16,
fontWeight: '700',
lineHeight: 20,
marginBottom: 4,
},
description: {
fontSize: 12,
lineHeight: 16,
opacity: 0.7,
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 12,
},
timeContainer: {
flexDirection: 'row',
alignItems: 'center',
},
timeText: {
fontSize: 12,
fontWeight: '500',
marginLeft: 4,
},
completeButton: {
width: 24,
height: 24,
borderRadius: 12,
borderWidth: 1.5,
justifyContent: 'center',
alignItems: 'center',
},
completedOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
},
});