fitaiProto/apps/mobile/src/components/WeeklyProgressWidget.tsx
2026-03-11 06:07:16 +01:00

213 lines
5.8 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet, ActivityIndicator } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import { Ionicons } from "@expo/vector-icons";
import { theme } from "../styles/theme";
import { useStatistics } from "../contexts/StatisticsContext";
import type { WeeklyTrendData } from "../api/types";
import log from "../utils/logger";
export function WeeklyProgressWidget() {
const { statistics, loading, refetchStatistics } = useStatistics();
const [weeklyData, setWeeklyData] = useState<WeeklyTrendData[]>([]);
useEffect(() => {
refetchStatistics();
}, [refetchStatistics]);
useEffect(() => {
if (statistics?.weeklyTrend) {
log.debug("WeeklyProgressWidget - Processing weekly trend", {
weeklyTrendLength: statistics.weeklyTrend.length,
weeklyTrend: statistics.weeklyTrend,
statisticsKeys: Object.keys(statistics),
});
// Get last 4 weeks for compact display
const last4Weeks = statistics.weeklyTrend.slice(-4);
setWeeklyData(last4Weeks);
log.debug("WeeklyProgressWidget - Set weekly data", {
last4Weeks,
});
} else {
log.debug("WeeklyProgressWidget - No weekly trend data", {
hasStatistics: !!statistics,
statistics,
});
}
}, [statistics]);
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="small" color={theme.colors.primary} />
</View>
);
}
if (weeklyData.length === 0) {
return null;
}
// Calculate totals from last 4 weeks
const totalCheckIns = weeklyData.reduce(
(sum, week) => sum + week.checkIns,
0,
);
const totalGoals = weeklyData.reduce(
(sum, week) => sum + week.goalsCompleted,
0,
);
const avgProgress =
weeklyData.reduce((sum, week) => sum + week.averageProgress, 0) /
weeklyData.length;
// Get max value for scaling bars
const maxCheckIns = Math.max(...weeklyData.map((w) => w.checkIns), 1);
return (
<View style={styles.container}>
<LinearGradient
colors={theme.gradients.purple}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.card}
>
<View style={styles.header}>
<View>
<Text style={styles.title}>Weekly Progress</Text>
<Text style={styles.subtitle}>Last 4 weeks</Text>
</View>
<View style={styles.iconContainer}>
<Ionicons name="trending-up" size={24} color="#fff" />
</View>
</View>
<View style={styles.statsRow}>
<View style={styles.statItem}>
<Text style={styles.statValue}>{totalCheckIns}</Text>
<Text style={styles.statLabel}>Check-ins</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statValue}>{totalGoals}</Text>
<Text style={styles.statLabel}>Goals Met</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statValue}>{Math.round(avgProgress)}%</Text>
<Text style={styles.statLabel}>Avg Progress</Text>
</View>
</View>
<View style={styles.chartContainer}>
{weeklyData.map((week, index) => {
const barHeight = (week.checkIns / maxCheckIns) * 60;
return (
<View key={index} style={styles.barContainer}>
<View style={styles.barWrapper}>
<View
style={[styles.bar, { height: Math.max(barHeight, 4) }]}
/>
</View>
<Text style={styles.barLabel}>{week.weekLabel}</Text>
</View>
);
})}
</View>
</LinearGradient>
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 20,
marginBottom: 16,
},
card: {
borderRadius: theme.borderRadius["2xl"],
padding: 20,
...theme.shadows.medium,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 20,
},
title: {
fontSize: theme.typography.fontSize.xl,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.white,
marginBottom: 4,
},
subtitle: {
fontSize: theme.typography.fontSize.sm,
color: "rgba(255, 255, 255, 0.8)",
},
iconContainer: {
backgroundColor: "rgba(255, 255, 255, 0.2)",
width: 48,
height: 48,
borderRadius: 24,
justifyContent: "center",
alignItems: "center",
},
statsRow: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 20,
backgroundColor: "rgba(255, 255, 255, 0.15)",
borderRadius: theme.borderRadius.xl,
padding: 16,
},
statItem: {
alignItems: "center",
flex: 1,
},
statValue: {
fontSize: theme.typography.fontSize["2xl"],
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.white,
marginBottom: 4,
},
statLabel: {
fontSize: theme.typography.fontSize.xs,
color: "rgba(255, 255, 255, 0.7)",
textAlign: "center",
},
statDivider: {
width: 1,
backgroundColor: "rgba(255, 255, 255, 0.2)",
marginHorizontal: 8,
},
chartContainer: {
flexDirection: "row",
justifyContent: "space-around",
alignItems: "flex-end",
height: 80,
},
barContainer: {
alignItems: "center",
flex: 1,
},
barWrapper: {
height: 60,
justifyContent: "flex-end",
marginBottom: 6,
},
bar: {
width: 24,
backgroundColor: "rgba(255, 255, 255, 0.9)",
borderRadius: 4,
minHeight: 4,
},
barLabel: {
fontSize: 10,
color: "rgba(255, 255, 255, 0.7)",
fontWeight: "500",
},
});