phase 1-6 implemented
This commit is contained in:
1 parent
22c274bb83
commit
2c4927478a
73 files changed
+9702
-2354
No files matched your search
@@ -0,0 +1,174 @@
|
||||
CREATE TABLE `attendance` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`check_in_time` integer NOT NULL,
|
||||
`check_out_time` integer,
|
||||
`type` text DEFAULT 'gym' NOT NULL,
|
||||
`notes` text,
|
||||
`created_at` integer NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `attendance_user_id_idx` ON `attendance` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `attendance_check_in_time_idx` ON `attendance` (`check_in_time`);--> statement-breakpoint
|
||||
CREATE INDEX `attendance_user_check_in_idx` ON `attendance` (`user_id`,`check_in_time`);--> statement-breakpoint
|
||||
CREATE TABLE `clients` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`membership_type` text DEFAULT 'basic' NOT NULL,
|
||||
`membership_status` text DEFAULT 'active' NOT NULL,
|
||||
`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 UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `clients_user_id_unique` ON `clients` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `clients_user_id_idx` ON `clients` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `clients_membership_status_idx` ON `clients` (`membership_status`);--> statement-breakpoint
|
||||
CREATE TABLE `fitness_goals` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`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 DEFAULT 'active' NOT NULL,
|
||||
`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 UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`fitness_profile_id`) REFERENCES `fitness_profiles`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `fitness_goals_user_id_idx` ON `fitness_goals` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `fitness_goals_status_idx` ON `fitness_goals` (`status`);--> statement-breakpoint
|
||||
CREATE INDEX `fitness_goals_user_status_idx` ON `fitness_goals` (`user_id`,`status`);--> statement-breakpoint
|
||||
CREATE TABLE `fitness_profiles` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`height` real,
|
||||
`weight` real,
|
||||
`age` integer,
|
||||
`gender` text,
|
||||
`fitness_goals` text,
|
||||
`activity_level` 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 UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `fitness_profiles_user_id_unique` ON `fitness_profiles` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `fitness_profiles_user_id_idx` ON `fitness_profiles` (`user_id`);--> statement-breakpoint
|
||||
CREATE TABLE `gyms` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`location` text,
|
||||
`status` text DEFAULT 'active' NOT NULL,
|
||||
`admin_user_id` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`updated_at` integer NOT NULL,
|
||||
FOREIGN KEY (`admin_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `gyms_admin_user_id_idx` ON `gyms` (`admin_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `gyms_status_idx` ON `gyms` (`status`);--> statement-breakpoint
|
||||
CREATE TABLE `notifications` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`message` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`read` integer DEFAULT false NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `notifications_user_id_idx` ON `notifications` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `notifications_read_idx` ON `notifications` (`read`);--> statement-breakpoint
|
||||
CREATE INDEX `notifications_user_read_idx` ON `notifications` (`user_id`,`read`);--> statement-breakpoint
|
||||
CREATE TABLE `payments` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`client_id` text NOT NULL,
|
||||
`amount` real NOT NULL,
|
||||
`currency` text DEFAULT 'USD' NOT NULL,
|
||||
`status` text DEFAULT 'pending' NOT NULL,
|
||||
`payment_method` text NOT NULL,
|
||||
`due_date` integer NOT NULL,
|
||||
`paid_at` integer,
|
||||
`description` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`updated_at` integer NOT NULL,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `payments_client_id_idx` ON `payments` (`client_id`);--> statement-breakpoint
|
||||
CREATE INDEX `payments_status_idx` ON `payments` (`status`);--> statement-breakpoint
|
||||
CREATE INDEX `payments_due_date_idx` ON `payments` (`due_date`);--> statement-breakpoint
|
||||
CREATE TABLE `recommendations` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`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 DEFAULT 'pending' NOT NULL,
|
||||
`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 UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`fitness_profile_id`) REFERENCES `fitness_profiles`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `recommendations_user_id_idx` ON `recommendations` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `recommendations_status_idx` ON `recommendations` (`status`);--> statement-breakpoint
|
||||
CREATE INDEX `recommendations_fitness_profile_id_idx` ON `recommendations` (`fitness_profile_id`);--> statement-breakpoint
|
||||
CREATE TABLE `trainer_clients` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`trainer_user_id` text NOT NULL,
|
||||
`client_user_id` text NOT NULL,
|
||||
`gym_id` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
FOREIGN KEY (`trainer_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`client_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`gym_id`) REFERENCES `gyms`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `trainer_clients_trainer_id_idx` ON `trainer_clients` (`trainer_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `trainer_clients_client_id_idx` ON `trainer_clients` (`client_user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `trainer_clients_gym_id_idx` ON `trainer_clients` (`gym_id`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `trainer_client_unique` ON `trainer_clients` (`trainer_user_id`,`client_user_id`);--> statement-breakpoint
|
||||
CREATE TABLE `users` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`first_name` text NOT NULL,
|
||||
`last_name` text NOT NULL,
|
||||
`password` text,
|
||||
`role` text DEFAULT 'client' NOT NULL,
|
||||
`phone` text,
|
||||
`gym_id` text,
|
||||
`created_at` integer NOT NULL,
|
||||
`updated_at` integer NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
|
||||
CREATE INDEX `users_email_idx` ON `users` (`email`);--> statement-breakpoint
|
||||
CREATE INDEX `users_gym_id_idx` ON `users` (`gym_id`);--> statement-breakpoint
|
||||
CREATE INDEX `users_role_idx` ON `users` (`role`);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1773106919464,
|
||||
"tag": "0000_rich_rictor",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,7 +7,9 @@
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db:push": "drizzle-kit push:sqlite",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -19,4 +21,4 @@
|
||||
"typescript": "^5.9.3",
|
||||
"drizzle-kit": "^0.31.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
+359
-251
@@ -1,271 +1,379 @@
|
||||
import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
|
||||
import {
|
||||
sqliteTable,
|
||||
text,
|
||||
integer,
|
||||
real,
|
||||
index,
|
||||
unique,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: text("id").primaryKey(),
|
||||
email: text("email").notNull().unique(),
|
||||
firstName: text("first_name").notNull(),
|
||||
lastName: text("last_name").notNull(),
|
||||
password: text("password"), // Optional - Clerk handles authentication
|
||||
role: text("role", {
|
||||
enum: ["superAdmin", "admin", "trainer", "client", "generalUser"],
|
||||
})
|
||||
.notNull()
|
||||
.default("client"),
|
||||
phone: text("phone"),
|
||||
// Remove direct foreign key reference to avoid circular dependency; validate at application level
|
||||
gymId: text("gym_id"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
export const gyms = sqliteTable("gyms", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
location: text("location"),
|
||||
status: text("status", { enum: ["active", "inactive"] })
|
||||
.notNull()
|
||||
.default("active"),
|
||||
adminUserId: text("admin_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
export const clients = sqliteTable("clients", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
membershipType: text("membership_type", { enum: ["basic", "premium", "vip"] })
|
||||
.notNull()
|
||||
.default("basic"),
|
||||
membershipStatus: text("membership_status", {
|
||||
enum: ["active", "inactive", "suspended"],
|
||||
})
|
||||
.notNull()
|
||||
.default("active"),
|
||||
joinDate: integer("join_date", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
lastVisit: integer("last_visit", { mode: "timestamp" }),
|
||||
emergencyContactName: text("emergency_contact_name"),
|
||||
emergencyContactPhone: text("emergency_contact_phone"),
|
||||
emergencyContactRelationship: text("emergency_contact_relationship"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
export const payments = sqliteTable("payments", {
|
||||
id: text("id").primaryKey(),
|
||||
clientId: text("client_id")
|
||||
.notNull()
|
||||
.references(() => clients.id, { onDelete: "cascade" }),
|
||||
amount: real("amount").notNull(),
|
||||
currency: text("currency").notNull().default("USD"),
|
||||
status: text("status", {
|
||||
enum: ["pending", "completed", "failed", "refunded"],
|
||||
})
|
||||
.notNull()
|
||||
.default("pending"),
|
||||
paymentMethod: text("payment_method", {
|
||||
enum: ["cash", "card", "bank_transfer"],
|
||||
}).notNull(),
|
||||
dueDate: integer("due_date", { mode: "timestamp" }).notNull(),
|
||||
paidAt: integer("paid_at", { mode: "timestamp" }),
|
||||
description: text("description").notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
export const attendance = sqliteTable("attendance", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
checkInTime: integer("check_in_time", { mode: "timestamp" }).notNull(),
|
||||
checkOutTime: integer("check_out_time", { mode: "timestamp" }),
|
||||
type: text("type", { enum: ["gym", "class", "personal_training"] })
|
||||
.notNull()
|
||||
.default("gym"),
|
||||
notes: text("notes"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
export const notifications = sqliteTable("notifications", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull(),
|
||||
message: text("message").notNull(),
|
||||
type: text("type", {
|
||||
enum: ["payment_reminder", "attendance", "promotion", "system"],
|
||||
}).notNull(),
|
||||
read: integer("read", { mode: "boolean" }).notNull().default(false),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
|
||||
export const fitnessProfiles = sqliteTable("fitness_profiles", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.unique()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
height: real("height"), // in cm
|
||||
weight: real("weight"), // in kg
|
||||
age: integer("age"),
|
||||
gender: text("gender", {
|
||||
enum: ["male", "female", "other", "prefer_not_to_say"],
|
||||
export const users = sqliteTable(
|
||||
"users",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
email: text("email").notNull().unique(),
|
||||
firstName: text("first_name").notNull(),
|
||||
lastName: text("last_name").notNull(),
|
||||
password: text("password"), // Optional - Clerk handles authentication
|
||||
role: text("role", {
|
||||
enum: ["superAdmin", "admin", "trainer", "client"],
|
||||
})
|
||||
.notNull()
|
||||
.default("client"),
|
||||
phone: text("phone"),
|
||||
gymId: text("gym_id"), // FK reference added after gyms table
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
emailIdx: index("users_email_idx").on(table.email),
|
||||
gymIdIdx: index("users_gym_id_idx").on(table.gymId),
|
||||
roleIdx: index("users_role_idx").on(table.role),
|
||||
}),
|
||||
fitnessGoals: text("fitness_goals", { mode: "json" }).$type<string[]>(),
|
||||
activityLevel: text("activity_level", {
|
||||
enum: [
|
||||
"sedentary",
|
||||
"lightly_active",
|
||||
"moderately_active",
|
||||
"very_active",
|
||||
"extremely_active",
|
||||
],
|
||||
);
|
||||
|
||||
export const gyms = sqliteTable(
|
||||
"gyms",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
location: text("location"),
|
||||
status: text("status", { enum: ["active", "inactive"] })
|
||||
.notNull()
|
||||
.default("active"),
|
||||
adminUserId: text("admin_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
adminUserIdIdx: index("gyms_admin_user_id_idx").on(table.adminUserId),
|
||||
statusIdx: index("gyms_status_idx").on(table.status),
|
||||
}),
|
||||
medicalConditions: text("medical_conditions"),
|
||||
allergies: text("allergies"),
|
||||
injuries: text("injuries"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
);
|
||||
|
||||
export const fitnessGoals = sqliteTable("fitness_goals", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
fitnessProfileId: text("fitness_profile_id").references(
|
||||
() => fitnessProfiles.id,
|
||||
{ onDelete: "cascade" },
|
||||
),
|
||||
export const clients = sqliteTable(
|
||||
"clients",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.unique()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
membershipType: text("membership_type", {
|
||||
enum: ["basic", "premium", "vip"],
|
||||
})
|
||||
.notNull()
|
||||
.default("basic"),
|
||||
membershipStatus: text("membership_status", {
|
||||
enum: ["active", "inactive", "suspended"],
|
||||
})
|
||||
.notNull()
|
||||
.default("active"),
|
||||
joinDate: integer("join_date", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
lastVisit: integer("last_visit", { mode: "timestamp" }),
|
||||
emergencyContactName: text("emergency_contact_name"),
|
||||
emergencyContactPhone: text("emergency_contact_phone"),
|
||||
emergencyContactRelationship: text("emergency_contact_relationship"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("clients_user_id_idx").on(table.userId),
|
||||
membershipStatusIdx: index("clients_membership_status_idx").on(
|
||||
table.membershipStatus,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Goal details
|
||||
goalType: text("goal_type", {
|
||||
enum: [
|
||||
"weight_target",
|
||||
"strength_milestone",
|
||||
"endurance_target",
|
||||
"flexibility_goal",
|
||||
"habit_building",
|
||||
"custom",
|
||||
],
|
||||
}).notNull(),
|
||||
export const payments = sqliteTable(
|
||||
"payments",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
clientId: text("client_id")
|
||||
.notNull()
|
||||
.references(() => clients.id, { onDelete: "cascade" }),
|
||||
amount: real("amount").notNull(),
|
||||
currency: text("currency").notNull().default("USD"),
|
||||
status: text("status", {
|
||||
enum: ["pending", "completed", "failed", "refunded"],
|
||||
})
|
||||
.notNull()
|
||||
.default("pending"),
|
||||
paymentMethod: text("payment_method", {
|
||||
enum: ["cash", "card", "bank_transfer"],
|
||||
}).notNull(),
|
||||
dueDate: integer("due_date", { mode: "timestamp" }).notNull(),
|
||||
paidAt: integer("paid_at", { mode: "timestamp" }),
|
||||
description: text("description").notNull(),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
clientIdIdx: index("payments_client_id_idx").on(table.clientId),
|
||||
statusIdx: index("payments_status_idx").on(table.status),
|
||||
dueDateIdx: index("payments_due_date_idx").on(table.dueDate),
|
||||
}),
|
||||
);
|
||||
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
export const attendance = sqliteTable(
|
||||
"attendance",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
checkInTime: integer("check_in_time", { mode: "timestamp" }).notNull(),
|
||||
checkOutTime: integer("check_out_time", { mode: "timestamp" }),
|
||||
type: text("type", { enum: ["gym", "class", "personal_training"] })
|
||||
.notNull()
|
||||
.default("gym"),
|
||||
notes: text("notes"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("attendance_user_id_idx").on(table.userId),
|
||||
checkInTimeIdx: index("attendance_check_in_time_idx").on(table.checkInTime),
|
||||
// Composite index for common query: get user's attendance history
|
||||
userCheckInIdx: index("attendance_user_check_in_idx").on(
|
||||
table.userId,
|
||||
table.checkInTime,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Measurable targets
|
||||
targetValue: real("target_value"), // e.g., 70 (kg), 100 (kg bench press)
|
||||
currentValue: real("current_value"), // Current progress
|
||||
unit: text("unit"), // kg, km, reps, etc.
|
||||
export const notifications = sqliteTable(
|
||||
"notifications",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull(),
|
||||
message: text("message").notNull(),
|
||||
type: text("type", {
|
||||
enum: ["payment_reminder", "attendance", "promotion", "system"],
|
||||
}).notNull(),
|
||||
read: integer("read", { mode: "boolean" }).notNull().default(false),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("notifications_user_id_idx").on(table.userId),
|
||||
readIdx: index("notifications_read_idx").on(table.read),
|
||||
// Composite index for common query: get user's unread notifications
|
||||
userReadIdx: index("notifications_user_read_idx").on(
|
||||
table.userId,
|
||||
table.read,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Timeline
|
||||
startDate: integer("start_date", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
targetDate: integer("target_date", { mode: "timestamp" }),
|
||||
completedDate: integer("completed_date", { mode: "timestamp" }),
|
||||
export const fitnessProfiles = sqliteTable(
|
||||
"fitness_profiles",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.unique()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
height: real("height"), // in cm
|
||||
weight: real("weight"), // in kg
|
||||
age: integer("age"),
|
||||
gender: text("gender", {
|
||||
enum: ["male", "female", "other", "prefer_not_to_say"],
|
||||
}),
|
||||
fitnessGoals: text("fitness_goals", { mode: "json" }).$type<string[]>(),
|
||||
activityLevel: text("activity_level", {
|
||||
enum: [
|
||||
"sedentary",
|
||||
"lightly_active",
|
||||
"moderately_active",
|
||||
"very_active",
|
||||
"extremely_active",
|
||||
],
|
||||
}),
|
||||
medicalConditions: text("medical_conditions"),
|
||||
allergies: text("allergies"),
|
||||
injuries: text("injuries"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("fitness_profiles_user_id_idx").on(table.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
// Status tracking
|
||||
status: text("status", {
|
||||
enum: ["active", "completed", "abandoned", "paused"],
|
||||
})
|
||||
.notNull()
|
||||
.default("active"),
|
||||
export const fitnessGoals = sqliteTable(
|
||||
"fitness_goals",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
fitnessProfileId: text("fitness_profile_id").references(
|
||||
() => fitnessProfiles.id,
|
||||
{ onDelete: "cascade" },
|
||||
),
|
||||
|
||||
progress: real("progress").default(0), // 0-100 percentage
|
||||
// Goal details
|
||||
goalType: text("goal_type", {
|
||||
enum: [
|
||||
"weight_target",
|
||||
"strength_milestone",
|
||||
"endurance_target",
|
||||
"flexibility_goal",
|
||||
"habit_building",
|
||||
"custom",
|
||||
],
|
||||
}).notNull(),
|
||||
|
||||
// Metadata
|
||||
priority: text("priority", {
|
||||
enum: ["low", "medium", "high"],
|
||||
}).default("medium"),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
|
||||
notes: text("notes"),
|
||||
// Measurable targets
|
||||
targetValue: real("target_value"), // e.g., 70 (kg), 100 (kg bench press)
|
||||
currentValue: real("current_value"), // Current progress
|
||||
unit: text("unit"), // kg, km, reps, etc.
|
||||
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
// Timeline
|
||||
startDate: integer("start_date", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
targetDate: integer("target_date", { mode: "timestamp" }),
|
||||
completedDate: integer("completed_date", { mode: "timestamp" }),
|
||||
|
||||
// Status tracking
|
||||
status: text("status", {
|
||||
enum: ["active", "completed", "abandoned", "paused"],
|
||||
})
|
||||
.notNull()
|
||||
.default("active"),
|
||||
|
||||
progress: real("progress").default(0), // 0-100 percentage
|
||||
|
||||
// Metadata
|
||||
priority: text("priority", {
|
||||
enum: ["low", "medium", "high"],
|
||||
}).default("medium"),
|
||||
|
||||
notes: text("notes"),
|
||||
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("fitness_goals_user_id_idx").on(table.userId),
|
||||
statusIdx: index("fitness_goals_status_idx").on(table.status),
|
||||
// Composite index for common query: get user's active goals
|
||||
userStatusIdx: index("fitness_goals_user_status_idx").on(
|
||||
table.userId,
|
||||
table.status,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Removed local invitations table; Clerk invitations are the source of truth
|
||||
|
||||
export const trainerClients = sqliteTable("trainer_clients", {
|
||||
id: text("id").primaryKey(),
|
||||
trainerUserId: text("trainer_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
clientUserId: text("client_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
gymId: text("gym_id")
|
||||
.notNull()
|
||||
.references(() => gyms.id, { onDelete: "cascade" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
export const trainerClients = sqliteTable(
|
||||
"trainer_clients",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
trainerUserId: text("trainer_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
clientUserId: text("client_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
gymId: text("gym_id")
|
||||
.notNull()
|
||||
.references(() => gyms.id, { onDelete: "cascade" }),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
trainerIdIdx: index("trainer_clients_trainer_id_idx").on(
|
||||
table.trainerUserId,
|
||||
),
|
||||
clientIdIdx: index("trainer_clients_client_id_idx").on(table.clientUserId),
|
||||
gymIdIdx: index("trainer_clients_gym_id_idx").on(table.gymId),
|
||||
// Composite unique constraint: prevent duplicate trainer-client assignments
|
||||
trainerClientUnique: unique("trainer_client_unique").on(
|
||||
table.trainerUserId,
|
||||
table.clientUserId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const recommendations = sqliteTable("recommendations", {
|
||||
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" }),
|
||||
recommendationText: text("recommendation_text").notNull(),
|
||||
activityPlan: text("activity_plan").notNull(),
|
||||
dietPlan: text("diet_plan").notNull(),
|
||||
status: text("status", {
|
||||
enum: ["pending", "approved", "rejected"],
|
||||
})
|
||||
.notNull()
|
||||
.default("pending"),
|
||||
generatedAt: integer("generated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
approvedAt: integer("approved_at", { mode: "timestamp" }),
|
||||
approvedBy: text("approved_by"), // User ID of admin/trainer
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
});
|
||||
export const recommendations = sqliteTable(
|
||||
"recommendations",
|
||||
{
|
||||
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" }),
|
||||
recommendationText: text("recommendation_text").notNull(),
|
||||
activityPlan: text("activity_plan").notNull(),
|
||||
dietPlan: text("diet_plan").notNull(),
|
||||
status: text("status", {
|
||||
enum: ["pending", "approved", "rejected"],
|
||||
})
|
||||
.notNull()
|
||||
.default("pending"),
|
||||
generatedAt: integer("generated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
approvedAt: integer("approved_at", { mode: "timestamp" }),
|
||||
approvedBy: text("approved_by"), // User ID of admin/trainer
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index("recommendations_user_id_idx").on(table.userId),
|
||||
statusIdx: index("recommendations_status_idx").on(table.status),
|
||||
fitnessProfileIdIdx: index("recommendations_fitness_profile_id_idx").on(
|
||||
table.fitnessProfileId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type NewUser = typeof users.$inferInsert;
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Shared constants and enums used across the FitAI application
|
||||
* Single source of truth for all enum values
|
||||
*/
|
||||
|
||||
// User Roles
|
||||
export const USER_ROLES = ["superAdmin", "admin", "trainer", "client"] as const;
|
||||
export type UserRole = (typeof USER_ROLES)[number];
|
||||
|
||||
// Export UserRole for easier imports
|
||||
export type { UserRole as UserRoleType };
|
||||
|
||||
// Gym Status
|
||||
export const GYM_STATUSES = ["active", "inactive"] as const;
|
||||
export type GymStatus = (typeof GYM_STATUSES)[number];
|
||||
|
||||
// Membership Types
|
||||
export const MEMBERSHIP_TYPES = ["basic", "premium", "vip"] as const;
|
||||
export type MembershipType = (typeof MEMBERSHIP_TYPES)[number];
|
||||
|
||||
// Membership Statuses
|
||||
export const MEMBERSHIP_STATUSES = ["active", "inactive", "suspended"] as const;
|
||||
export type MembershipStatus = (typeof MEMBERSHIP_STATUSES)[number];
|
||||
|
||||
// Payment Statuses
|
||||
export const PAYMENT_STATUSES = [
|
||||
"pending",
|
||||
"completed",
|
||||
"failed",
|
||||
"refunded",
|
||||
] as const;
|
||||
export type PaymentStatus = (typeof PAYMENT_STATUSES)[number];
|
||||
|
||||
// Payment Methods
|
||||
export const PAYMENT_METHODS = ["cash", "card", "bank_transfer"] as const;
|
||||
export type PaymentMethod = (typeof PAYMENT_METHODS)[number];
|
||||
|
||||
// Attendance Types
|
||||
export const ATTENDANCE_TYPES = ["gym", "class", "personal_training"] as const;
|
||||
export type AttendanceType = (typeof ATTENDANCE_TYPES)[number];
|
||||
|
||||
// Notification Types
|
||||
export const NOTIFICATION_TYPES = [
|
||||
"payment_reminder",
|
||||
"attendance",
|
||||
"promotion",
|
||||
"system",
|
||||
] as const;
|
||||
export type NotificationType = (typeof NOTIFICATION_TYPES)[number];
|
||||
|
||||
// Genders
|
||||
export const GENDERS = [
|
||||
"male",
|
||||
"female",
|
||||
"other",
|
||||
"prefer_not_to_say",
|
||||
] as const;
|
||||
export type Gender = (typeof GENDERS)[number];
|
||||
|
||||
// Activity Levels
|
||||
export const ACTIVITY_LEVELS = [
|
||||
"sedentary",
|
||||
"lightly_active",
|
||||
"moderately_active",
|
||||
"very_active",
|
||||
"extremely_active",
|
||||
] as const;
|
||||
export type ActivityLevel = (typeof ACTIVITY_LEVELS)[number];
|
||||
|
||||
// Goal Types
|
||||
export const GOAL_TYPES = [
|
||||
"weight_target",
|
||||
"strength_milestone",
|
||||
"endurance_target",
|
||||
"flexibility_goal",
|
||||
"habit_building",
|
||||
"custom",
|
||||
] as const;
|
||||
export type GoalType = (typeof GOAL_TYPES)[number];
|
||||
|
||||
// Goal Statuses
|
||||
export const GOAL_STATUSES = [
|
||||
"active",
|
||||
"completed",
|
||||
"abandoned",
|
||||
"paused",
|
||||
] as const;
|
||||
export type GoalStatus = (typeof GOAL_STATUSES)[number];
|
||||
|
||||
// Priority Levels
|
||||
export const PRIORITY_LEVELS = ["low", "medium", "high"] as const;
|
||||
export type PriorityLevel = (typeof PRIORITY_LEVELS)[number];
|
||||
|
||||
// Recommendation Statuses
|
||||
export const RECOMMENDATION_STATUSES = [
|
||||
"pending",
|
||||
"approved",
|
||||
"rejected",
|
||||
] as const;
|
||||
export type RecommendationStatus = (typeof RECOMMENDATION_STATUSES)[number];
|
||||
|
||||
// Helper functions to check enum values
|
||||
export function isValidUserRole(role: string): role is UserRole {
|
||||
return USER_ROLES.includes(role as UserRole);
|
||||
}
|
||||
|
||||
export function isValidMembershipStatus(
|
||||
status: string,
|
||||
): status is MembershipStatus {
|
||||
return MEMBERSHIP_STATUSES.includes(status as MembershipStatus);
|
||||
}
|
||||
|
||||
export function isValidPaymentStatus(status: string): status is PaymentStatus {
|
||||
return PAYMENT_STATUSES.includes(status as PaymentStatus);
|
||||
}
|
||||
|
||||
export function isValidGoalStatus(status: string): status is GoalStatus {
|
||||
return GOAL_STATUSES.includes(status as GoalStatus);
|
||||
}
|
||||
|
||||
// Display labels for UI
|
||||
export const USER_ROLE_LABELS: Record<UserRole, string> = {
|
||||
superAdmin: "Super Admin",
|
||||
admin: "Admin",
|
||||
trainer: "Trainer",
|
||||
client: "Client",
|
||||
};
|
||||
|
||||
export const MEMBERSHIP_STATUS_LABELS: Record<MembershipStatus, string> = {
|
||||
active: "Active",
|
||||
inactive: "Inactive",
|
||||
suspended: "Suspended",
|
||||
};
|
||||
|
||||
export const PAYMENT_STATUS_LABELS: Record<PaymentStatus, string> = {
|
||||
pending: "Pending",
|
||||
completed: "Completed",
|
||||
failed: "Failed",
|
||||
refunded: "Refunded",
|
||||
};
|
||||
|
||||
export const GOAL_STATUS_LABELS: Record<GoalStatus, string> = {
|
||||
active: "Active",
|
||||
completed: "Completed",
|
||||
abandoned: "Abandoned",
|
||||
paused: "Paused",
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './types'
|
||||
export * from './schemas'
|
||||
export * from './utils'
|
||||
export * from "./types";
|
||||
export * from "./schemas";
|
||||
export * from "./utils";
|
||||
export * from "./constants";
|
||||
@@ -1,3 +1,20 @@
|
||||
import type {
|
||||
UserRole,
|
||||
MembershipType,
|
||||
MembershipStatus,
|
||||
Gender,
|
||||
ActivityLevel,
|
||||
GoalType,
|
||||
GoalStatus,
|
||||
PriorityLevel,
|
||||
AttendanceType,
|
||||
PaymentStatus,
|
||||
PaymentMethod,
|
||||
NotificationType,
|
||||
GymStatus,
|
||||
RecommendationStatus,
|
||||
} from "../constants";
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
@@ -5,7 +22,7 @@ export interface User {
|
||||
lastName: string;
|
||||
password?: string;
|
||||
phone?: string;
|
||||
role: "superAdmin" | "admin" | "trainer" | "client";
|
||||
role: UserRole;
|
||||
gymId?: string;
|
||||
imageUrl?: string;
|
||||
createdAt: Date;
|
||||
@@ -16,8 +33,8 @@ export interface Client {
|
||||
id: string;
|
||||
userId: string;
|
||||
user?: User;
|
||||
membershipType: "basic" | "premium" | "vip";
|
||||
membershipStatus: "active" | "inactive" | "suspended" | "expired";
|
||||
membershipType: MembershipType;
|
||||
membershipStatus: MembershipStatus;
|
||||
joinDate: Date;
|
||||
lastVisit?: Date;
|
||||
emergencyContact?: {
|
||||
@@ -30,20 +47,13 @@ export interface Client {
|
||||
export interface FitnessProfile {
|
||||
id: string;
|
||||
userId: string;
|
||||
height: string;
|
||||
weight: string;
|
||||
age: string;
|
||||
gender: "male" | "female" | "other";
|
||||
activityLevel:
|
||||
| "sedentary"
|
||||
| "lightly_active"
|
||||
| "moderately_active"
|
||||
| "very_active"
|
||||
| "extremely_active";
|
||||
fitnessGoals: string[];
|
||||
exerciseHabits: string;
|
||||
dietHabits: string;
|
||||
medicalConditions: string;
|
||||
height?: number; // in cm (fixed from string to number)
|
||||
weight?: number; // in kg (fixed from string to number)
|
||||
age?: number; // (fixed from string to number)
|
||||
gender?: Gender;
|
||||
activityLevel?: ActivityLevel;
|
||||
fitnessGoals?: string[];
|
||||
medicalConditions?: string;
|
||||
allergies?: string;
|
||||
injuries?: string;
|
||||
createdAt: Date;
|
||||
@@ -57,7 +67,7 @@ export interface Attendance {
|
||||
client?: Client;
|
||||
checkInTime: Date;
|
||||
checkOutTime?: Date;
|
||||
type: "gym" | "class" | "personal_training";
|
||||
type: AttendanceType;
|
||||
notes?: string;
|
||||
createdAt?: Date;
|
||||
}
|
||||
@@ -65,28 +75,23 @@ export interface Attendance {
|
||||
export interface Recommendation {
|
||||
id: string;
|
||||
userId: string;
|
||||
fitnessProfileId?: string;
|
||||
type: "short_term" | "medium_term" | "long_term" | "ai_plan";
|
||||
fitnessProfileId: string;
|
||||
recommendationText: string;
|
||||
activityPlan?: string;
|
||||
dietPlan?: string;
|
||||
status: "pending" | "approved" | "rejected" | "completed";
|
||||
createdAt: Date;
|
||||
activityPlan: string;
|
||||
dietPlan: string;
|
||||
status: RecommendationStatus;
|
||||
generatedAt: Date;
|
||||
approvedAt?: Date;
|
||||
approvedBy?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface FitnessGoal {
|
||||
id: string;
|
||||
userId: string;
|
||||
fitnessProfileId?: string;
|
||||
goalType:
|
||||
| "weight_target"
|
||||
| "strength_milestone"
|
||||
| "endurance_target"
|
||||
| "flexibility_goal"
|
||||
| "habit_building"
|
||||
| "custom";
|
||||
goalType: GoalType;
|
||||
title: string;
|
||||
description?: string;
|
||||
targetValue?: number;
|
||||
@@ -95,9 +100,9 @@ export interface FitnessGoal {
|
||||
startDate: Date;
|
||||
targetDate?: Date;
|
||||
completedDate?: Date;
|
||||
status: "active" | "completed" | "abandoned" | "paused";
|
||||
status: GoalStatus;
|
||||
progress: number;
|
||||
priority: "low" | "medium" | "high";
|
||||
priority: PriorityLevel;
|
||||
notes?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -106,11 +111,11 @@ export interface FitnessGoal {
|
||||
export interface Payment {
|
||||
id: string;
|
||||
clientId: string;
|
||||
client: Client;
|
||||
client?: Client; // Made optional (was required)
|
||||
amount: number;
|
||||
currency: string;
|
||||
status: "pending" | "completed" | "failed" | "refunded";
|
||||
paymentMethod: "cash" | "card" | "bank_transfer";
|
||||
status: PaymentStatus;
|
||||
paymentMethod: PaymentMethod;
|
||||
dueDate: Date;
|
||||
paidAt?: Date;
|
||||
description: string;
|
||||
@@ -121,7 +126,7 @@ export interface Notification {
|
||||
userId: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: "payment_reminder" | "attendance" | "promotion" | "system";
|
||||
type: NotificationType;
|
||||
read: boolean;
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -130,7 +135,7 @@ export interface Gym {
|
||||
id: string;
|
||||
name: string;
|
||||
location?: string;
|
||||
status: "active" | "inactive";
|
||||
status: GymStatus;
|
||||
adminUserId: string;
|
||||
}
|
||||
|
||||
@@ -138,7 +143,7 @@ export interface Invitation {
|
||||
id: string;
|
||||
inviterUserId: string;
|
||||
inviteeEmail: string;
|
||||
roleAssigned: "trainer" | "client" | "admin";
|
||||
roleAssigned: UserRole;
|
||||
gymId: string;
|
||||
token: string;
|
||||
status: "sent" | "accepted" | "expired";
|
||||
|
||||
Reference in new issue
Block a user