user report gtound work
This commit is contained in:
1 parent
1be2de05fa
commit
74f0d0dbed
42 files changed
+8127
-5
No files matched your search
@@ -5,6 +5,7 @@ import {
|
||||
real,
|
||||
index,
|
||||
unique,
|
||||
uniqueIndex,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const users = sqliteTable(
|
||||
@@ -380,6 +381,193 @@ export const recommendations = sqliteTable(
|
||||
}),
|
||||
);
|
||||
|
||||
// Daily nutrition tracking table
|
||||
export const dailyNutrition = sqliteTable(
|
||||
"daily_nutrition",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
date: text("date").notNull(), // YYYY-MM-DD format
|
||||
totalCalories: real("total_calories").notNull().default(0),
|
||||
calorieGoal: real("calorie_goal").notNull().default(2000),
|
||||
meals: text("meals", { mode: "json" }).$type<
|
||||
Array<{
|
||||
type: "breakfast" | "lunch" | "dinner" | "snack";
|
||||
name: string;
|
||||
calories: number;
|
||||
time?: string;
|
||||
}>
|
||||
>(),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("daily_nutrition_user_id_idx").on(table.userId),
|
||||
dateIdx: index("daily_nutrition_date_idx").on(table.date),
|
||||
userDateIdx: uniqueIndex("daily_nutrition_user_date_idx").on(
|
||||
table.userId,
|
||||
table.date,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Individual meal entries table
|
||||
export const mealEntries = sqliteTable(
|
||||
"meal_entries",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
dailyNutritionId: text("daily_nutrition_id").references(
|
||||
() => dailyNutrition.id,
|
||||
{ onDelete: "cascade" },
|
||||
),
|
||||
mealType: text("meal_type", {
|
||||
enum: ["breakfast", "lunch", "dinner", "snack"],
|
||||
}).notNull(),
|
||||
foodName: text("food_name").notNull(),
|
||||
calories: real("calories").notNull(),
|
||||
protein: real("protein"), // grams (optional)
|
||||
carbs: real("carbs"), // grams (optional)
|
||||
fats: real("fats"), // grams (optional)
|
||||
timestamp: integer("timestamp", { mode: "timestamp" }).notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("meal_entries_user_id_idx").on(table.userId),
|
||||
timestampIdx: index("meal_entries_timestamp_idx").on(table.timestamp),
|
||||
dailyNutritionIdIdx: index("meal_entries_daily_nutrition_id_idx").on(
|
||||
table.dailyNutritionId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Daily hydration tracking table
|
||||
export const dailyHydration = sqliteTable(
|
||||
"daily_hydration",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
date: text("date").notNull(), // YYYY-MM-DD format
|
||||
totalWater: real("total_water").notNull().default(0), // ml
|
||||
waterGoal: real("water_goal").notNull().default(2000), // ml
|
||||
entries: text("entries", { mode: "json" }).$type<
|
||||
Array<{ amount: number; time: string }>
|
||||
>(),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("daily_hydration_user_id_idx").on(table.userId),
|
||||
dateIdx: index("daily_hydration_date_idx").on(table.date),
|
||||
userDateIdx: uniqueIndex("daily_hydration_user_date_idx").on(
|
||||
table.userId,
|
||||
table.date,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Fitness profile history tracking table
|
||||
export const fitnessProfileHistory = sqliteTable(
|
||||
"fitness_profile_history",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
fitnessProfileId: text("fitness_profile_id")
|
||||
.notNull()
|
||||
.references(() => fitnessProfiles.id, { onDelete: "cascade" }),
|
||||
changeType: text("change_type", {
|
||||
enum: [
|
||||
"weight",
|
||||
"height",
|
||||
"age",
|
||||
"activity_level",
|
||||
"goals",
|
||||
"medical",
|
||||
"other",
|
||||
],
|
||||
}).notNull(),
|
||||
fieldName: text("field_name").notNull(), // "weight", "height", etc.
|
||||
previousValue: text("previous_value"), // JSON string for flexibility
|
||||
newValue: text("new_value"), // JSON string
|
||||
changedAt: integer("changed_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("fitness_profile_history_user_id_idx").on(table.userId),
|
||||
changedAtIdx: index("fitness_profile_history_changed_at_idx").on(
|
||||
table.changedAt,
|
||||
),
|
||||
userChangedIdx: index("fitness_profile_history_user_changed_idx").on(
|
||||
table.userId,
|
||||
table.changedAt,
|
||||
),
|
||||
changeTypeIdx: index("fitness_profile_history_change_type_idx").on(
|
||||
table.changeType,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Trainer-client assignment table
|
||||
export const trainerClientAssignments = sqliteTable(
|
||||
"trainer_client_assignments",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
trainerId: text("trainer_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
clientId: text("client_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
assignedAt: integer("assigned_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
assignedBy: text("assigned_by").references(() => users.id),
|
||||
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
trainerIdx: index("trainer_client_assignments_trainer_idx").on(
|
||||
table.trainerId,
|
||||
),
|
||||
clientIdx: index("trainer_client_assignments_client_idx").on(
|
||||
table.clientId,
|
||||
),
|
||||
trainerClientIdx: uniqueIndex(
|
||||
"trainer_client_assignments_trainer_client_idx",
|
||||
).on(table.trainerId, table.clientId),
|
||||
isActiveIdx: index("trainer_client_assignments_is_active_idx").on(
|
||||
table.isActive,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type NewUser = typeof users.$inferInsert;
|
||||
export type Client = typeof clients.$inferSelect;
|
||||
@@ -396,3 +584,16 @@ export type FitnessGoal = typeof fitnessGoals.$inferSelect;
|
||||
export type NewFitnessGoal = typeof fitnessGoals.$inferInsert;
|
||||
export type Recommendation = typeof recommendations.$inferSelect;
|
||||
export type NewRecommendation = typeof recommendations.$inferInsert;
|
||||
export type DailyNutrition = typeof dailyNutrition.$inferSelect;
|
||||
export type NewDailyNutrition = typeof dailyNutrition.$inferInsert;
|
||||
export type MealEntry = typeof mealEntries.$inferSelect;
|
||||
export type NewMealEntry = typeof mealEntries.$inferInsert;
|
||||
export type DailyHydration = typeof dailyHydration.$inferSelect;
|
||||
export type NewDailyHydration = typeof dailyHydration.$inferInsert;
|
||||
export type FitnessProfileHistory = typeof fitnessProfileHistory.$inferSelect;
|
||||
export type NewFitnessProfileHistory =
|
||||
typeof fitnessProfileHistory.$inferInsert;
|
||||
export type TrainerClientAssignment =
|
||||
typeof trainerClientAssignments.$inferSelect;
|
||||
export type NewTrainerClientAssignment =
|
||||
typeof trainerClientAssignments.$inferInsert;
|
||||
@@ -160,3 +160,158 @@ export interface TrainerClient {
|
||||
gymId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface DailyNutrition {
|
||||
id: string;
|
||||
userId: string;
|
||||
date: string; // YYYY-MM-DD
|
||||
totalCalories: number;
|
||||
calorieGoal: number;
|
||||
meals?: Array<{
|
||||
type: "breakfast" | "lunch" | "dinner" | "snack";
|
||||
name: string;
|
||||
calories: number;
|
||||
time?: string;
|
||||
}>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface MealEntry {
|
||||
id: string;
|
||||
userId: string;
|
||||
dailyNutritionId?: string;
|
||||
mealType: "breakfast" | "lunch" | "dinner" | "snack";
|
||||
foodName: string;
|
||||
calories: number;
|
||||
protein?: number; // grams
|
||||
carbs?: number; // grams
|
||||
fats?: number; // grams
|
||||
timestamp: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface DailyHydration {
|
||||
id: string;
|
||||
userId: string;
|
||||
date: string; // YYYY-MM-DD
|
||||
totalWater: number; // ml
|
||||
waterGoal: number; // ml
|
||||
entries?: Array<{
|
||||
amount: number;
|
||||
time: string;
|
||||
}>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface FitnessProfileHistory {
|
||||
id: string;
|
||||
userId: string;
|
||||
fitnessProfileId: string;
|
||||
changeType:
|
||||
| "weight"
|
||||
| "height"
|
||||
| "age"
|
||||
| "activity_level"
|
||||
| "goals"
|
||||
| "medical"
|
||||
| "other";
|
||||
fieldName: string;
|
||||
previousValue?: string;
|
||||
newValue?: string;
|
||||
changedAt: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface TrainerClientAssignment {
|
||||
id: string;
|
||||
trainerId: string;
|
||||
clientId: string;
|
||||
assignedAt: Date;
|
||||
assignedBy?: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// User Report Types
|
||||
export interface WeeklyCheckInStats {
|
||||
weekStart: string; // ISO 8601 date (YYYY-MM-DD)
|
||||
weekEnd: string; // ISO 8601 date (YYYY-MM-DD)
|
||||
totalCheckIns: number;
|
||||
totalTimeMinutes: number;
|
||||
averageDurationMinutes: number;
|
||||
checkInsByType: {
|
||||
type: AttendanceType;
|
||||
count: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface NutritionSummary {
|
||||
date: string; // YYYY-MM-DD
|
||||
totalCalories: number;
|
||||
calorieGoal: number;
|
||||
caloriesDelta: number; // totalCalories - calorieGoal
|
||||
mealsCount: number;
|
||||
}
|
||||
|
||||
export interface HydrationSummary {
|
||||
date: string; // YYYY-MM-DD
|
||||
totalWater: number; // ml
|
||||
waterGoal: number; // ml
|
||||
hydrationPercentage: number; // (totalWater / waterGoal) * 100
|
||||
}
|
||||
|
||||
export interface GoalSummary {
|
||||
active: FitnessGoal[];
|
||||
completed: FitnessGoal[];
|
||||
totalActive: number;
|
||||
totalCompleted: number;
|
||||
averageProgress: number; // Average progress of active goals
|
||||
}
|
||||
|
||||
export interface ProfileChangeSummary {
|
||||
changeType: FitnessProfileHistory["changeType"];
|
||||
fieldName: string;
|
||||
previousValue?: string;
|
||||
newValue?: string;
|
||||
changedAt: Date;
|
||||
}
|
||||
|
||||
export interface RecommendationSummary {
|
||||
accepted: Recommendation[];
|
||||
rejected: Recommendation[];
|
||||
pending: Recommendation[];
|
||||
totalAccepted: number;
|
||||
totalRejected: number;
|
||||
totalPending: number;
|
||||
}
|
||||
|
||||
export interface UserReport {
|
||||
userId: string;
|
||||
user: User;
|
||||
client?: Client | null;
|
||||
fitnessProfile?: FitnessProfile | null;
|
||||
reportPeriod: {
|
||||
startDate: string; // ISO 8601
|
||||
endDate: string; // ISO 8601
|
||||
};
|
||||
weeklyCheckIns: WeeklyCheckInStats[];
|
||||
nutrition: {
|
||||
dailySummaries: NutritionSummary[];
|
||||
averageDailyCalories: number;
|
||||
totalDays: number;
|
||||
daysMetGoal: number; // Days where calories were within ±10% of goal
|
||||
};
|
||||
hydration: {
|
||||
dailySummaries: HydrationSummary[];
|
||||
averageDailyWater: number;
|
||||
totalDays: number;
|
||||
daysMetGoal: number; // Days where water goal was met
|
||||
};
|
||||
goals: GoalSummary;
|
||||
profileHistory: ProfileChangeSummary[];
|
||||
recommendations: RecommendationSummary;
|
||||
generatedAt: Date;
|
||||
}
|
||||
Reference in new issue
Block a user