user report gtound work
This commit is contained in:
1 parent
1be2de05fa
commit
74f0d0dbed
42 files changed
+8127
-5
No files matched your search
@@ -7,6 +7,11 @@ import {
|
||||
Recommendation,
|
||||
FitnessGoal,
|
||||
Notification,
|
||||
DailyNutrition,
|
||||
DailyHydration,
|
||||
MealEntry,
|
||||
FitnessProfileHistory,
|
||||
TrainerClientAssignment,
|
||||
DatabaseConfig,
|
||||
} from "./types";
|
||||
import {
|
||||
@@ -18,6 +23,11 @@ import {
|
||||
recommendations,
|
||||
fitnessGoals,
|
||||
notifications,
|
||||
dailyNutrition,
|
||||
dailyHydration,
|
||||
mealEntries,
|
||||
fitnessProfileHistory,
|
||||
trainerClientAssignments,
|
||||
eq,
|
||||
and,
|
||||
desc,
|
||||
@@ -1524,4 +1534,651 @@ export class DrizzleDatabase implements IDatabase {
|
||||
createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== DAILY NUTRITION OPERATIONS ====================
|
||||
|
||||
async createDailyNutrition(
|
||||
nutrition: Omit<DailyNutrition, "id" | "createdAt" | "updatedAt">,
|
||||
): Promise<DailyNutrition> {
|
||||
const id = `nutrition_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const now = new Date();
|
||||
|
||||
const newNutrition = {
|
||||
id,
|
||||
...nutrition,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await this.db.insert(dailyNutrition).values(newNutrition as any);
|
||||
return this.mapDailyNutrition(newNutrition);
|
||||
}
|
||||
|
||||
async getDailyNutrition(
|
||||
userId: string,
|
||||
date: string,
|
||||
): Promise<DailyNutrition | null> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyNutrition)
|
||||
.where(
|
||||
and(eq(dailyNutrition.userId, userId), eq(dailyNutrition.date, date)),
|
||||
)
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapDailyNutrition(results[0]) : null;
|
||||
}
|
||||
|
||||
async getDailyNutritionById(id: string): Promise<DailyNutrition | null> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyNutrition)
|
||||
.where(eq(dailyNutrition.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapDailyNutrition(results[0]) : null;
|
||||
}
|
||||
|
||||
async getDailyNutritionRange(
|
||||
userId: string,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
): Promise<DailyNutrition[]> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyNutrition)
|
||||
.where(
|
||||
and(
|
||||
eq(dailyNutrition.userId, userId),
|
||||
gte(dailyNutrition.date, startDate),
|
||||
lte(dailyNutrition.date, endDate),
|
||||
),
|
||||
)
|
||||
.orderBy(dailyNutrition.date)
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapDailyNutrition(row));
|
||||
}
|
||||
|
||||
async updateDailyNutrition(
|
||||
id: string,
|
||||
updates: Partial<DailyNutrition>,
|
||||
): Promise<DailyNutrition | null> {
|
||||
const { id: _, ...updateData } = updates as any;
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
const existing = await this.db
|
||||
.select()
|
||||
.from(dailyNutrition)
|
||||
.where(eq(dailyNutrition.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
return existing.length > 0 ? this.mapDailyNutrition(existing[0]) : null;
|
||||
}
|
||||
|
||||
await this.db
|
||||
.update(dailyNutrition)
|
||||
.set({ ...updateData, updatedAt: new Date() })
|
||||
.where(eq(dailyNutrition.id, id))
|
||||
.run();
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyNutrition)
|
||||
.where(eq(dailyNutrition.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapDailyNutrition(results[0]) : null;
|
||||
}
|
||||
|
||||
async deleteDailyNutrition(id: string): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.delete(dailyNutrition)
|
||||
.where(eq(dailyNutrition.id, id))
|
||||
.run();
|
||||
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
private mapDailyNutrition(row: Record<string, unknown>): DailyNutrition {
|
||||
const createdAtValue = row.createdAt as number | Date;
|
||||
const updatedAtValue = row.updatedAt as number | Date;
|
||||
|
||||
return {
|
||||
id: String(row.id),
|
||||
userId: String(row.userId),
|
||||
date: String(row.date),
|
||||
totalCalories: Number(row.totalCalories || 0),
|
||||
calorieGoal: Number(row.calorieGoal || 2000),
|
||||
meals: row.meals ? (JSON.parse(String(row.meals)) as any) : undefined,
|
||||
createdAt:
|
||||
typeof createdAtValue === "number"
|
||||
? new Date(createdAtValue * 1000)
|
||||
: new Date(createdAtValue),
|
||||
updatedAt:
|
||||
typeof updatedAtValue === "number"
|
||||
? new Date(updatedAtValue * 1000)
|
||||
: new Date(updatedAtValue),
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== MEAL ENTRY OPERATIONS ====================
|
||||
|
||||
async createMealEntry(
|
||||
meal: Omit<MealEntry, "id" | "createdAt">,
|
||||
): Promise<MealEntry> {
|
||||
const id = `meal_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const now = new Date();
|
||||
|
||||
const newMeal = {
|
||||
id,
|
||||
...meal,
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
await this.db.insert(mealEntries).values(newMeal as any);
|
||||
return this.mapMealEntry(newMeal);
|
||||
}
|
||||
|
||||
async getMealEntriesByDate(
|
||||
userId: string,
|
||||
date: string,
|
||||
): Promise<MealEntry[]> {
|
||||
// Parse date string to get start and end of day in seconds
|
||||
const startOfDay = new Date(date);
|
||||
startOfDay.setHours(0, 0, 0, 0);
|
||||
const endOfDay = new Date(date);
|
||||
endOfDay.setHours(23, 59, 59, 999);
|
||||
|
||||
const startTimestamp = Math.floor(startOfDay.getTime() / 1000);
|
||||
const endTimestamp = Math.floor(endOfDay.getTime() / 1000);
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(mealEntries)
|
||||
.where(
|
||||
and(
|
||||
eq(mealEntries.userId, userId),
|
||||
sql`${mealEntries.timestamp} >= ${startTimestamp}`,
|
||||
sql`${mealEntries.timestamp} <= ${endTimestamp}`,
|
||||
),
|
||||
)
|
||||
.orderBy(mealEntries.timestamp)
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapMealEntry(row));
|
||||
}
|
||||
|
||||
async getMealEntryById(id: string): Promise<MealEntry | null> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(mealEntries)
|
||||
.where(eq(mealEntries.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapMealEntry(results[0]) : null;
|
||||
}
|
||||
|
||||
async deleteMealEntry(id: string): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.delete(mealEntries)
|
||||
.where(eq(mealEntries.id, id))
|
||||
.run();
|
||||
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
private mapMealEntry(row: Record<string, unknown>): MealEntry {
|
||||
const timestampValue = row.timestamp as number | Date;
|
||||
const createdAtValue = row.createdAt as number | Date;
|
||||
|
||||
return {
|
||||
id: String(row.id),
|
||||
userId: String(row.userId),
|
||||
dailyNutritionId: row.dailyNutritionId
|
||||
? String(row.dailyNutritionId)
|
||||
: undefined,
|
||||
mealType: String(row.mealType) as MealEntry["mealType"],
|
||||
foodName: String(row.foodName),
|
||||
calories: Number(row.calories),
|
||||
protein: row.protein ? Number(row.protein) : undefined,
|
||||
carbs: row.carbs ? Number(row.carbs) : undefined,
|
||||
fats: row.fats ? Number(row.fats) : undefined,
|
||||
timestamp:
|
||||
typeof timestampValue === "number"
|
||||
? new Date(timestampValue * 1000)
|
||||
: new Date(timestampValue),
|
||||
createdAt:
|
||||
typeof createdAtValue === "number"
|
||||
? new Date(createdAtValue * 1000)
|
||||
: new Date(createdAtValue),
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== DAILY HYDRATION OPERATIONS ====================
|
||||
|
||||
async createDailyHydration(
|
||||
hydration: Omit<DailyHydration, "id" | "createdAt" | "updatedAt">,
|
||||
): Promise<DailyHydration> {
|
||||
const id = `hydration_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const now = new Date();
|
||||
|
||||
const newHydration = {
|
||||
id,
|
||||
...hydration,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await this.db.insert(dailyHydration).values(newHydration as any);
|
||||
return this.mapDailyHydration(newHydration);
|
||||
}
|
||||
|
||||
async getDailyHydration(
|
||||
userId: string,
|
||||
date: string,
|
||||
): Promise<DailyHydration | null> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyHydration)
|
||||
.where(
|
||||
and(eq(dailyHydration.userId, userId), eq(dailyHydration.date, date)),
|
||||
)
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapDailyHydration(results[0]) : null;
|
||||
}
|
||||
|
||||
async getDailyHydrationById(id: string): Promise<DailyHydration | null> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyHydration)
|
||||
.where(eq(dailyHydration.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapDailyHydration(results[0]) : null;
|
||||
}
|
||||
|
||||
async getDailyHydrationRange(
|
||||
userId: string,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
): Promise<DailyHydration[]> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyHydration)
|
||||
.where(
|
||||
and(
|
||||
eq(dailyHydration.userId, userId),
|
||||
gte(dailyHydration.date, startDate),
|
||||
lte(dailyHydration.date, endDate),
|
||||
),
|
||||
)
|
||||
.orderBy(dailyHydration.date)
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapDailyHydration(row));
|
||||
}
|
||||
|
||||
async updateDailyHydration(
|
||||
id: string,
|
||||
updates: Partial<DailyHydration>,
|
||||
): Promise<DailyHydration | null> {
|
||||
const { id: _, ...updateData } = updates as any;
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
const existing = await this.db
|
||||
.select()
|
||||
.from(dailyHydration)
|
||||
.where(eq(dailyHydration.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
return existing.length > 0 ? this.mapDailyHydration(existing[0]) : null;
|
||||
}
|
||||
|
||||
await this.db
|
||||
.update(dailyHydration)
|
||||
.set({ ...updateData, updatedAt: new Date() })
|
||||
.where(eq(dailyHydration.id, id))
|
||||
.run();
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(dailyHydration)
|
||||
.where(eq(dailyHydration.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0 ? this.mapDailyHydration(results[0]) : null;
|
||||
}
|
||||
|
||||
async deleteDailyHydration(id: string): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.delete(dailyHydration)
|
||||
.where(eq(dailyHydration.id, id))
|
||||
.run();
|
||||
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
private mapDailyHydration(row: Record<string, unknown>): DailyHydration {
|
||||
const createdAtValue = row.createdAt as number | Date;
|
||||
const updatedAtValue = row.updatedAt as number | Date;
|
||||
|
||||
return {
|
||||
id: String(row.id),
|
||||
userId: String(row.userId),
|
||||
date: String(row.date),
|
||||
totalWater: Number(row.totalWater || 0),
|
||||
waterGoal: Number(row.waterGoal || 2000),
|
||||
entries: row.entries
|
||||
? (JSON.parse(String(row.entries)) as any)
|
||||
: undefined,
|
||||
createdAt:
|
||||
typeof createdAtValue === "number"
|
||||
? new Date(createdAtValue * 1000)
|
||||
: new Date(createdAtValue),
|
||||
updatedAt:
|
||||
typeof updatedAtValue === "number"
|
||||
? new Date(updatedAtValue * 1000)
|
||||
: new Date(updatedAtValue),
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== FITNESS PROFILE HISTORY OPERATIONS ====================
|
||||
|
||||
async createFitnessProfileHistory(
|
||||
history: Omit<FitnessProfileHistory, "id" | "createdAt">,
|
||||
): Promise<FitnessProfileHistory> {
|
||||
const id = `profile_history_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const now = new Date();
|
||||
|
||||
const newHistory = {
|
||||
id,
|
||||
...history,
|
||||
createdAt: now,
|
||||
};
|
||||
|
||||
await this.db.insert(fitnessProfileHistory).values(newHistory as any);
|
||||
return this.mapFitnessProfileHistory(newHistory);
|
||||
}
|
||||
|
||||
async getFitnessProfileHistory(
|
||||
userId: string,
|
||||
startDate?: Date,
|
||||
endDate?: Date,
|
||||
): Promise<FitnessProfileHistory[]> {
|
||||
const conditions = [eq(fitnessProfileHistory.userId, userId)];
|
||||
|
||||
if (startDate) {
|
||||
const startTimestamp = Math.floor(startDate.getTime() / 1000);
|
||||
conditions.push(
|
||||
sql`${fitnessProfileHistory.changedAt} >= ${startTimestamp}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
const endTimestamp = Math.floor(endDate.getTime() / 1000);
|
||||
conditions.push(
|
||||
sql`${fitnessProfileHistory.changedAt} <= ${endTimestamp}`,
|
||||
);
|
||||
}
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(fitnessProfileHistory)
|
||||
.where(and(...conditions))
|
||||
.orderBy(desc(fitnessProfileHistory.changedAt))
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapFitnessProfileHistory(row));
|
||||
}
|
||||
|
||||
async getWeightHistory(
|
||||
userId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
): Promise<FitnessProfileHistory[]> {
|
||||
const startTimestamp = Math.floor(startDate.getTime() / 1000);
|
||||
const endTimestamp = Math.floor(endDate.getTime() / 1000);
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(fitnessProfileHistory)
|
||||
.where(
|
||||
and(
|
||||
eq(fitnessProfileHistory.userId, userId),
|
||||
eq(fitnessProfileHistory.changeType, "weight"),
|
||||
sql`${fitnessProfileHistory.changedAt} >= ${startTimestamp}`,
|
||||
sql`${fitnessProfileHistory.changedAt} <= ${endTimestamp}`,
|
||||
),
|
||||
)
|
||||
.orderBy(fitnessProfileHistory.changedAt)
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapFitnessProfileHistory(row));
|
||||
}
|
||||
|
||||
private mapFitnessProfileHistory(
|
||||
row: Record<string, unknown>,
|
||||
): FitnessProfileHistory {
|
||||
const changedAtValue = row.changedAt as number | Date;
|
||||
const createdAtValue = row.createdAt as number | Date;
|
||||
|
||||
return {
|
||||
id: String(row.id),
|
||||
userId: String(row.userId),
|
||||
fitnessProfileId: String(row.fitnessProfileId),
|
||||
changeType: String(row.changeType) as FitnessProfileHistory["changeType"],
|
||||
fieldName: String(row.fieldName),
|
||||
previousValue: row.previousValue ? String(row.previousValue) : undefined,
|
||||
newValue: row.newValue ? String(row.newValue) : undefined,
|
||||
changedAt:
|
||||
typeof changedAtValue === "number"
|
||||
? new Date(changedAtValue * 1000)
|
||||
: new Date(changedAtValue),
|
||||
createdAt:
|
||||
typeof createdAtValue === "number"
|
||||
? new Date(createdAtValue * 1000)
|
||||
: new Date(createdAtValue),
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== TRAINER-CLIENT ASSIGNMENT OPERATIONS ====================
|
||||
|
||||
async createTrainerClientAssignment(
|
||||
assignment: Omit<TrainerClientAssignment, "id" | "createdAt" | "updatedAt">,
|
||||
): Promise<TrainerClientAssignment> {
|
||||
const id = `assignment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const now = new Date();
|
||||
|
||||
const newAssignment = {
|
||||
id,
|
||||
...assignment,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await this.db.insert(trainerClientAssignments).values(newAssignment as any);
|
||||
return this.mapTrainerClientAssignment(newAssignment);
|
||||
}
|
||||
|
||||
async getTrainerClientAssignments(
|
||||
trainerId: string,
|
||||
): Promise<TrainerClientAssignment[]> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(trainerClientAssignments)
|
||||
.where(
|
||||
and(
|
||||
eq(trainerClientAssignments.trainerId, trainerId),
|
||||
eq(trainerClientAssignments.isActive, true),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(trainerClientAssignments.assignedAt))
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapTrainerClientAssignment(row));
|
||||
}
|
||||
|
||||
async getClientTrainerAssignment(
|
||||
clientId: string,
|
||||
): Promise<TrainerClientAssignment | null> {
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(trainerClientAssignments)
|
||||
.where(
|
||||
and(
|
||||
eq(trainerClientAssignments.clientId, clientId),
|
||||
eq(trainerClientAssignments.isActive, true),
|
||||
),
|
||||
)
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0
|
||||
? this.mapTrainerClientAssignment(results[0])
|
||||
: null;
|
||||
}
|
||||
|
||||
async deleteTrainerClientAssignment(id: string): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.delete(trainerClientAssignments)
|
||||
.where(eq(trainerClientAssignments.id, id))
|
||||
.run();
|
||||
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
async deactivateTrainerClientAssignment(
|
||||
id: string,
|
||||
): Promise<TrainerClientAssignment | null> {
|
||||
await this.db
|
||||
.update(trainerClientAssignments)
|
||||
.set({ isActive: false, updatedAt: new Date() })
|
||||
.where(eq(trainerClientAssignments.id, id))
|
||||
.run();
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(trainerClientAssignments)
|
||||
.where(eq(trainerClientAssignments.id, id))
|
||||
.limit(1)
|
||||
.all();
|
||||
|
||||
return results.length > 0
|
||||
? this.mapTrainerClientAssignment(results[0])
|
||||
: null;
|
||||
}
|
||||
|
||||
private mapTrainerClientAssignment(
|
||||
row: Record<string, unknown>,
|
||||
): TrainerClientAssignment {
|
||||
const assignedAtValue = row.assignedAt as number | Date;
|
||||
const createdAtValue = row.createdAt as number | Date;
|
||||
const updatedAtValue = row.updatedAt as number | Date;
|
||||
|
||||
return {
|
||||
id: String(row.id),
|
||||
trainerId: String(row.trainerId),
|
||||
clientId: String(row.clientId),
|
||||
assignedAt:
|
||||
typeof assignedAtValue === "number"
|
||||
? new Date(assignedAtValue * 1000)
|
||||
: new Date(assignedAtValue),
|
||||
assignedBy: row.assignedBy ? String(row.assignedBy) : undefined,
|
||||
isActive: Boolean(row.isActive),
|
||||
createdAt:
|
||||
typeof createdAtValue === "number"
|
||||
? new Date(createdAtValue * 1000)
|
||||
: new Date(createdAtValue),
|
||||
updatedAt:
|
||||
typeof updatedAtValue === "number"
|
||||
? new Date(updatedAtValue * 1000)
|
||||
: new Date(updatedAtValue),
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== ENHANCED ATTENDANCE OPERATIONS FOR REPORTS ====================
|
||||
|
||||
async getAttendanceByWeek(
|
||||
userId: string,
|
||||
weekStart: Date,
|
||||
): Promise<Attendance[]> {
|
||||
// Calculate week end (Sunday at 23:59:59)
|
||||
const weekEnd = new Date(weekStart);
|
||||
weekEnd.setDate(weekEnd.getDate() + 6);
|
||||
weekEnd.setHours(23, 59, 59, 999);
|
||||
|
||||
const startTimestamp = Math.floor(weekStart.getTime() / 1000);
|
||||
const endTimestamp = Math.floor(weekEnd.getTime() / 1000);
|
||||
|
||||
const results = await this.db
|
||||
.select()
|
||||
.from(attendance)
|
||||
.where(
|
||||
and(
|
||||
eq(attendance.userId, userId),
|
||||
sql`${attendance.checkInTime} >= ${startTimestamp}`,
|
||||
sql`${attendance.checkInTime} <= ${endTimestamp}`,
|
||||
),
|
||||
)
|
||||
.orderBy(attendance.checkInTime)
|
||||
.all();
|
||||
|
||||
return results.map((row) => this.mapAttendance(row));
|
||||
}
|
||||
|
||||
async calculateWeeklyCheckInStats(
|
||||
userId: string,
|
||||
weekStart: Date,
|
||||
): Promise<{
|
||||
totalCheckIns: number;
|
||||
totalTimeSpent: number;
|
||||
avgSessionDuration: number;
|
||||
byType: { gym: number; class: number; personal_training: number };
|
||||
}> {
|
||||
const attendanceRecords = await this.getAttendanceByWeek(userId, weekStart);
|
||||
|
||||
const stats = {
|
||||
totalCheckIns: attendanceRecords.length,
|
||||
totalTimeSpent: 0,
|
||||
avgSessionDuration: 0,
|
||||
byType: {
|
||||
gym: 0,
|
||||
class: 0,
|
||||
personal_training: 0,
|
||||
},
|
||||
};
|
||||
|
||||
let completedSessions = 0;
|
||||
|
||||
for (const record of attendanceRecords) {
|
||||
// Count by type
|
||||
if (record.type === "gym") stats.byType.gym++;
|
||||
else if (record.type === "class") stats.byType.class++;
|
||||
else if (record.type === "personal_training")
|
||||
stats.byType.personal_training++;
|
||||
|
||||
// Calculate time spent (only for completed sessions with check-out)
|
||||
if (record.checkOutTime) {
|
||||
const duration =
|
||||
(record.checkOutTime.getTime() - record.checkInTime.getTime()) /
|
||||
(1000 * 60); // in minutes
|
||||
stats.totalTimeSpent += duration;
|
||||
completedSessions++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate average session duration
|
||||
if (completedSessions > 0) {
|
||||
stats.avgSessionDuration = stats.totalTimeSpent / completedSessions;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user