721 lines
21 KiB
TypeScript
721 lines
21 KiB
TypeScript
import {
|
|
IDatabase,
|
|
User,
|
|
Client,
|
|
FitnessProfile,
|
|
Attendance,
|
|
Recommendation,
|
|
FitnessGoal,
|
|
DatabaseConfig,
|
|
} from "./types";
|
|
import {
|
|
db as defaultDb,
|
|
users,
|
|
clients,
|
|
fitnessProfiles,
|
|
attendance,
|
|
recommendations,
|
|
fitnessGoals,
|
|
eq,
|
|
and,
|
|
desc,
|
|
sql,
|
|
} from "@fitai/database";
|
|
import { InferSelectModel } from "drizzle-orm";
|
|
|
|
export class DrizzleDatabase implements IDatabase {
|
|
private config: DatabaseConfig;
|
|
private db: typeof defaultDb;
|
|
|
|
constructor(config: DatabaseConfig, db?: typeof defaultDb) {
|
|
this.config = config;
|
|
this.db = db || defaultDb;
|
|
}
|
|
|
|
async connect(): Promise<void> {
|
|
// Drizzle with better-sqlite3 connects synchronously on initialization
|
|
// We can just log here if needed
|
|
if (this.config.options?.logging) {
|
|
console.log("Drizzle database connected");
|
|
}
|
|
await this.createTables();
|
|
}
|
|
|
|
async disconnect(): Promise<void> {
|
|
// better-sqlite3 handle is managed by Drizzle, usually no explicit disconnect needed for connection pooling
|
|
// but we can close the underlying sqlite instance if we had access to it.
|
|
// For now, we'll assume it's handled.
|
|
if (this.config.options?.logging) {
|
|
console.log("Drizzle database disconnected");
|
|
}
|
|
}
|
|
|
|
private async createTables(): Promise<void> {
|
|
// Users table
|
|
await this.db.run(sql`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
email TEXT UNIQUE NOT NULL,
|
|
first_name TEXT NOT NULL,
|
|
last_name TEXT NOT NULL,
|
|
password TEXT,
|
|
phone TEXT,
|
|
role TEXT NOT NULL CHECK (role IN ('superAdmin', 'admin', 'trainer', 'client')),
|
|
gym_id TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
)
|
|
`);
|
|
|
|
// Migration: ensure gym_id column exists on users (for existing DBs)
|
|
const userCols = await this.db.all(sql`PRAGMA table_info('users')`);
|
|
if (!userCols.some((c: any) => c.name === "gym_id")) {
|
|
await this.db.run(sql`ALTER TABLE users ADD COLUMN gym_id TEXT`);
|
|
}
|
|
// Clients table
|
|
await this.db.run(sql`
|
|
CREATE TABLE IF NOT EXISTS clients (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
membership_type TEXT NOT NULL CHECK (membership_type IN ('basic', 'premium', 'vip')),
|
|
membership_status TEXT NOT NULL CHECK (membership_status IN ('active', 'inactive', 'suspended')),
|
|
join_date INTEGER NOT NULL,
|
|
last_visit INTEGER,
|
|
emergency_contact_name TEXT,
|
|
emergency_contact_phone TEXT,
|
|
emergency_contact_relationship TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
|
|
// Fitness profiles table
|
|
await this.db.run(sql`
|
|
CREATE TABLE IF NOT EXISTS fitness_profiles (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL UNIQUE,
|
|
height REAL,
|
|
weight REAL,
|
|
age INTEGER,
|
|
gender TEXT CHECK (gender IN ('male', 'female', 'other', 'prefer_not_to_say')),
|
|
activity_level TEXT CHECK (activity_level IN ('sedentary', 'lightly_active', 'moderately_active', 'very_active', 'extremely_active')),
|
|
fitness_goals TEXT,
|
|
exercise_habits TEXT,
|
|
diet_habits TEXT,
|
|
medical_conditions TEXT,
|
|
allergies TEXT,
|
|
injuries TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
|
|
// Attendance table
|
|
await this.db.run(sql`
|
|
CREATE TABLE IF NOT EXISTS attendance (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
check_in_time INTEGER NOT NULL,
|
|
check_out_time INTEGER,
|
|
type TEXT NOT NULL CHECK (type IN ('gym', 'class', 'personal_training')),
|
|
notes TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
|
|
// Recommendations table
|
|
await this.db.run(sql`
|
|
CREATE TABLE IF NOT EXISTS recommendations (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
fitness_profile_id TEXT NOT NULL,
|
|
recommendation_text TEXT NOT NULL,
|
|
activity_plan TEXT NOT NULL,
|
|
diet_plan TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'rejected')) DEFAULT 'pending',
|
|
generated_at INTEGER NOT NULL,
|
|
approved_at INTEGER,
|
|
approved_by TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (fitness_profile_id) REFERENCES fitness_profiles (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
|
|
// Fitness Goals table
|
|
await this.db.run(sql`
|
|
CREATE TABLE IF NOT EXISTS fitness_goals (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
fitness_profile_id TEXT,
|
|
goal_type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
target_value REAL,
|
|
current_value REAL,
|
|
unit TEXT,
|
|
start_date INTEGER NOT NULL,
|
|
target_date INTEGER,
|
|
completed_date INTEGER,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
progress REAL DEFAULT 0,
|
|
priority TEXT DEFAULT 'medium',
|
|
notes TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
|
FOREIGN KEY (fitness_profile_id) REFERENCES fitness_profiles (id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
}
|
|
|
|
// User operations
|
|
async createUser(
|
|
userData: Omit<User, "createdAt" | "updatedAt" | "id"> & { id?: string },
|
|
): Promise<User> {
|
|
const id = userData.id || Math.random().toString(36).substr(2, 9);
|
|
const now = new Date();
|
|
|
|
const newUser = {
|
|
...userData,
|
|
id,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
|
|
await this.db.insert(users).values(newUser);
|
|
return newUser;
|
|
}
|
|
|
|
async getUserById(id: string): Promise<User | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.id, id))
|
|
.get();
|
|
return result ? this.mapUser(result) : null;
|
|
}
|
|
|
|
async getUserByEmail(email: string): Promise<User | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, email))
|
|
.get();
|
|
return result ? this.mapUser(result) : null;
|
|
}
|
|
|
|
async getAllUsers(): Promise<User[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(users)
|
|
.orderBy(desc(users.createdAt))
|
|
.all();
|
|
return results.map(this.mapUser);
|
|
}
|
|
|
|
async updateUser(id: string, updates: Partial<User>): Promise<User | null> {
|
|
const { id: _, ...updateData } = updates;
|
|
if (Object.keys(updateData).length === 0) return this.getUserById(id);
|
|
|
|
await this.db
|
|
.update(users)
|
|
.set({ ...updateData, updatedAt: new Date() })
|
|
.where(eq(users.id, id))
|
|
.run();
|
|
|
|
return this.getUserById(id);
|
|
}
|
|
|
|
async deleteUser(id: string): Promise<boolean> {
|
|
const result = await this.db.delete(users).where(eq(users.id, id)).run();
|
|
return result.changes > 0;
|
|
}
|
|
|
|
async migrateUserId(oldId: string, newId: string): Promise<void> {
|
|
await this.db
|
|
.update(users)
|
|
.set({ id: newId })
|
|
.where(eq(users.id, oldId))
|
|
.run();
|
|
}
|
|
|
|
// Client operations
|
|
async createClient(clientData: Omit<Client, "id">): Promise<Client> {
|
|
const id = Math.random().toString(36).substr(2, 9);
|
|
const newClient = {
|
|
id,
|
|
...clientData,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
await this.db.insert(clients).values(newClient as any);
|
|
return this.mapClient(newClient);
|
|
}
|
|
|
|
async getClientById(id: string): Promise<Client | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(clients)
|
|
.where(eq(clients.id, id))
|
|
.get();
|
|
return result ? this.mapClient(result) : null;
|
|
}
|
|
|
|
async getClientByUserId(userId: string): Promise<Client | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(clients)
|
|
.where(eq(clients.userId, userId))
|
|
.get();
|
|
return result ? this.mapClient(result) : null;
|
|
}
|
|
|
|
async getAllClients(): Promise<Client[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(clients)
|
|
.orderBy(desc(clients.joinDate))
|
|
.all();
|
|
return results.map(this.mapClient);
|
|
}
|
|
|
|
async updateClient(
|
|
id: string,
|
|
updates: Partial<Client>,
|
|
): Promise<Client | null> {
|
|
const { id: _, ...updateData } = updates;
|
|
if (Object.keys(updateData).length === 0) return this.getClientById(id);
|
|
|
|
await this.db
|
|
.update(clients)
|
|
.set({ ...updateData, updatedAt: new Date() } as any)
|
|
.where(eq(clients.id, id))
|
|
.run();
|
|
|
|
return this.getClientById(id);
|
|
}
|
|
|
|
async deleteClient(id: string): Promise<boolean> {
|
|
const result = await this.db
|
|
.delete(clients)
|
|
.where(eq(clients.id, id))
|
|
.run();
|
|
return result.changes > 0;
|
|
}
|
|
|
|
// Fitness Profile operations
|
|
async createFitnessProfile(
|
|
profileData: Omit<FitnessProfile, "id" | "createdAt" | "updatedAt">,
|
|
): Promise<FitnessProfile> {
|
|
const now = new Date();
|
|
const id = Math.random().toString(36).substr(2, 9);
|
|
const newProfile = {
|
|
id,
|
|
...profileData,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
|
|
await this.db.insert(fitnessProfiles).values(newProfile as any);
|
|
return { id, ...profileData, createdAt: now, updatedAt: now };
|
|
}
|
|
|
|
async getFitnessProfileByUserId(
|
|
userId: string,
|
|
): Promise<FitnessProfile | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(fitnessProfiles)
|
|
.where(eq(fitnessProfiles.userId, userId))
|
|
.get();
|
|
return result ? this.mapFitnessProfile(result) : null;
|
|
}
|
|
|
|
async getAllFitnessProfiles(): Promise<FitnessProfile[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(fitnessProfiles)
|
|
.orderBy(desc(fitnessProfiles.createdAt))
|
|
.all();
|
|
return results.map(this.mapFitnessProfile);
|
|
}
|
|
|
|
async updateFitnessProfile(
|
|
userId: string,
|
|
updates: Partial<FitnessProfile>,
|
|
): Promise<FitnessProfile | null> {
|
|
const { userId: _, ...updateData } = updates;
|
|
if (Object.keys(updateData).length === 0)
|
|
return this.getFitnessProfileByUserId(userId);
|
|
|
|
const dbUpdates = { ...updateData } as any;
|
|
if (updateData.fitnessGoals) {
|
|
dbUpdates.fitnessGoals = JSON.stringify(updateData.fitnessGoals);
|
|
}
|
|
|
|
await this.db
|
|
.update(fitnessProfiles)
|
|
.set({ ...dbUpdates, updatedAt: new Date() })
|
|
.where(eq(fitnessProfiles.userId, userId))
|
|
.run();
|
|
|
|
return this.getFitnessProfileByUserId(userId);
|
|
}
|
|
|
|
async deleteFitnessProfile(userId: string): Promise<boolean> {
|
|
const result = await this.db
|
|
.delete(fitnessProfiles)
|
|
.where(eq(fitnessProfiles.userId, userId))
|
|
.run();
|
|
return result.changes > 0;
|
|
}
|
|
|
|
// Attendance operations
|
|
async checkIn(
|
|
userId: string,
|
|
type: "gym" | "class" | "personal_training",
|
|
notes?: string,
|
|
): Promise<Attendance> {
|
|
const id = Math.random().toString(36).substr(2, 9);
|
|
const now = new Date();
|
|
|
|
const newAttendance = {
|
|
id,
|
|
userId,
|
|
checkInTime: now,
|
|
type,
|
|
notes,
|
|
createdAt: now,
|
|
};
|
|
|
|
await this.db.insert(attendance).values(newAttendance as any);
|
|
|
|
// Update client last visit
|
|
const client = await this.getClientByUserId(userId);
|
|
if (client) {
|
|
await this.updateClient(client.id, { lastVisit: now });
|
|
}
|
|
|
|
return newAttendance;
|
|
}
|
|
|
|
async checkOut(attendanceId: string): Promise<Attendance | null> {
|
|
const now = new Date();
|
|
await this.db
|
|
.update(attendance)
|
|
.set({ checkOutTime: now })
|
|
.where(eq(attendance.id, attendanceId))
|
|
.run();
|
|
|
|
return this.getAttendanceById(attendanceId);
|
|
}
|
|
|
|
async getAttendanceById(id: string): Promise<Attendance | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(attendance)
|
|
.where(eq(attendance.id, id))
|
|
.get();
|
|
return result ? this.mapAttendance(result) : null;
|
|
}
|
|
|
|
async getAttendanceHistory(userId: string): Promise<Attendance[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(attendance)
|
|
.where(eq(attendance.userId, userId))
|
|
.orderBy(desc(attendance.checkInTime))
|
|
.all();
|
|
return results.map(this.mapAttendance);
|
|
}
|
|
|
|
async getAllAttendance(): Promise<Attendance[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(attendance)
|
|
.orderBy(desc(attendance.checkInTime))
|
|
.all();
|
|
return results.map(this.mapAttendance);
|
|
}
|
|
|
|
async getActiveCheckIn(userId: string): Promise<Attendance | null> {
|
|
// Drizzle doesn't support IS NULL in where directly with simple syntax sometimes, but eq(col, null) works or isNull(col)
|
|
// We need to check how to filter for null checkOutTime.
|
|
// In Drizzle, we can filter in JS or use isNull operator if imported.
|
|
// Let's fetch recent and filter for now to be safe, or use raw sql if needed, but better to use Drizzle operators.
|
|
// Actually, we can just fetch all for user and filter in memory since it's unlikely to be huge for active checkins,
|
|
// but correct way is `isNull(attendance.checkOutTime)`.
|
|
// Since I didn't import `isNull`, I'll fetch recent history and find first active.
|
|
|
|
const history = await this.getAttendanceHistory(userId);
|
|
return history.find((a) => !a.checkOutTime) || null;
|
|
}
|
|
|
|
// Recommendation operations
|
|
async createRecommendation(
|
|
data: Omit<Recommendation, "createdAt" | "approvedAt" | "approvedBy">,
|
|
): Promise<Recommendation> {
|
|
const now = new Date();
|
|
const newRec = {
|
|
...data,
|
|
createdAt: now,
|
|
status: data.status || "pending",
|
|
};
|
|
|
|
await this.db.insert(recommendations).values(newRec as any);
|
|
return newRec as Recommendation;
|
|
}
|
|
|
|
async getRecommendationsByUserId(userId: string): Promise<Recommendation[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(recommendations)
|
|
.where(eq(recommendations.userId, userId))
|
|
.orderBy(desc(recommendations.createdAt))
|
|
.all();
|
|
return results.map(this.mapRecommendation);
|
|
}
|
|
|
|
async getAllRecommendations(): Promise<Recommendation[]> {
|
|
const results = await this.db
|
|
.select()
|
|
.from(recommendations)
|
|
.orderBy(desc(recommendations.createdAt))
|
|
.all();
|
|
return results.map(this.mapRecommendation);
|
|
}
|
|
|
|
async updateRecommendation(
|
|
id: string,
|
|
updates: Partial<Recommendation>,
|
|
): Promise<Recommendation | null> {
|
|
const { id: _, ...updateData } = updates;
|
|
if (Object.keys(updateData).length === 0)
|
|
return this.getRecommendationById(id);
|
|
|
|
await this.db
|
|
.update(recommendations)
|
|
.set(updateData as any)
|
|
.where(eq(recommendations.id, id))
|
|
.run();
|
|
|
|
return this.getRecommendationById(id);
|
|
}
|
|
|
|
async deleteRecommendation(id: string): Promise<boolean> {
|
|
const result = await this.db
|
|
.delete(recommendations)
|
|
.where(eq(recommendations.id, id))
|
|
.run();
|
|
return result.changes > 0;
|
|
}
|
|
|
|
async getRecommendationById(id: string): Promise<Recommendation | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(recommendations)
|
|
.where(eq(recommendations.id, id))
|
|
.get();
|
|
return result ? this.mapRecommendation(result) : null;
|
|
}
|
|
|
|
// Fitness Goals operations
|
|
async createFitnessGoal(
|
|
goalData: Omit<FitnessGoal, "createdAt" | "updatedAt">,
|
|
): Promise<FitnessGoal> {
|
|
const now = new Date();
|
|
const newGoal = {
|
|
...goalData,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
|
|
await this.db.insert(fitnessGoals).values(newGoal as any);
|
|
return newGoal as FitnessGoal;
|
|
}
|
|
|
|
async getFitnessGoalById(id: string): Promise<FitnessGoal | null> {
|
|
const result = await this.db
|
|
.select()
|
|
.from(fitnessGoals)
|
|
.where(eq(fitnessGoals.id, id))
|
|
.get();
|
|
return result ? this.mapFitnessGoal(result) : null;
|
|
}
|
|
|
|
async getFitnessGoalsByUserId(
|
|
userId: string,
|
|
status?: string,
|
|
): Promise<FitnessGoal[]> {
|
|
let query = this.db
|
|
.select()
|
|
.from(fitnessGoals)
|
|
.where(eq(fitnessGoals.userId, userId));
|
|
|
|
if (status) {
|
|
query = this.db
|
|
.select()
|
|
.from(fitnessGoals)
|
|
.where(
|
|
and(
|
|
eq(fitnessGoals.userId, userId),
|
|
eq(fitnessGoals.status, status as any),
|
|
),
|
|
);
|
|
}
|
|
|
|
const results = await query.orderBy(desc(fitnessGoals.createdAt)).all();
|
|
return results.map(this.mapFitnessGoal);
|
|
}
|
|
|
|
async updateFitnessGoal(
|
|
id: string,
|
|
updates: Partial<FitnessGoal>,
|
|
): Promise<FitnessGoal | null> {
|
|
const { id: _, ...updateData } = updates;
|
|
if (Object.keys(updateData).length === 0)
|
|
return this.getFitnessGoalById(id);
|
|
|
|
await this.db
|
|
.update(fitnessGoals)
|
|
.set({ ...updateData, updatedAt: new Date() } as any)
|
|
.where(eq(fitnessGoals.id, id))
|
|
.run();
|
|
|
|
return this.getFitnessGoalById(id);
|
|
}
|
|
|
|
async deleteFitnessGoal(id: string): Promise<boolean> {
|
|
const result = await this.db
|
|
.delete(fitnessGoals)
|
|
.where(eq(fitnessGoals.id, id))
|
|
.run();
|
|
return result.changes > 0;
|
|
}
|
|
|
|
async updateGoalProgress(
|
|
id: string,
|
|
currentValue: number,
|
|
): Promise<FitnessGoal | null> {
|
|
const goal = await this.getFitnessGoalById(id);
|
|
if (!goal) return null;
|
|
|
|
let progress = goal.progress;
|
|
if (goal.targetValue && goal.targetValue > 0) {
|
|
progress = Math.min(100, (currentValue / goal.targetValue) * 100);
|
|
}
|
|
|
|
await this.db
|
|
.update(fitnessGoals)
|
|
.set({ currentValue, progress, updatedAt: new Date() })
|
|
.where(eq(fitnessGoals.id, id))
|
|
.run();
|
|
|
|
return this.getFitnessGoalById(id);
|
|
}
|
|
|
|
async completeGoal(id: string): Promise<FitnessGoal | null> {
|
|
const now = new Date();
|
|
await this.db
|
|
.update(fitnessGoals)
|
|
.set({
|
|
status: "completed",
|
|
progress: 100,
|
|
completedDate: now,
|
|
updatedAt: now,
|
|
})
|
|
.where(eq(fitnessGoals.id, id))
|
|
.run();
|
|
|
|
return this.getFitnessGoalById(id);
|
|
}
|
|
|
|
async getDashboardStats(): Promise<{
|
|
totalUsers: number;
|
|
activeClients: number;
|
|
totalRevenue: number;
|
|
revenueGrowth: number;
|
|
}> {
|
|
// Placeholder implementation as per original sqlite.ts (which didn't implement this either in the viewed snippet,
|
|
// but interface requires it. I'll provide a basic implementation or mock).
|
|
// The original sqlite.ts snippet ended before showing this method, but the interface has it.
|
|
// I'll implement a basic count.
|
|
|
|
const allUsers = await this.db.select().from(users).all();
|
|
const activeClients = await this.db
|
|
.select()
|
|
.from(clients)
|
|
.where(eq(clients.membershipStatus, "active"))
|
|
.all();
|
|
|
|
return {
|
|
totalUsers: allUsers.length,
|
|
activeClients: activeClients.length,
|
|
totalRevenue: 0, // Not tracking payments yet
|
|
revenueGrowth: 0,
|
|
};
|
|
}
|
|
|
|
// Mappers
|
|
private mapUser(row: any): User {
|
|
return {
|
|
...row,
|
|
gymId: (row as any).gymId ?? (row as any).gym_id ?? undefined,
|
|
createdAt: new Date(row.createdAt),
|
|
updatedAt: new Date(row.updatedAt),
|
|
};
|
|
}
|
|
|
|
private mapClient(row: any): Client {
|
|
return {
|
|
...row,
|
|
joinDate: new Date(row.joinDate),
|
|
lastVisit: row.lastVisit ? new Date(row.lastVisit) : undefined,
|
|
};
|
|
}
|
|
|
|
private mapFitnessProfile(row: any): FitnessProfile {
|
|
return {
|
|
...row,
|
|
createdAt: new Date(row.createdAt),
|
|
updatedAt: new Date(row.updatedAt),
|
|
};
|
|
}
|
|
|
|
private mapAttendance(row: any): Attendance {
|
|
return {
|
|
...row,
|
|
checkInTime: new Date(row.checkInTime),
|
|
checkOutTime: row.checkOutTime ? new Date(row.checkOutTime) : undefined,
|
|
createdAt: new Date(row.createdAt),
|
|
};
|
|
}
|
|
|
|
private mapRecommendation(row: any): Recommendation {
|
|
return {
|
|
...row,
|
|
createdAt: new Date(row.createdAt),
|
|
approvedAt: row.approvedAt ? new Date(row.approvedAt) : undefined,
|
|
};
|
|
}
|
|
|
|
private mapFitnessGoal(row: any): FitnessGoal {
|
|
return {
|
|
...row,
|
|
startDate: new Date(row.startDate),
|
|
targetDate: row.targetDate ? new Date(row.targetDate) : undefined,
|
|
completedDate: row.completedDate
|
|
? new Date(row.completedDate)
|
|
: undefined,
|
|
createdAt: new Date(row.createdAt),
|
|
updatedAt: new Date(row.updatedAt),
|
|
};
|
|
}
|
|
}
|