From 96db3ea3b738808289f3225be07cd6bbb79a3bb0 Mon Sep 17 00:00:00 2001 From: echo Date: Thu, 12 Mar 2026 16:25:57 +0100 Subject: [PATCH] recent activity feed updated, now showing real data --- apps/admin/data/fitai.db | Bin 172032 -> 172032 bytes apps/mobile/src/app/(tabs)/index.tsx | 187 +++++++++++++++------- apps/mobile/src/utils/activityFeed.ts | 220 ++++++++++++++++++++++++++ 3 files changed, 351 insertions(+), 56 deletions(-) create mode 100644 apps/mobile/src/utils/activityFeed.ts diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index d0afb89685d1167da2cb262f26e626b34eb4624a..4915813e455662ce069d3a76065ab2b6df7d8585 100644 GIT binary patch delta 311 zcmZoTz}0YoYl1Xm#zYxs#*B>#OZr7Pc)u|4@8hrGx8nQ3w~eoa&xrTSW<`ZNyuvmd z9P+*_48ja7rY6}1xjE_OhLbPOSLE7st3SOmHxo#0Hkx;dk%NPOA_Mb^Q!vf7pE#12YIJwZo2U- zbKf?D#N?99veewvl+03~yMSUqy!pjFNk(1<1_oySNeukk`S0*g+AQeM!asS^dsS5x UW?x1^*0D6-dB6S6dq$NF0Pv?}-~a#s delta 202 zcmZoTz}0YoYl1Xm+C&*=#8Tk-wi+s0SIXS7*SL56R$(Y#BH zZ0!8=8Ti+3=2_6nKXHM;=K1SW6}XuAUNG<%^1a|o1u6{Z6Rcwv_Vr~@=VxGK6l7sx zkmQ*B>t38lVsc4lS!zyxdFDp`;~>nyz_9tnJxNAh{_PA*{F4~?@9C6w4$dBePWVWXlv|Q diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx index 8aa8a0b..d630d89 100644 --- a/apps/mobile/src/app/(tabs)/index.tsx +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -9,12 +9,13 @@ import { TouchableOpacity, } from "react-native"; import { useUser } from "@clerk/clerk-expo"; -import { useState, useCallback, useEffect, useRef } from "react"; +import { useState, useCallback, useEffect, useRef, useMemo } from "react"; import { useFocusEffect } from "@react-navigation/native"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { Ionicons } from "@expo/vector-icons"; import { useTheme } from "../../contexts/ThemeContext"; import { useStatistics } from "../../contexts/StatisticsContext"; +import { useFitnessGoals } from "../../contexts/FitnessGoalsContext"; import { MinimalCard } from "../../components/MinimalCard"; import { SectionHeader } from "../../components/SectionHeader"; import { IconContainer } from "../../components/IconContainer"; @@ -22,6 +23,18 @@ import { ProgressBar } from "../../components/ProgressBar"; import { TrackMealModal } from "../../components/TrackMealModal"; import { AddWaterModal } from "../../components/AddWaterModal"; import { ScanFoodModal } from "../../components/ScanFoodModal"; +import { + checkInsToActivities, + completedGoalsToActivities, + newGoalsToActivities, + combineActivities, + getRecentActivities, + formatActivityTime, + formatDuration, + getActivityIcon, + getActivityEmoji, + ActivityType, +} from "../../utils/activityFeed"; const CALORIE_GOAL = 2000; // kcal const WATER_GOAL = 2000; // ml @@ -31,6 +44,7 @@ export default function HomeScreen() { const { colors, typography } = useTheme(); const { refetchStatistics, forceRefresh, statistics, loading } = useStatistics(); + const { goals } = useFitnessGoals(); const [refreshing, setRefreshing] = useState(false); const [trackMealModalVisible, setTrackMealModalVisible] = useState(false); const [addWaterModalVisible, setAddWaterModalVisible] = useState(false); @@ -205,6 +219,29 @@ export default function HomeScreen() { const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0; const currentStreak = statistics?.attendance.currentStreak || 0; + // Combine activities from multiple sources + const recentActivities = useMemo(() => { + if (!statistics || !goals) return []; + + const checkInActivities = checkInsToActivities(statistics.attendance).slice( + 0, + 2, + ); // Only last 2 check-ins + const completedGoalActivities = completedGoalsToActivities(goals).slice( + 0, + 2, + ); // Only last 2 completed goals + const newGoalActivities = newGoalsToActivities(goals, 7); // Last 7 days + + const allActivities = combineActivities( + checkInActivities, + completedGoalActivities, + newGoalActivities, + ); + + return getRecentActivities(allActivities, 5); // Show top 5 + }, [statistics, goals]); + // Check for midnight reset useEffect(() => { const checkAndResetIfNeeded = async () => { @@ -657,72 +694,104 @@ export default function HomeScreen() { {/* Recent Activity */} console.log("See all tapped")} + title="📊 Recent Activity" + subtitle={ + recentActivities.length === 0 + ? "Your activity will appear here" + : `${recentActivities.length} recent activities` + } /> - - - - - - - - Upper Body Power + {recentActivities.length === 0 ? ( + + + 🏃 + + No Recent Activity - Today, 10:00 AM + Check in at the gym or complete a goal to get started! 💪 - - 45m - + ) : ( + + {recentActivities.map((activity) => { + const iconName = getActivityIcon(activity.type); + const emoji = getActivityEmoji(activity.type); + const timeStr = formatActivityTime(activity.timestamp); + const durationStr = formatDuration(activity.duration); - - - - - - - Morning Cardio - - - Yesterday, 7:30 AM - - - - 30m - - - + // Determine color based on activity type + let iconColor = colors.primary; + if (activity.type === ActivityType.GOAL_COMPLETED) { + iconColor = colors.success; + } else if (activity.type === ActivityType.GOAL_CREATED) { + iconColor = colors.accent; + } else if (activity.type === ActivityType.GYM_CHECKIN) { + iconColor = colors.primary; + } + + return ( + + + + + + + {emoji} {activity.title} + + + {timeStr} + + + {durationStr && ( + + {durationStr} + + )} + + ); + })} + + )} ; +} + +/** + * Convert attendance check-ins to activity items + */ +export function checkInsToActivities( + attendance: AttendanceStatistics, +): ActivityItem[] { + return attendance.recentCheckIns.map((checkIn) => { + const checkInDate = new Date(checkIn.checkInTime); + + return { + id: `checkin-${checkIn.id}`, + type: ActivityType.GYM_CHECKIN, + title: "Gym Check-in", + timestamp: checkInDate, + duration: checkIn.duration || undefined, + metadata: { + checkOutTime: checkIn.checkOutTime, + }, + }; + }); +} + +/** + * Convert completed goals to activity items + */ +export function completedGoalsToActivities( + goals: FitnessGoal[], +): ActivityItem[] { + return goals + .filter((goal) => goal.status === "completed" && goal.completedDate) + .map((goal) => { + const completedDate = new Date(goal.completedDate!); + + return { + id: `goal-completed-${goal.id}`, + type: ActivityType.GOAL_COMPLETED, + title: goal.title, + timestamp: completedDate, + metadata: { + goalType: goal.goalType, + priority: goal.priority, + targetValue: goal.targetValue, + unit: goal.unit, + }, + }; + }); +} + +/** + * Convert recently created goals to activity items + */ +export function newGoalsToActivities( + goals: FitnessGoal[], + daysBack: number = 7, +): ActivityItem[] { + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - daysBack); + + return goals + .filter((goal) => { + const createdDate = new Date(goal.createdAt); + return goal.status === "active" && createdDate > cutoffDate; + }) + .map((goal) => { + const createdDate = new Date(goal.createdAt); + + return { + id: `goal-created-${goal.id}`, + type: ActivityType.GOAL_CREATED, + title: goal.title, + timestamp: createdDate, + metadata: { + goalType: goal.goalType, + priority: goal.priority, + }, + }; + }); +} + +/** + * Combine and sort all activities by timestamp (most recent first) + */ +export function combineActivities( + ...activityGroups: ActivityItem[][] +): ActivityItem[] { + const allActivities = activityGroups.flat(); + + return allActivities.sort( + (a, b) => b.timestamp.getTime() - a.timestamp.getTime(), + ); +} + +/** + * Get recent activities limited to a specific count + */ +export function getRecentActivities( + activities: ActivityItem[], + limit: number = 5, +): ActivityItem[] { + return activities.slice(0, limit); +} + +/** + * Format activity timestamp for display + */ +export function formatActivityTime(timestamp: Date): string { + const now = new Date(); + const diffMs = now.getTime() - timestamp.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + // Today + if (diffDays === 0) { + if (diffMins < 60) { + return diffMins <= 1 ? "Just now" : `${diffMins}m ago`; + } + return `Today, ${timestamp.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })}`; + } + + // Yesterday + if (diffDays === 1) { + return `Yesterday, ${timestamp.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })}`; + } + + // This week (within 7 days) + if (diffDays < 7) { + return `${diffDays}d ago`; + } + + // Older + return timestamp.toLocaleDateString(); +} + +/** + * Format duration in minutes to human-readable string + */ +export function formatDuration(minutes?: number): string | undefined { + if (!minutes) return undefined; + + if (minutes < 60) { + return `${minutes}m`; + } + + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + + if (mins === 0) { + return `${hours}h`; + } + + return `${hours}h ${mins}m`; +} + +/** + * Get icon name for activity type + */ +export function getActivityIcon(type: ActivityType): string { + switch (type) { + case ActivityType.GYM_CHECKIN: + return "barbell"; + case ActivityType.GOAL_COMPLETED: + return "trophy"; + case ActivityType.GOAL_CREATED: + return "flag"; + case ActivityType.NUTRITION_MILESTONE: + return "restaurant"; + default: + return "checkmark-circle"; + } +} + +/** + * Get activity title prefix/emoji + */ +export function getActivityEmoji(type: ActivityType): string { + switch (type) { + case ActivityType.GYM_CHECKIN: + return "💪"; + case ActivityType.GOAL_COMPLETED: + return "🏆"; + case ActivityType.GOAL_CREATED: + return "🎯"; + case ActivityType.NUTRITION_MILESTONE: + return "🍽️"; + default: + return "✅"; + } +}