recent activity feed
updated, now showing real data
This commit is contained in:
1 parent
981208ab7b
commit
96db3ea3b7
3 files changed
+351
-56
No files matched your search
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Activity Feed Utilities
|
||||
*
|
||||
* Combines multiple data sources (check-ins, goals, nutrition) into a unified activity feed
|
||||
*/
|
||||
|
||||
import type { AttendanceStatistics } from "../api/types";
|
||||
import type { FitnessGoal } from "../services/fitnessGoals";
|
||||
|
||||
export enum ActivityType {
|
||||
GYM_CHECKIN = "gym_checkin",
|
||||
GOAL_COMPLETED = "goal_completed",
|
||||
GOAL_CREATED = "goal_created",
|
||||
NUTRITION_MILESTONE = "nutrition_milestone",
|
||||
}
|
||||
|
||||
export interface ActivityItem {
|
||||
id: string;
|
||||
type: ActivityType;
|
||||
title: string;
|
||||
timestamp: Date;
|
||||
duration?: number; // in minutes
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "✅";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user