fitaiProto/apps/mobile/src/components/GoalCarousel.tsx
echo febcdc111e redesign completed
alot of work ahead
2025-11-26 02:31:46 +01:00

224 lines
7.8 KiB
TypeScript

import React, { useRef } from 'react';
import { View, Text, StyleSheet, Dimensions, ScrollView, TouchableOpacity, Animated } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons } from '@expo/vector-icons';
import { theme } from '../styles/theme';
import { CircularProgress } from './CircularProgress';
import type { FitnessGoal } from '../services/fitnessGoals';
const { width } = Dimensions.get('window');
const CARD_WIDTH = width * 0.8;
const SPACING = 20;
interface GoalCarouselProps {
goals: FitnessGoal[];
onGoalPress: (goal: FitnessGoal) => void;
}
export function GoalCarousel({ goals, onGoalPress }: GoalCarouselProps) {
const scrollX = useRef(new Animated.Value(0)).current;
if (goals.length === 0) {
return (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>No active goals</Text>
</View>
);
}
return (
<View>
<Animated.ScrollView
horizontal
showsHorizontalScrollIndicator={false}
snapToInterval={CARD_WIDTH + SPACING}
decelerationRate="fast"
contentContainerStyle={styles.scrollContent}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
{goals.map((goal, index) => {
const inputRange = [
(index - 1) * (CARD_WIDTH + SPACING),
index * (CARD_WIDTH + SPACING),
(index + 1) * (CARD_WIDTH + SPACING),
];
const scale = scrollX.interpolate({
inputRange,
outputRange: [0.9, 1, 0.9],
extrapolate: 'clamp',
});
const opacity = scrollX.interpolate({
inputRange,
outputRange: [0.7, 1, 0.7],
extrapolate: 'clamp',
});
return (
<TouchableOpacity
key={goal.id}
activeOpacity={0.9}
onPress={() => onGoalPress(goal)}
>
<Animated.View
style={[
styles.cardContainer,
{ transform: [{ scale }], opacity },
]}
>
<LinearGradient
colors={['#ffffff', '#f9fafb']}
style={[styles.card, theme.shadows.medium]}
>
<View style={styles.cardHeader}>
<View style={styles.iconContainer}>
<LinearGradient
colors={theme.gradients.primary}
style={styles.iconGradient}
>
<Ionicons name="trophy" size={24} color="#fff" />
</LinearGradient>
</View>
<View style={styles.priorityBadge}>
<Text style={styles.priorityText}>{goal.priority.toUpperCase()}</Text>
</View>
</View>
<Text style={styles.title} numberOfLines={1}>{goal.title}</Text>
<Text style={styles.description} numberOfLines={2}>{goal.description}</Text>
<View style={styles.progressContainer}>
<CircularProgress
progress={goal.progress || 0}
size={80}
strokeWidth={8}
color={theme.colors.primary}
/>
<View style={styles.progressDetails}>
<Text style={styles.progressValue}>
{goal.currentValue} / {goal.targetValue}
</Text>
<Text style={styles.progressUnit}>{goal.unit}</Text>
</View>
</View>
<View style={styles.footer}>
<Text style={styles.date}>
Target: {goal.targetDate ? new Date(goal.targetDate).toLocaleDateString() : 'No date'}
</Text>
</View>
</LinearGradient>
</Animated.View>
</TouchableOpacity>
);
})}
</Animated.ScrollView>
</View>
);
}
const styles = StyleSheet.create({
scrollContent: {
paddingHorizontal: (width - CARD_WIDTH) / 2,
paddingVertical: 20,
},
emptyContainer: {
height: 200,
justifyContent: 'center',
alignItems: 'center',
},
emptyText: {
color: theme.colors.gray500,
fontSize: 16,
},
cardContainer: {
width: CARD_WIDTH,
marginRight: SPACING,
},
card: {
borderRadius: 24,
padding: 24,
height: 320,
justifyContent: 'space-between',
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.05)',
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 16,
},
iconContainer: {
shadowColor: theme.colors.primary,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 4,
},
iconGradient: {
width: 48,
height: 48,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
},
priorityBadge: {
backgroundColor: 'rgba(59, 130, 246, 0.1)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 12,
},
priorityText: {
color: theme.colors.primary,
fontSize: 12,
fontWeight: '700',
},
title: {
fontSize: 24,
fontWeight: '800',
color: theme.colors.gray900,
marginBottom: 8,
},
description: {
fontSize: 14,
color: theme.colors.gray500,
marginBottom: 24,
lineHeight: 20,
},
progressContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 24,
},
progressDetails: {
alignItems: 'flex-end',
},
progressValue: {
fontSize: 24,
fontWeight: '700',
color: theme.colors.gray900,
},
progressUnit: {
fontSize: 14,
color: theme.colors.gray500,
fontWeight: '500',
},
footer: {
borderTopWidth: 1,
borderTopColor: theme.colors.gray100,
paddingTop: 16,
},
date: {
fontSize: 12,
color: theme.colors.gray400,
fontWeight: '500',
},
});