fitness goals ui fix

This commit is contained in:
echo 2026-03-11 08:47:14 +01:00
parent df08ff8950
commit b1439f059a
2 changed files with 257 additions and 275 deletions

View File

@ -256,7 +256,12 @@ export default function GoalsScreen() {
</TouchableOpacity>
{showAnalytics && (
<View style={styles.analyticsContent}>
<View
style={[
styles.analyticsContent,
{ borderTopColor: colors.border },
]}
>
{statistics.weeklyTrend.length > 0 && (
<WeeklyProgressChart
weeklyData={statistics.weeklyTrend}
@ -414,7 +419,6 @@ const styles = StyleSheet.create({
paddingTop: 16,
marginTop: 16,
borderTopWidth: 1,
borderTopColor: "rgba(0, 0, 0, 0.05)",
},
goalsList: {
gap: 12,

View File

@ -1,9 +1,12 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import type { FitnessGoal } from '../services/fitnessGoals';
import { theme } from '../styles/theme';
import React from "react";
import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import type { FitnessGoal } from "../services/fitnessGoals";
import { useTheme } from "../contexts/ThemeContext";
import { MinimalCard } from "./MinimalCard";
import { Badge } from "./Badge";
import { ProgressBar } from "./ProgressBar";
import { IconContainer } from "./IconContainer";
interface GoalProgressCardProps {
goal: FitnessGoal;
@ -12,220 +15,240 @@ interface GoalProgressCardProps {
onDelete?: () => void;
}
export function GoalProgressCard({ goal, onPress, onComplete, onDelete }: GoalProgressCardProps) {
const isCompleted = goal.status === 'completed';
const progress = goal.progress || 0;
export function GoalProgressCard({
goal,
onPress,
onComplete,
onDelete,
}: GoalProgressCardProps) {
const { colors, typography } = useTheme();
const isCompleted = goal.status === "completed";
const progress = (goal.progress || 0) / 100; // Convert to 0-1 scale
// Calculate days remaining
const daysRemaining = goal.targetDate
? Math.ceil((new Date(goal.targetDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24))
? Math.ceil(
(new Date(goal.targetDate).getTime() - Date.now()) /
(1000 * 60 * 60 * 24),
)
: null;
const getGoalTypeIcon = (type: string) => {
switch (type) {
case 'weight_target': return 'scale-outline';
case 'strength_milestone': return 'barbell-outline';
case 'endurance_target': return 'bicycle-outline';
case 'flexibility_goal': return 'body-outline';
case 'habit_building': return 'calendar-outline';
default: return 'flag-outline';
case "weight_target":
return "scale-outline";
case "strength_milestone":
return "barbell-outline";
case "endurance_target":
return "bicycle-outline";
case "flexibility_goal":
return "body-outline";
case "habit_building":
return "calendar-outline";
default:
return "flag-outline";
}
};
const getPriorityGradient = (priority: string): readonly [string, string] => {
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return theme.gradients.danger;
case 'medium': return theme.gradients.warning;
case 'low': return theme.gradients.success;
default: return theme.gradients.primary;
case "high":
return colors.danger;
case "medium":
return colors.warning;
case "low":
return colors.success;
default:
return colors.primary;
}
};
const handleDelete = () => {
Alert.alert(
'Delete Goal',
'Are you sure you want to delete this goal?',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Delete', style: 'destructive', onPress: onDelete },
]
);
Alert.alert("Delete Goal", "Are you sure you want to delete this goal?", [
{ text: "Cancel", style: "cancel" },
{ text: "Delete", style: "destructive", onPress: onDelete },
]);
};
return (
<TouchableOpacity
onPress={onPress}
activeOpacity={0.7}
>
<LinearGradient
colors={isCompleted
? ['rgba(16, 185, 129, 0.05)', 'rgba(5, 150, 105, 0.02)'] as const
: ['rgba(255, 255, 255, 1)', 'rgba(249, 250, 251, 1)'] as const
}
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
<MinimalCard
variant="bordered"
style={[
styles.card,
theme.shadows.medium,
isCompleted && styles.cardCompleted
isCompleted && {
backgroundColor: colors.overlayLight,
},
]}
>
{/* Priority Accent Bar */}
<LinearGradient
colors={getPriorityGradient(goal.priority)}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
style={styles.priorityAccent}
/>
{/* Header */}
<View style={styles.header}>
<View style={styles.titleRow}>
<LinearGradient
colors={isCompleted ? theme.gradients.success : getPriorityGradient(goal.priority)}
style={styles.iconContainer}
<IconContainer
variant="colored"
backgroundColor={
isCompleted ? colors.success : getPriorityColor(goal.priority)
}
>
<Ionicons
name={getGoalTypeIcon(goal.goalType) as any}
size={20}
color="#fff"
color={colors.white}
/>
</LinearGradient>
</IconContainer>
<View style={styles.titleContainer}>
<Text style={[styles.title, isCompleted && styles.titleCompleted]}>
<Text
style={[
typography.h3,
{ color: colors.textPrimary },
isCompleted && {
color: colors.textSecondary,
textDecorationLine: "line-through",
},
]}
>
{goal.title}
</Text>
{goal.description && (
<Text style={styles.description} numberOfLines={2}>
<Text
style={[
typography.caption,
{ color: colors.textTertiary, marginTop: 2 },
]}
numberOfLines={2}
>
{goal.description}
</Text>
)}
</View>
</View>
{/* Action Buttons */}
<View style={styles.actions}>
{!isCompleted && onComplete && (
<TouchableOpacity onPress={onComplete} style={styles.actionButton}>
<Ionicons name="checkmark-circle-outline" size={24} color={theme.colors.success} />
<TouchableOpacity
onPress={onComplete}
style={styles.actionButton}
>
<Ionicons
name="checkmark-circle-outline"
size={24}
color={colors.success}
/>
</TouchableOpacity>
)}
{onDelete && (
<TouchableOpacity onPress={handleDelete} style={styles.actionButton}>
<Ionicons name="trash-outline" size={22} color={theme.colors.danger} />
<TouchableOpacity
onPress={handleDelete}
style={styles.actionButton}
>
<Ionicons
name="trash-outline"
size={22}
color={colors.danger}
/>
</TouchableOpacity>
)}
</View>
</View>
{/* Progress Section */}
{goal.targetValue && (
<View style={styles.progressSection}>
<View style={styles.progressInfo}>
<Text style={styles.progressText}>
{goal.currentValue || 0} / {goal.targetValue} {goal.unit || ''}
<Text
style={[typography.caption, { color: colors.textSecondary }]}
>
{goal.currentValue || 0} / {goal.targetValue} {goal.unit || ""}
</Text>
<Text style={[styles.progressPercentage, isCompleted && { color: theme.colors.success }]}>
{progress.toFixed(0)}%
<Text
style={[
typography.bodyEmphasis,
{
color: isCompleted ? colors.success : colors.primary,
},
]}
>
{(progress * 100).toFixed(0)}%
</Text>
</View>
<View style={styles.progressBarContainer}>
<LinearGradient
colors={isCompleted ? theme.gradients.success : getPriorityGradient(goal.priority)}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={[
styles.progressBar,
{ width: `${Math.min(progress, 100)}%` }
]}
<ProgressBar
progress={progress}
color={
isCompleted ? colors.success : getPriorityColor(goal.priority)
}
/>
</View>
</View>
)}
{/* Footer */}
<View style={styles.footer}>
<LinearGradient
colors={getPriorityGradient(goal.priority)}
style={styles.priorityBadge}
>
<Text style={styles.priorityText}>{goal.priority.toUpperCase()}</Text>
</LinearGradient>
{isCompleted ? (
<Badge variant="success" label="COMPLETED" />
) : (
<View>
{goal.priority === "high" && (
<Badge variant="danger" label={goal.priority.toUpperCase()} />
)}
{goal.priority === "medium" && (
<Badge variant="warning" label={goal.priority.toUpperCase()} />
)}
{goal.priority === "low" && (
<Badge variant="success" label={goal.priority.toUpperCase()} />
)}
</View>
)}
{daysRemaining !== null && !isCompleted && (
<Text style={[styles.daysRemaining, daysRemaining < 0 && styles.overdue]}>
<Text
style={[
typography.caption,
{
color:
daysRemaining < 0 ? colors.danger : colors.textTertiary,
},
]}
>
{daysRemaining < 0
? `${Math.abs(daysRemaining)} days overdue`
: `${daysRemaining} days remaining`
}
: `${daysRemaining} days remaining`}
</Text>
)}
{isCompleted && goal.completedDate && (
<Text style={styles.completedDate}>
<Text style={[typography.caption, { color: colors.success }]}>
Completed {new Date(goal.completedDate).toLocaleDateString()}
</Text>
)}
</View>
</LinearGradient>
</MinimalCard>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
card: {
borderRadius: theme.borderRadius.xl,
padding: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: 'rgba(59, 130, 246, 0.1)',
overflow: 'hidden',
},
cardCompleted: {
borderColor: 'rgba(16, 185, 129, 0.2)',
},
priorityAccent: {
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
width: 4,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 12,
marginLeft: 8,
},
titleRow: {
flexDirection: 'row',
alignItems: 'flex-start',
flexDirection: "row",
alignItems: "flex-start",
flex: 1,
},
iconContainer: {
width: 40,
height: 40,
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
titleContainer: {
flex: 1,
},
title: {
fontSize: theme.typography.fontSize.lg,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.gray900,
marginBottom: 4,
},
titleCompleted: {
color: theme.colors.gray600,
textDecorationLine: 'line-through',
},
description: {
fontSize: theme.typography.fontSize.sm,
color: theme.colors.gray600,
lineHeight: 18,
marginLeft: 12,
},
actions: {
flexDirection: 'row',
flexDirection: "row",
gap: 8,
},
actionButton: {
@ -233,61 +256,16 @@ const styles = StyleSheet.create({
},
progressSection: {
marginBottom: 12,
marginLeft: 8,
},
progressInfo: {
flexDirection: 'row',
justifyContent: 'space-between',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 8,
},
progressText: {
fontSize: theme.typography.fontSize.sm,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.gray700,
},
progressPercentage: {
fontSize: theme.typography.fontSize.sm,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.primary,
},
progressBarContainer: {
height: 8,
backgroundColor: theme.colors.gray200,
borderRadius: 4,
overflow: 'hidden',
},
progressBar: {
height: '100%',
borderRadius: 4,
},
footer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginLeft: 8,
},
priorityBadge: {
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: theme.borderRadius.md,
},
priorityText: {
fontSize: theme.typography.fontSize.xs,
fontWeight: theme.typography.fontWeight.semibold,
color: theme.colors.white,
},
daysRemaining: {
fontSize: theme.typography.fontSize.xs,
color: theme.colors.gray600,
fontWeight: theme.typography.fontWeight.medium,
},
overdue: {
color: theme.colors.danger,
fontWeight: theme.typography.fontWeight.semibold,
},
completedDate: {
fontSize: theme.typography.fontSize.xs,
color: theme.colors.success,
fontWeight: theme.typography.fontWeight.medium,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
},
});